diff --git a/pom.xml b/pom.xml index 1c320dd..1937c5d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,309 +1,309 @@ - - 4.0.0 - - cokr.xit - xit-foundation - 23.04.01 - jar - - xit-foundation - http://maven.apache.org - - - UTF-8 - - 1.8 - ${java.version} - ${java.version} - - 5.3.20 - 4.1.0 - - - - - mvn2s - https://repo1.maven.org/maven2/ - - true - - - true - - - - egovframe - http://maven.egovframe.kr:8080/maven/ - - true - - - false - - - - egovframe2 - https://www.egovframe.go.kr/maven/ - - true - - - false - - - - - - - - org.egovframe.rte - org.egovframe.rte.fdl.excel - ${org.egovframe.rte.version} - - - - org.egovframe.rte - org.egovframe.rte.ptl.mvc - ${org.egovframe.rte.version} - - - - org.egovframe.rte - org.egovframe.rte.fdl.property - ${org.egovframe.rte.version} - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - - javax.servlet.jsp - jsp-api - 2.2 - provided - - - - javax.servlet - jstl - 1.2 - - - - commons-fileupload - commons-fileupload - 1.3.1 - - - - org.springframework - spring-test - ${spring.maven.artifact.version} - - - - - commons-dbcp - commons-dbcp - 1.4 - - - - org.bgee.log4jdbc-log4j2 - log4jdbc-log4j2-jdbc4.1 - 1.16 - - - - org.projectlombok - lombok - 1.18.16 - - - - org.junit.jupiter - junit-jupiter-engine - 5.9.2 - - - - - org.mariadb.jdbc - mariadb-java-client - 2.7.2 - test - - - - - - install - ${basedir}/target - ${project.artifactId}-${project.version} - - - ${basedir}/src/main/resources - - - - ${basedir}/src/test/resources - ${basedir}/src/main/resources - - - - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - 80 - / - - -Xms256m -Xmx768m -XX:MaxPermSize=256m - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - UTF-8 - - - - org.codehaus.mojo - hibernate3-maven-plugin - 2.1 - - - - hbm2ddl - annotationconfiguration - - - - - - org.hsqldb - hsqldb - 2.3.2 - - - - - - org.codehaus.mojo - emma-maven-plugin - 1.0-alpha-3 - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.1 - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - **/*.class - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0 - - true - xml - - **/Abstract*.java - **/*Suite.java - - - **/*Test.java - - - - - org.codehaus.mojo - emma-maven-plugin - true - - - org.apache.maven.plugins - maven-source-plugin - 2.2 - - - attach-sources - - jar - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - - - - - - - maven-snapshot - http://xit.xit-nexus.com:8081/repository/maven-snapshots/ - - - - maven-release - http://xit.xit-nexus.com:8081/repository/maven-releases/ - - - - + + 4.0.0 + + cokr.xit + xit-foundation + 23.04.01 + jar + + xit-foundation + http://maven.apache.org + + + UTF-8 + + 1.8 + ${java.version} + ${java.version} + + 5.3.20 + 4.1.0 + + + + + mvn2s + https://repo1.maven.org/maven2/ + + true + + + true + + + + egovframe + http://maven.egovframe.kr:8080/maven/ + + true + + + false + + + + egovframe2 + https://www.egovframe.go.kr/maven/ + + true + + + false + + + + + + + + org.egovframe.rte + org.egovframe.rte.fdl.excel + ${org.egovframe.rte.version} + + + + org.egovframe.rte + org.egovframe.rte.ptl.mvc + ${org.egovframe.rte.version} + + + + org.egovframe.rte + org.egovframe.rte.fdl.property + ${org.egovframe.rte.version} + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + javax.servlet.jsp + jsp-api + 2.2 + provided + + + + javax.servlet + jstl + 1.2 + + + + commons-fileupload + commons-fileupload + 1.3.1 + + + + org.springframework + spring-test + ${spring.maven.artifact.version} + + + + + commons-dbcp + commons-dbcp + 1.4 + + + + org.bgee.log4jdbc-log4j2 + log4jdbc-log4j2-jdbc4.1 + 1.16 + + + + org.projectlombok + lombok + 1.18.16 + + + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + + + + + org.mariadb.jdbc + mariadb-java-client + 2.7.2 + test + + + + + + install + ${basedir}/target + ${project.artifactId}-${project.version} + + + ${basedir}/src/main/resources + + + + ${basedir}/src/test/resources + ${basedir}/src/main/resources + + + + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + 80 + / + + -Xms256m -Xmx768m -XX:MaxPermSize=256m + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + + + + org.codehaus.mojo + hibernate3-maven-plugin + 2.1 + + + + hbm2ddl + annotationconfiguration + + + + + + org.hsqldb + hsqldb + 2.3.2 + + + + + + org.codehaus.mojo + emma-maven-plugin + 1.0-alpha-3 + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.1 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + **/*.class + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0 + + true + xml + + **/Abstract*.java + **/*Suite.java + + + **/*Test.java + + + + + org.codehaus.mojo + emma-maven-plugin + true + + + org.apache.maven.plugins + maven-source-plugin + 2.2 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + + + + + + + maven-snapshot + http://xit.xit-nexus.com:8081/repository/maven-snapshots/ + + + + maven-release + http://xit.xit-nexus.com:8081/repository/maven-releases/ + + + + \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/AbstractComponent.java b/src/main/java/cokr/xit/foundation/AbstractComponent.java index 5ee425c..4c0e3c0 100644 --- a/src/main/java/cokr/xit/foundation/AbstractComponent.java +++ b/src/main/java/cokr/xit/foundation/AbstractComponent.java @@ -1,208 +1,208 @@ -package cokr.xit.foundation; - -import java.util.function.Supplier; - -import javax.annotation.Resource; - -import org.egovframe.rte.fdl.property.EgovPropertyService; -import org.springframework.context.MessageSource; - -import cokr.xit.foundation.data.Convert; - -/**업무 컴포넌트나 기능 구현 컴포넌트를 정의할 때 상속받는 클래스. - *

AbstractComponent를 상속받는 클래스는 - *

- * 등의 기능을 사용할 수 있다. - *

AbstractComponent의 상속 클래스는 '업무 용어 또는 기능 이름' + 'Bean'과 같이 이름을 명시한다.
- * 그리고 정의한 클래스를 애플리케이션 컨텍스트에 등록하려면, @Component 어노테이션을 적용하고 카멜 표기법으로 이름을 지정한다.
- * 다음은 그 예이다. - *

 package cokr.xit.example.business.service.bean;
- * {@code @Component("businessBean")}
- * public class BusinessBean extends AbstractComponent {
- *     ...
- * }
- *

정의한 컴포넌트를 사용하려면 @Resource 어노테이션에 이름을 명시해 객체에 주입한다. - *

 @Resource(name = "businessBean")
- * BusinessBean businessBean;
- */ -public class AbstractComponent { - /** 프로퍼티 서비스 */ - @Resource(name="propertyService") - protected EgovPropertyService properties; - /** 메시지 소스 */ - @Resource(name="messageSource") - protected MessageSource messages; - private String logName; - - /**로그이름을 설정한다. - * @param logName 로그이름 - */ - public void setLogName(String logName) { - this.logName = logName; - } - - /**현재 접근한 사용자를 반환한다. - * @param 사용자 정보 유형 - * @return 현재 접근한 사용자 - */ - protected T currentUser() { - return UserInfo.current(); - } - - /**현재 사용자의 접근정보를 반환한다. - * @return 현재 사용자의 접근정보 - */ - protected static Access currentAccess() { - return Access.current(); - } - - /**lv와 rv가 같은 지 반환한다. - * @param lv 좌측 값 - * @param rv 우측 값 - * @return - *
  • lv와 rv가 같으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - protected static final boolean equals(Object lv, Object rv) { - return lv == rv ? true : lv != null && lv.equals(rv); - } - - /**{@link Assert#isEmpty(Object)} 참고 - */ - protected static final boolean isEmpty(Object obj) { - return Assert.isEmpty(obj); - } - - /**{@link Assert#ifEmpty(Object, Supplier)} 참고*/ - protected static final T ifEmpty(T t, Supplier nt) { - return Assert.ifEmpty(t, nt); - } - - /**{@link Assert#ifEmpty(Object, Object)} 참고. - */ - protected static final T ifEmpty(T t, T nt) { - return Assert.ifEmpty(t, nt); - } - - /**obj가 빈 값이면 공백문자("")를 반환한다. 빈 값이 아니면 obj를 String으로 캐스팅하여 반환한다. - * @param obj 객체 - * @return - *
  • obj가 빈 값이면 공백문자("")
  • - *
  • 빈 값이 아니면 String으로 캐스팅한 obj
  • - *
- */ - protected static final String blankIfEmpty(Object obj) { - return ifEmpty((String)obj, ""); - } - - /**{@link Assert#notEmpty(Object, String)} 참고. - */ - protected static final T notEmpty(T t, String name) { - return Assert.notEmpty(t, name); - } - - /**See {@link Convert#toShort(Object)}.*/ - protected static final short toShort(Object obj) { - return Convert.toShort(obj); - } - - /**See {@link Convert#toInt(Object)}.*/ - protected static final int toInt(Object obj) { - return Convert.toInt(obj); - } - - /**See {@link Convert#toLong(Object)}.*/ - protected static final long toLong(Object obj) { - return Convert.toLong(obj); - } - - /**See {@link Convert#toDouble(Object)}.*/ - protected static final double toDouble(Object obj) { - return Convert.toDouble(obj); - } - - /**See {@link Convert#toFloat(Object)}.*/ - protected static final float toFloat(Object obj) { - return Convert.toFloat(obj); - } - - /**See {@link Convert#toByte(Object)}.*/ - protected static final byte toByte(Object obj) { - return Convert.toByte(obj); - } - - /**See {@link Convert#toBoolean(Object)}.*/ - protected static final boolean toBoolean(Object obj) { - return Convert.toBoolean(obj); - } - - /**메시지 리소스 파일에서 key로 정의된 문자열과 args로 메시지를 만들어 반환한다. - * @param key 메시지 리소스 파일의 문자열 key - * @param args 해당 문자열에 전달할 인자 - * @return 메시지 - */ - protected String message(String key, String... args) { - return messages.getMessage(key, ifEmpty(args, () -> null), "", currentAccess().getLocale()); - } - - /**t가 공백이 아닌지 확인하고 t를 반환한다. - * t가 공백이면, NullPointerException을 발생시키며, 메시지는 message-common 프로퍼티 파일의 'valueRequired'키에 설정된 값으로 만든다. - * @param 객체 유형 - * @param t 값이나 객체 - * @param name assertion 실패 시 오류 메시지에 사용할 이름 - * @return t - */ - protected final T required(T t, String name) { - if (!isEmpty(t)) return t; - throw new NullPointerException(message("valueRequired", name)); - } - - /**{@link Assert#rootCause(Throwable)} 참고 - */ - protected static final Throwable rootCause(Throwable t) { - return Assert.rootCause(t); - } - - /**{@link ApplicationException#get(Throwable)} 참고 - */ - protected static final ApplicationException applicationException(Throwable t) { - return ApplicationException.get(t); - } - - /**{@link Assert#runtimeException(Throwable)} 참고 - */ - protected static final RuntimeException runtimeException(Throwable t) { - return Assert.runtimeException(t); - } - - /**klass와 연계된 Log를 반환한다. - * @param klass Log와 연계된 클래스 - * @return Log - */ - protected static Log log(Class klass) { - return Log.get(klass); - } - - /**현재 클래스와 연계된 Log를 반환한다. - * @return Log - */ - protected Log log() { - return isEmpty(logName) ? log(getClass()) : Log.get(logName); - } - - /**현재 객체를 반환한다. - * @param AbstractComponent 유형 - * @return 현재 객체 - */ - @SuppressWarnings("unchecked") - protected T self() { - return (T)this; - } +package cokr.xit.foundation; + +import java.util.function.Supplier; + +import javax.annotation.Resource; + +import org.egovframe.rte.fdl.property.EgovPropertyService; +import org.springframework.context.MessageSource; + +import cokr.xit.foundation.data.Convert; + +/**업무 컴포넌트나 기능 구현 컴포넌트를 정의할 때 상속받는 클래스. + *

AbstractComponent를 상속받는 클래스는 + *

  • {@link Assert}
  • + *
  • {@link #log() 로깅} 서비스
  • + *
  • {@link #currentAccess() 현재 접속 정보}
  • + *
  • {@link #currentUser() 현재 접속한 사용자 정보}
  • + *
  • {@link #properties 프로퍼티 서비스}
  • + *
  • {@link #message(String, String...) 메시지 처리}
  • + *
+ * 등의 기능을 사용할 수 있다. + *

AbstractComponent의 상속 클래스는 '업무 용어 또는 기능 이름' + 'Bean'과 같이 이름을 명시한다.
+ * 그리고 정의한 클래스를 애플리케이션 컨텍스트에 등록하려면, @Component 어노테이션을 적용하고 카멜 표기법으로 이름을 지정한다.
+ * 다음은 그 예이다. + *

 package cokr.xit.example.business.service.bean;
+ * {@code @Component("businessBean")}
+ * public class BusinessBean extends AbstractComponent {
+ *     ...
+ * }
+ *

정의한 컴포넌트를 사용하려면 @Resource 어노테이션에 이름을 명시해 객체에 주입한다. + *

 @Resource(name = "businessBean")
+ * BusinessBean businessBean;
+ */ +public class AbstractComponent { + /** 프로퍼티 서비스 */ + @Resource(name="propertyService") + protected EgovPropertyService properties; + /** 메시지 소스 */ + @Resource(name="messageSource") + protected MessageSource messages; + private String logName; + + /**로그이름을 설정한다. + * @param logName 로그이름 + */ + public void setLogName(String logName) { + this.logName = logName; + } + + /**현재 접근한 사용자를 반환한다. + * @param 사용자 정보 유형 + * @return 현재 접근한 사용자 + */ + protected T currentUser() { + return UserInfo.current(); + } + + /**현재 사용자의 접근정보를 반환한다. + * @return 현재 사용자의 접근정보 + */ + protected static Access currentAccess() { + return Access.current(); + } + + /**lv와 rv가 같은 지 반환한다. + * @param lv 좌측 값 + * @param rv 우측 값 + * @return + *
  • lv와 rv가 같으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + protected static final boolean equals(Object lv, Object rv) { + return lv == rv ? true : lv != null && lv.equals(rv); + } + + /**{@link Assert#isEmpty(Object)} 참고 + */ + protected static final boolean isEmpty(Object obj) { + return Assert.isEmpty(obj); + } + + /**{@link Assert#ifEmpty(Object, Supplier)} 참고*/ + protected static final T ifEmpty(T t, Supplier nt) { + return Assert.ifEmpty(t, nt); + } + + /**{@link Assert#ifEmpty(Object, Object)} 참고. + */ + protected static final T ifEmpty(T t, T nt) { + return Assert.ifEmpty(t, nt); + } + + /**obj가 빈 값이면 공백문자("")를 반환한다. 빈 값이 아니면 obj를 String으로 캐스팅하여 반환한다. + * @param obj 객체 + * @return + *
  • obj가 빈 값이면 공백문자("")
  • + *
  • 빈 값이 아니면 String으로 캐스팅한 obj
  • + *
+ */ + protected static final String blankIfEmpty(Object obj) { + return ifEmpty((String)obj, ""); + } + + /**{@link Assert#notEmpty(Object, String)} 참고. + */ + protected static final T notEmpty(T t, String name) { + return Assert.notEmpty(t, name); + } + + /**See {@link Convert#toShort(Object)}.*/ + protected static final short toShort(Object obj) { + return Convert.toShort(obj); + } + + /**See {@link Convert#toInt(Object)}.*/ + protected static final int toInt(Object obj) { + return Convert.toInt(obj); + } + + /**See {@link Convert#toLong(Object)}.*/ + protected static final long toLong(Object obj) { + return Convert.toLong(obj); + } + + /**See {@link Convert#toDouble(Object)}.*/ + protected static final double toDouble(Object obj) { + return Convert.toDouble(obj); + } + + /**See {@link Convert#toFloat(Object)}.*/ + protected static final float toFloat(Object obj) { + return Convert.toFloat(obj); + } + + /**See {@link Convert#toByte(Object)}.*/ + protected static final byte toByte(Object obj) { + return Convert.toByte(obj); + } + + /**See {@link Convert#toBoolean(Object)}.*/ + protected static final boolean toBoolean(Object obj) { + return Convert.toBoolean(obj); + } + + /**메시지 리소스 파일에서 key로 정의된 문자열과 args로 메시지를 만들어 반환한다. + * @param key 메시지 리소스 파일의 문자열 key + * @param args 해당 문자열에 전달할 인자 + * @return 메시지 + */ + protected String message(String key, String... args) { + return messages.getMessage(key, ifEmpty(args, () -> null), "", currentAccess().getLocale()); + } + + /**t가 공백이 아닌지 확인하고 t를 반환한다. + * t가 공백이면, NullPointerException을 발생시키며, 메시지는 message-common 프로퍼티 파일의 'valueRequired'키에 설정된 값으로 만든다. + * @param 객체 유형 + * @param t 값이나 객체 + * @param name assertion 실패 시 오류 메시지에 사용할 이름 + * @return t + */ + protected final T required(T t, String name) { + if (!isEmpty(t)) return t; + throw new NullPointerException(message("valueRequired", name)); + } + + /**{@link Assert#rootCause(Throwable)} 참고 + */ + protected static final Throwable rootCause(Throwable t) { + return Assert.rootCause(t); + } + + /**{@link ApplicationException#get(Throwable)} 참고 + */ + protected static final ApplicationException applicationException(Throwable t) { + return ApplicationException.get(t); + } + + /**{@link Assert#runtimeException(Throwable)} 참고 + */ + protected static final RuntimeException runtimeException(Throwable t) { + return Assert.runtimeException(t); + } + + /**klass와 연계된 Log를 반환한다. + * @param klass Log와 연계된 클래스 + * @return Log + */ + protected static Log log(Class klass) { + return Log.get(klass); + } + + /**현재 클래스와 연계된 Log를 반환한다. + * @return Log + */ + protected Log log() { + return isEmpty(logName) ? log(getClass()) : Log.get(logName); + } + + /**현재 객체를 반환한다. + * @param AbstractComponent 유형 + * @return 현재 객체 + */ + @SuppressWarnings("unchecked") + protected T self() { + return (T)this; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/Access.java b/src/main/java/cokr/xit/foundation/Access.java index 514a921..ea39c3c 100644 --- a/src/main/java/cokr/xit/foundation/Access.java +++ b/src/main/java/cokr/xit/foundation/Access.java @@ -1,229 +1,229 @@ -package cokr.xit.foundation; - -import java.util.Locale; - -/**현재 클라이언트(사용자)의 접근정보를 추출하여 제공하는 클래스. - */ -public class Access extends AbstractComponent { - protected static final ThreadLocal current = new ThreadLocal<>(); - - private String - id, - sessionID, - action, - ipAddress; - private boolean - newSession, - mobile, - ajaxRequest, - jsonResponse; - private Locale locale; - - private static final String - LOCALHOST_V4 = "127.0.0.1", - LOCALHOST_V6 = "0:0:0:0:0:0:0:1"; - - /**현재 클라이언트의 IP주소가 로컬호스트와 같으면 로컬호스트의 주소를 반환한다. - * @param address 클라이언트의 IP 주소 - * @param localhost 로컬호스트 주소 - * @return - *
  • 현재 클라이언트의 IP주소가 로컬호스트와 같으면 로컬호스트의 주소
  • - *
  • 그렇지 않으면 클라이언트 주소
  • - *
- */ - public static String getClientAddress(String address, String localhost) { - return LOCALHOST_V4.equals(address) || LOCALHOST_V6.equals(address) ? localhost : address; - } - - /**현재 클라이언트의 접근 아이디를 반환한다. - * @return 현재 클라이언트의 접근 아이디 - */ - public String getId() { - return ifEmpty(id, id = "access-" + System.currentTimeMillis()); - } - - /**세션 아이디를 반환한다. - * @return 세션 아이디 - */ - public String getSessionID() { - return sessionID; - } - - /**세션 아이디를 설정한다. - * @param sessionID 세션 아이디 - * @return Access - */ - public Access setSessionID(String sessionID) { - this.sessionID = sessionID; - return this; - } - - /**접근 후 실행할 액션이름(예: uri나 url)을 반환한다. - * @return 접근 후 실행할 액션이름 - */ - public String getAction() { - return action; - } - - /**접근 후 실행할 액션이름(예:uri나 url)을 설정한다. - * @param action 접근 후 실행할 액션이름 - * @return Access - */ - public Access setAction(String action) { - this.action = action; - return this; - } - - /**클라이언트의 IP주소를 반환한다. - * @return 클라이언트의 IP주소 - */ - public String getIpAddress() { - return ipAddress; - } - - /**클라이언트의 IP주소를 설정한다. - * @param ipAddress 클라이언트의 IP주소 - * @return Access - */ - public Access setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - return this; - } - - /**현재 스레드의 클라이언트의 Access 정보를 반환한다. - * @return 현재 스레드의 클라이언트의 Access - */ - public static Access current() { - return ifEmpty(current.get(), Access::new); - } - - /**현재 Access를 현재 스레드의 클라이언트의 접근정보로 설정한다. - * @return Access - */ - public Access setCurrent() { - current.set(this); - return this; - } - - /**현재 Access의 세션이 새 세션인지 반환한다. - * @return - *
  • 새 세션이면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean inNewSession() { - return newSession; - } - - /**현재 Access의 세션이 새 세션인지 설정한다. - * @param newSession - *
  • 새 세션이면 true
  • - *
  • 그렇지 않으면 false
  • - *
- * @return Access - */ - public Access setNewSession(boolean newSession) { - this.newSession = newSession; - return this; - } - - /**클라이언트가 모바일 기기로 접근한 것인지 반환한다. - * @return - *
  • 모바일 기기로 접근했으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isMobile() { - return mobile; - } - - /**클라이언트가 모바일 기기로 접근한 것인지 설정한다. - * @param mobile - *
  • 모바일 기기로 접근했으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- * @return Access - */ - public Access setMobile(boolean mobile) { - this.mobile = mobile; - return this; - } - - /**접근요청이 AJAX로 된 것인지 반환한다. - * @return - *
  • AJAX로 요청됐으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isAjaxRequest() { - return ajaxRequest; - } - - /**접근요청이 AJAX로 된 것인지 설정한다. - * @param header HTTP 요청의 'X-Requested-With' 헤더값 - * @return Access - */ - public Access setAjaxRequest(String header) { - ajaxRequest = "XMLHttpRequest".equalsIgnoreCase(header); - return this; - } - - /**접근요청의 응답을 JSON으로 하는지 반환한다. - * @return - *
  • JSON 응답이면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isJsonResponse() { - return jsonResponse; - } - - /**접근요청의 응답을 JSON으로 하는지 설정한다. - * @param header HTTP 요청의 'accept' 헤더값 - * @return Access - */ - public Access setJsonResponse(String header) { - this.jsonResponse = !isEmpty(header) && header.contains("json"); - return this; - } - - /**접근 요청의 Locale을 반환한다. - * @return 접근 요청의 Locale - */ - public Locale getLocale() { - return locale != null ? locale : Locale.getDefault(); - } - - /**접근 요청의 Locale을 설정한다. - * @param locale Locale - * @return Access - */ - public Access setLocale(Locale locale) { - this.locale = locale; - return this; - } - - /**접근요청이 디버그 모드에서 된 것인지 반환한다. - * @return - *
  • 디버그 모드면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isDebug() { - return "true".equals(System.getProperty("debug")); - } - - /**현재 Access를 현재 스레드에서 제거한다. - * @return 제거된 Access - */ - public static Access release() { - Access access = current.get(); - current.remove(); - return access; - } - - @Override - public String toString() { - return String.format("%s(id:'%s', clientIP:'%s', action:'%s')", getClass().getSimpleName(), getId(), getIpAddress(), getAction()); - } +package cokr.xit.foundation; + +import java.util.Locale; + +/**현재 클라이언트(사용자)의 접근정보를 추출하여 제공하는 클래스. + */ +public class Access extends AbstractComponent { + protected static final ThreadLocal current = new ThreadLocal<>(); + + private String + id, + sessionID, + action, + ipAddress; + private boolean + newSession, + mobile, + ajaxRequest, + jsonResponse; + private Locale locale; + + private static final String + LOCALHOST_V4 = "127.0.0.1", + LOCALHOST_V6 = "0:0:0:0:0:0:0:1"; + + /**현재 클라이언트의 IP주소가 로컬호스트와 같으면 로컬호스트의 주소를 반환한다. + * @param address 클라이언트의 IP 주소 + * @param localhost 로컬호스트 주소 + * @return + *
  • 현재 클라이언트의 IP주소가 로컬호스트와 같으면 로컬호스트의 주소
  • + *
  • 그렇지 않으면 클라이언트 주소
  • + *
+ */ + public static String getClientAddress(String address, String localhost) { + return LOCALHOST_V4.equals(address) || LOCALHOST_V6.equals(address) ? localhost : address; + } + + /**현재 클라이언트의 접근 아이디를 반환한다. + * @return 현재 클라이언트의 접근 아이디 + */ + public String getId() { + return ifEmpty(id, id = "access-" + System.currentTimeMillis()); + } + + /**세션 아이디를 반환한다. + * @return 세션 아이디 + */ + public String getSessionID() { + return sessionID; + } + + /**세션 아이디를 설정한다. + * @param sessionID 세션 아이디 + * @return Access + */ + public Access setSessionID(String sessionID) { + this.sessionID = sessionID; + return this; + } + + /**접근 후 실행할 액션이름(예: uri나 url)을 반환한다. + * @return 접근 후 실행할 액션이름 + */ + public String getAction() { + return action; + } + + /**접근 후 실행할 액션이름(예:uri나 url)을 설정한다. + * @param action 접근 후 실행할 액션이름 + * @return Access + */ + public Access setAction(String action) { + this.action = action; + return this; + } + + /**클라이언트의 IP주소를 반환한다. + * @return 클라이언트의 IP주소 + */ + public String getIpAddress() { + return ipAddress; + } + + /**클라이언트의 IP주소를 설정한다. + * @param ipAddress 클라이언트의 IP주소 + * @return Access + */ + public Access setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + return this; + } + + /**현재 스레드의 클라이언트의 Access 정보를 반환한다. + * @return 현재 스레드의 클라이언트의 Access + */ + public static Access current() { + return ifEmpty(current.get(), Access::new); + } + + /**현재 Access를 현재 스레드의 클라이언트의 접근정보로 설정한다. + * @return Access + */ + public Access setCurrent() { + current.set(this); + return this; + } + + /**현재 Access의 세션이 새 세션인지 반환한다. + * @return + *
  • 새 세션이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean inNewSession() { + return newSession; + } + + /**현재 Access의 세션이 새 세션인지 설정한다. + * @param newSession + *
  • 새 세션이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ * @return Access + */ + public Access setNewSession(boolean newSession) { + this.newSession = newSession; + return this; + } + + /**클라이언트가 모바일 기기로 접근한 것인지 반환한다. + * @return + *
  • 모바일 기기로 접근했으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isMobile() { + return mobile; + } + + /**클라이언트가 모바일 기기로 접근한 것인지 설정한다. + * @param mobile + *
  • 모바일 기기로 접근했으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ * @return Access + */ + public Access setMobile(boolean mobile) { + this.mobile = mobile; + return this; + } + + /**접근요청이 AJAX로 된 것인지 반환한다. + * @return + *
  • AJAX로 요청됐으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isAjaxRequest() { + return ajaxRequest; + } + + /**접근요청이 AJAX로 된 것인지 설정한다. + * @param header HTTP 요청의 'X-Requested-With' 헤더값 + * @return Access + */ + public Access setAjaxRequest(String header) { + ajaxRequest = "XMLHttpRequest".equalsIgnoreCase(header); + return this; + } + + /**접근요청의 응답을 JSON으로 하는지 반환한다. + * @return + *
  • JSON 응답이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isJsonResponse() { + return jsonResponse; + } + + /**접근요청의 응답을 JSON으로 하는지 설정한다. + * @param header HTTP 요청의 'accept' 헤더값 + * @return Access + */ + public Access setJsonResponse(String header) { + this.jsonResponse = !isEmpty(header) && header.contains("json"); + return this; + } + + /**접근 요청의 Locale을 반환한다. + * @return 접근 요청의 Locale + */ + public Locale getLocale() { + return locale != null ? locale : Locale.getDefault(); + } + + /**접근 요청의 Locale을 설정한다. + * @param locale Locale + * @return Access + */ + public Access setLocale(Locale locale) { + this.locale = locale; + return this; + } + + /**접근요청이 디버그 모드에서 된 것인지 반환한다. + * @return + *
  • 디버그 모드면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isDebug() { + return "true".equals(System.getProperty("debug")); + } + + /**현재 Access를 현재 스레드에서 제거한다. + * @return 제거된 Access + */ + public static Access release() { + Access access = current.get(); + current.remove(); + return access; + } + + @Override + public String toString() { + return String.format("%s(id:'%s', clientIP:'%s', action:'%s')", getClass().getSimpleName(), getId(), getIpAddress(), getAction()); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/ApplicationContainer.java b/src/main/java/cokr/xit/foundation/ApplicationContainer.java index 57efe5a..acff280 100644 --- a/src/main/java/cokr/xit/foundation/ApplicationContainer.java +++ b/src/main/java/cokr/xit/foundation/ApplicationContainer.java @@ -1,146 +1,146 @@ -package cokr.xit.foundation; - -import java.net.InetAddress; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Component; - -/**애플리케이션이 동작하는 다음과 같은 환경정보를 제공한다. - *
  • {@link #getApplicationContext() ApplicationContext}
  • - *
  • {@link #getHostName() 호스트 정보}
  • - *
  • {@link #getActiveProfiles() 프로필 정보}
  • - *
  • {@link #getStartupDate() 애플리케이션 시작 일시}
  • - *
- * @author mjkhan - */ -@Component("applicationContainer") -public class ApplicationContainer implements ApplicationContextAware { - private static ApplicationContainer obj; - - public static ApplicationContainer get() { - return obj; - } - - private String - hostAddress, - hostName, - canonicalHostName; - private ApplicationContext actx; - private String applicationName; - private List activeProfiles; - private Date startupDate; - private boolean secured; - - private ApplicationContainer() { - try { - init(); - } catch (Exception e) { - throw ApplicationException.get(e); - } - } - - /**호스트의 IP 주소를 반환한다. - * @return 호스트의 IP 주소 - */ - public String getHostAddress() { - return hostAddress; - } - - /**호스트 이름을 반환한다. - * @return 호스트 이름 - */ - public String getHostName() { - return hostName; - } - - /**{@link InetAddress#getCanonicalHostName()} 참고 - * @return Canonical host name - */ - public String getCanonicalHostName() { - return canonicalHostName; - } - - /**ApplicationContainer를 초기화한다. - * @throws Exception - */ - protected void init() throws Exception { - InetAddress inetAddress = InetAddress.getLocalHost(); - hostAddress = inetAddress.getHostAddress(); - hostName = inetAddress.getHostName(); - canonicalHostName = inetAddress.getCanonicalHostName(); - } - - /**ApplicationContext를 반환한다. - * @return ApplicationContext - */ - public ApplicationContext getApplicationContext() { - return actx; - } - - private Log log() { - return Log.get(getClass()); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - actx = applicationContext; - applicationName = actx.getApplicationName(); - String[] apf = Assert.ifEmpty(actx.getEnvironment().getActiveProfiles(), actx.getEnvironment()::getDefaultProfiles); - activeProfiles = Stream.of(apf).collect(Collectors.toList()); - startupDate = new Date(actx.getStartupDate()); - secured = actx.containsBean("authorityService"); - log().debug("{} loaded", this); - obj = this; - } - - /**애플리케이션 이름을 반환한다. 애플리케이션 이름은 'spring.application.name' 프로퍼티로 설정한다. - * @return 애플리케이션 이름 - */ - public String getApplicationName() { - return applicationName; - } - - /**애플리케이션에 활성화된 프로필을 반환한다. - * @return 애플리케이션에 활성화된 프로필 - */ - public List getActiveProfiles() { - return activeProfiles; - } - - /**지정하는 프로필이 활성화 돼있는지 반환한다. - * @param profiles 프로필 - * @return 프로필의 활성화 여부 - *
  • 활성화 돼있으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isProfileActive(String... profiles) { - for (String profile: profiles) { - if (activeProfiles.contains(profile)) - return true; - } - return false; - } - - public boolean isSecured() { - return secured; - } - - /**애플리케이션이 시작한 일시를 반환한다. - * @return 애플리케이션이 시작한 일시 - */ - public Date getStartupDate() { - return startupDate; - } - - @Override - public String toString() { - return getClass().getSimpleName() + "[hostAddress=" + hostAddress + ", hostName=" + hostName + ", applicationName=" + applicationName + (secured ? ", secured" : "") + "]"; - } +package cokr.xit.foundation; + +import java.net.InetAddress; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/**애플리케이션이 동작하는 다음과 같은 환경정보를 제공한다. + *
  • {@link #getApplicationContext() ApplicationContext}
  • + *
  • {@link #getHostName() 호스트 정보}
  • + *
  • {@link #getActiveProfiles() 프로필 정보}
  • + *
  • {@link #getStartupDate() 애플리케이션 시작 일시}
  • + *
+ * @author mjkhan + */ +@Component("applicationContainer") +public class ApplicationContainer implements ApplicationContextAware { + private static ApplicationContainer obj; + + public static ApplicationContainer get() { + return obj; + } + + private String + hostAddress, + hostName, + canonicalHostName; + private ApplicationContext actx; + private String applicationName; + private List activeProfiles; + private Date startupDate; + private boolean secured; + + private ApplicationContainer() { + try { + init(); + } catch (Exception e) { + throw ApplicationException.get(e); + } + } + + /**호스트의 IP 주소를 반환한다. + * @return 호스트의 IP 주소 + */ + public String getHostAddress() { + return hostAddress; + } + + /**호스트 이름을 반환한다. + * @return 호스트 이름 + */ + public String getHostName() { + return hostName; + } + + /**{@link InetAddress#getCanonicalHostName()} 참고 + * @return Canonical host name + */ + public String getCanonicalHostName() { + return canonicalHostName; + } + + /**ApplicationContainer를 초기화한다. + * @throws Exception + */ + protected void init() throws Exception { + InetAddress inetAddress = InetAddress.getLocalHost(); + hostAddress = inetAddress.getHostAddress(); + hostName = inetAddress.getHostName(); + canonicalHostName = inetAddress.getCanonicalHostName(); + } + + /**ApplicationContext를 반환한다. + * @return ApplicationContext + */ + public ApplicationContext getApplicationContext() { + return actx; + } + + private Log log() { + return Log.get(getClass()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + actx = applicationContext; + applicationName = actx.getApplicationName(); + String[] apf = Assert.ifEmpty(actx.getEnvironment().getActiveProfiles(), actx.getEnvironment()::getDefaultProfiles); + activeProfiles = Stream.of(apf).collect(Collectors.toList()); + startupDate = new Date(actx.getStartupDate()); + secured = actx.containsBean("authorityService"); + log().debug("{} loaded", this); + obj = this; + } + + /**애플리케이션 이름을 반환한다. 애플리케이션 이름은 'spring.application.name' 프로퍼티로 설정한다. + * @return 애플리케이션 이름 + */ + public String getApplicationName() { + return applicationName; + } + + /**애플리케이션에 활성화된 프로필을 반환한다. + * @return 애플리케이션에 활성화된 프로필 + */ + public List getActiveProfiles() { + return activeProfiles; + } + + /**지정하는 프로필이 활성화 돼있는지 반환한다. + * @param profiles 프로필 + * @return 프로필의 활성화 여부 + *
  • 활성화 돼있으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isProfileActive(String... profiles) { + for (String profile: profiles) { + if (activeProfiles.contains(profile)) + return true; + } + return false; + } + + public boolean isSecured() { + return secured; + } + + /**애플리케이션이 시작한 일시를 반환한다. + * @return 애플리케이션이 시작한 일시 + */ + public Date getStartupDate() { + return startupDate; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[hostAddress=" + hostAddress + ", hostName=" + hostName + ", applicationName=" + applicationName + (secured ? ", secured" : "") + "]"; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/ApplicationException.java b/src/main/java/cokr/xit/foundation/ApplicationException.java index 4e100a4..afe43ee 100644 --- a/src/main/java/cokr/xit/foundation/ApplicationException.java +++ b/src/main/java/cokr/xit/foundation/ApplicationException.java @@ -1,79 +1,79 @@ -package cokr.xit.foundation; - -import cokr.xit.foundation.data.DataObject; - -/**애플리케이션이 발생시키는 예외 - * @author mjkhan - */ -public class ApplicationException extends RuntimeException { - private static final long serialVersionUID = 1L; - - /**주어진 오류를 최초 원인으로 하는 ApplicationException을 반환한다. - * @param cause 최초 원인이 되는 오류 - * @return ApplicationException - */ - public static ApplicationException get(Throwable cause) { - return cause instanceof ApplicationException ? - ApplicationException.class.cast(cause) : - new ApplicationException(cause); - } - - private DataObject objs; - - private ApplicationException(Throwable cause) { - super(Assert.rootCause(cause)); - } - - /**추가정보를 갖는 DataObject를 반환한다. - * @return 추가정보를 갖는 DataObject - */ - public DataObject info() { - if (objs == null) - objs = new DataObject(); - return objs; - } - - /**추가정보를 설정한다. - * @param key 추가정보의 키 - * @param value 추가정보 - * @return ApplicationException - */ - public ApplicationException info(String key, Object value) { - info().set(key, value); - return this; - } - - /**에러코드를 반환한다. - * @return 에러코드 - */ - public String getCode() { - return objs != null ? objs.string("code") : null; - } - - /**에러코드를 설정한다. - * @param errorCode 에러코드 - * @return ApplicationException - */ - public ApplicationException setCode(String errorCode) { - return info("code", errorCode); - } - - @Override - public String getMessage() { - String msg = objs != null ? objs.string("msg") : ""; - if (Assert.isEmpty(msg)) { - Throwable cause = getCause(); - if (cause != null) - msg = cause.getMessage(); - } - return msg; - } - - /**에러 메시지를 설정한다. - * @param msg 에러 메시지 - * @return ApplicationException - */ - public ApplicationException setMessage(String msg) { - return info("msg", msg); - } +package cokr.xit.foundation; + +import cokr.xit.foundation.data.DataObject; + +/**애플리케이션이 발생시키는 예외 + * @author mjkhan + */ +public class ApplicationException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /**주어진 오류를 최초 원인으로 하는 ApplicationException을 반환한다. + * @param cause 최초 원인이 되는 오류 + * @return ApplicationException + */ + public static ApplicationException get(Throwable cause) { + return cause instanceof ApplicationException ? + ApplicationException.class.cast(cause) : + new ApplicationException(cause); + } + + private DataObject objs; + + private ApplicationException(Throwable cause) { + super(Assert.rootCause(cause)); + } + + /**추가정보를 갖는 DataObject를 반환한다. + * @return 추가정보를 갖는 DataObject + */ + public DataObject info() { + if (objs == null) + objs = new DataObject(); + return objs; + } + + /**추가정보를 설정한다. + * @param key 추가정보의 키 + * @param value 추가정보 + * @return ApplicationException + */ + public ApplicationException info(String key, Object value) { + info().set(key, value); + return this; + } + + /**에러코드를 반환한다. + * @return 에러코드 + */ + public String getCode() { + return objs != null ? objs.string("code") : null; + } + + /**에러코드를 설정한다. + * @param errorCode 에러코드 + * @return ApplicationException + */ + public ApplicationException setCode(String errorCode) { + return info("code", errorCode); + } + + @Override + public String getMessage() { + String msg = objs != null ? objs.string("msg") : ""; + if (Assert.isEmpty(msg)) { + Throwable cause = getCause(); + if (cause != null) + msg = cause.getMessage(); + } + return msg; + } + + /**에러 메시지를 설정한다. + * @param msg 에러 메시지 + * @return ApplicationException + */ + public ApplicationException setMessage(String msg) { + return info("msg", msg); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/Assert.java b/src/main/java/cokr/xit/foundation/Assert.java index bb57c7c..c1dc8c5 100644 --- a/src/main/java/cokr/xit/foundation/Assert.java +++ b/src/main/java/cokr/xit/foundation/Assert.java @@ -1,152 +1,152 @@ -package cokr.xit.foundation; - -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.util.function.Supplier; - -/**Assertion 유틸리티 - * */ -public class Assert { - private Assert() {} - - /**obj가 null인지, 공백문자인지, 빈 collection인지, 빈 배열인지 반환한다. - * @param obj 오브젝트 - * @return - *
  • obj가 null인지, 공백문자인지, 빈 collection인지, 빈 배열이면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public static final boolean isEmpty(Object obj) { - if (obj == null) return true; - if (obj instanceof String) { - String str = (String)obj; - return str.trim().isEmpty(); - } - if (obj instanceof Iterable) { - Iterable objs = (Iterable)obj; - return !objs.iterator().hasNext(); - } - if (obj.getClass().isArray()) { - return Array.getLength(obj) < 1; - } - return false; - } - - /**t가 {@link #isEmpty(Object) 공백이면} nt가 반환하는 값을, 그렇지 않으면 t를 반환한다. - * @param 타입 - * @param t 오브젝트 - * @param nt 공백일 경우 nt를 반환할 Supplier - * @return - *
  • t가 공백이 아니면 t
  • - *
  • t가 공백이면 Supplier가 반환하는 값
  • - *
- */ - public static final T ifEmpty(T t, Supplier nt) { - return !isEmpty(t) ? t : nt != null ? nt.get() : t; - } - - /**t가 {@link #isEmpty(Object) 공백이면} nt를, 그렇지 않으면 t를 반환한다. - * @param 타입 - * @param t 오브젝트 - * @param nt 공백일 경우 반환할 값 - * @return - *
  • t가 공백이 아니면 t
  • - *
  • t가 공백이면 nt
  • - *
- */ - public static final T ifEmpty(T t, T nt) { - return ifEmpty(t, () -> nt); - } - - /**t가 공백이 아닌지 확인하고 t를 반환한다.
- * 올바로 동작하려면 JVM을 시작할 때 -enableassertion:cokr.xit.Assert-ea:cokr.xit.Assert 옵션을 지정해야 한다.
- * t가 {@link #isEmpty(Object) 공백이면}, NullPointerException을 발생시킨다. - * @param t 오브젝트 - * @param name assertion 실패 시 오류 메시지에 사용할 이름 - * @param 객체 타입 - * @return t - */ - public static final T assertNotEmpty(T t, String name) { - try { - assert !isEmpty(t); - return t; - } catch (AssertionError e) { - throw new NullPointerException(name + " 값이 없습니다."); - } - } - - /**t가 공백이 아닌지 확인하고 t를 반환한다.
- * t가 {@link #isEmpty(Object) 공백이면}, NullPointerException을 발생시킨다. - * @param t 오브젝트 - * @param name assertion 실패 시 오류 메시지에 사용할 이름 - * @param 객체 타입 - * @return t - */ - public static final T notEmpty(T t, String name) { - if (!isEmpty(t)) return t; - throw new NullPointerException(name + " 값이 없습니다."); - } - - /**t의 최초원인이 되는 Throwable을 반환한다. - * @param t Throwable - * @return t의 최초원인이 되는 Throwable - */ - public static final Throwable rootCause(Throwable t) { - Throwable cause = t != null ? t.getCause() : null; - if (cause == null - && t instanceof InvocationTargetException) - cause = ((InvocationTargetException)t).getTargetException(); - return cause == null || cause == t ? t : rootCause(cause); - } - - /**t의 최초 원인이 된 예외를 RuntimeException에 담아 반환한다. - * @param t 예외 - * @return RuntimeException - */ - public static final RuntimeException runtimeException(Throwable t) { - t = rootCause(t); - return t instanceof RuntimeException ? RuntimeException.class.cast(t) : new RuntimeException(t); - } - - /**{@link Assert}에 대한 인터페이스. - *

이 인터페이스의 모든 메소드는 디폴트로 Assert 클래스의 메소드를 호출하도록 구현돼 있다.
- * Assert를 상속받지 않는 클래스에서 쉽게 Assert의 메소드를 사용하도록 하기 위한 것이다. - *

- */ - public static interface Support { - /** {@link Assert#isEmpty(Object)} 참고 */ - default boolean isEmpty(Object obj) { - return Assert.isEmpty(obj); - } - - /** {@link Assert#ifEmpty(Object, Supplier)} 참고 */ - default T ifEmpty(T t, Supplier nt) { - return Assert.ifEmpty(t, nt); - } - - /** {@link Assert#ifEmpty(Object, Object)} 참고 */ - default T ifEmpty(T t, T nt) { - return Assert.ifEmpty(t, nt); - } - - /** {@link Assert#assertNotEmpty(Object, String)} 참고 */ - default T assertNotEmpty(T t, String name) { - return Assert.assertNotEmpty(t, name); - } - - /** {@link Assert#notEmpty(Object, String)} 참고 */ - default T notEmpty(T t, String name) { - return Assert.notEmpty(t, name); - } - - /** {@link Assert#rootCause(Throwable)} 참고 */ - default Throwable rootCause(Throwable t) { - return Assert.rootCause(t); - } - - /** {@link Assert#runtimeException(Throwable)} 참고 */ - default RuntimeException runtimeException(Throwable t) { - return runtimeException(t); - } - } +package cokr.xit.foundation; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.util.function.Supplier; + +/**Assertion 유틸리티 + * */ +public class Assert { + private Assert() {} + + /**obj가 null인지, 공백문자인지, 빈 collection인지, 빈 배열인지 반환한다. + * @param obj 오브젝트 + * @return + *
  • obj가 null인지, 공백문자인지, 빈 collection인지, 빈 배열이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public static final boolean isEmpty(Object obj) { + if (obj == null) return true; + if (obj instanceof String) { + String str = (String)obj; + return str.trim().isEmpty(); + } + if (obj instanceof Iterable) { + Iterable objs = (Iterable)obj; + return !objs.iterator().hasNext(); + } + if (obj.getClass().isArray()) { + return Array.getLength(obj) < 1; + } + return false; + } + + /**t가 {@link #isEmpty(Object) 공백이면} nt가 반환하는 값을, 그렇지 않으면 t를 반환한다. + * @param 타입 + * @param t 오브젝트 + * @param nt 공백일 경우 nt를 반환할 Supplier + * @return + *
  • t가 공백이 아니면 t
  • + *
  • t가 공백이면 Supplier가 반환하는 값
  • + *
+ */ + public static final T ifEmpty(T t, Supplier nt) { + return !isEmpty(t) ? t : nt != null ? nt.get() : t; + } + + /**t가 {@link #isEmpty(Object) 공백이면} nt를, 그렇지 않으면 t를 반환한다. + * @param 타입 + * @param t 오브젝트 + * @param nt 공백일 경우 반환할 값 + * @return + *
  • t가 공백이 아니면 t
  • + *
  • t가 공백이면 nt
  • + *
+ */ + public static final T ifEmpty(T t, T nt) { + return ifEmpty(t, () -> nt); + } + + /**t가 공백이 아닌지 확인하고 t를 반환한다.
+ * 올바로 동작하려면 JVM을 시작할 때 -enableassertion:cokr.xit.Assert-ea:cokr.xit.Assert 옵션을 지정해야 한다.
+ * t가 {@link #isEmpty(Object) 공백이면}, NullPointerException을 발생시킨다. + * @param t 오브젝트 + * @param name assertion 실패 시 오류 메시지에 사용할 이름 + * @param 객체 타입 + * @return t + */ + public static final T assertNotEmpty(T t, String name) { + try { + assert !isEmpty(t); + return t; + } catch (AssertionError e) { + throw new NullPointerException(name + " 값이 없습니다."); + } + } + + /**t가 공백이 아닌지 확인하고 t를 반환한다.
+ * t가 {@link #isEmpty(Object) 공백이면}, NullPointerException을 발생시킨다. + * @param t 오브젝트 + * @param name assertion 실패 시 오류 메시지에 사용할 이름 + * @param 객체 타입 + * @return t + */ + public static final T notEmpty(T t, String name) { + if (!isEmpty(t)) return t; + throw new NullPointerException(name + " 값이 없습니다."); + } + + /**t의 최초원인이 되는 Throwable을 반환한다. + * @param t Throwable + * @return t의 최초원인이 되는 Throwable + */ + public static final Throwable rootCause(Throwable t) { + Throwable cause = t != null ? t.getCause() : null; + if (cause == null + && t instanceof InvocationTargetException) + cause = ((InvocationTargetException)t).getTargetException(); + return cause == null || cause == t ? t : rootCause(cause); + } + + /**t의 최초 원인이 된 예외를 RuntimeException에 담아 반환한다. + * @param t 예외 + * @return RuntimeException + */ + public static final RuntimeException runtimeException(Throwable t) { + t = rootCause(t); + return t instanceof RuntimeException ? RuntimeException.class.cast(t) : new RuntimeException(t); + } + + /**{@link Assert}에 대한 인터페이스. + *

이 인터페이스의 모든 메소드는 디폴트로 Assert 클래스의 메소드를 호출하도록 구현돼 있다.
+ * Assert를 상속받지 않는 클래스에서 쉽게 Assert의 메소드를 사용하도록 하기 위한 것이다. + *

+ */ + public static interface Support { + /** {@link Assert#isEmpty(Object)} 참고 */ + default boolean isEmpty(Object obj) { + return Assert.isEmpty(obj); + } + + /** {@link Assert#ifEmpty(Object, Supplier)} 참고 */ + default T ifEmpty(T t, Supplier nt) { + return Assert.ifEmpty(t, nt); + } + + /** {@link Assert#ifEmpty(Object, Object)} 참고 */ + default T ifEmpty(T t, T nt) { + return Assert.ifEmpty(t, nt); + } + + /** {@link Assert#assertNotEmpty(Object, String)} 참고 */ + default T assertNotEmpty(T t, String name) { + return Assert.assertNotEmpty(t, name); + } + + /** {@link Assert#notEmpty(Object, String)} 참고 */ + default T notEmpty(T t, String name) { + return Assert.notEmpty(t, name); + } + + /** {@link Assert#rootCause(Throwable)} 참고 */ + default Throwable rootCause(Throwable t) { + return Assert.rootCause(t); + } + + /** {@link Assert#runtimeException(Throwable)} 참고 */ + default RuntimeException runtimeException(Throwable t) { + return runtimeException(t); + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/Log.java b/src/main/java/cokr/xit/foundation/Log.java index 633ecf2..9e58755 100644 --- a/src/main/java/cokr/xit/foundation/Log.java +++ b/src/main/java/cokr/xit/foundation/Log.java @@ -1,95 +1,95 @@ -package cokr.xit.foundation; - -import java.util.HashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/**org.slf4j.Logger를 wrapping하여 로그 기능을 제공한다.
- * 로그를 활성화하려면 slf4j가 지원하는 로그 api를 정하여 해당 라이브러리를 추가하고 설정파일을 작성한다. - *

로그를 남길 문자열이 복잡할 경우 - *

  • 문자열의 '+' 연산을 하면 안된다.
  • - *
  • 대신 문자열 포맷에 '{}' 파라미터를 이용해 값을 제공한다.
  • - *
- *
 debug("디버그 내용");
- * debug("복잡한 디버그 내용 => {}, {}, {}", "a", "b", "c");
- */ -public class Log { - private static final HashMap, Log> byClass = new HashMap<>(); - private static final HashMap byName = new HashMap<>(); - - /**klass와 연계된 Log를 반환한다. - * @param klass 클래스 - * @return Log - */ - public static final Log get(Class klass) { - Log log = byClass.get(klass); - if (log == null) { - byClass.put(klass, log = new Log(LoggerFactory.getLogger(klass))); - } - return log; - } - - /**지정한 이름과 연계된 Log를 반환한다. - * @param logName 로그이름 - * @return Log - */ - public static final Log get(String logName) { - Log log = byName.get(logName); - if (log == null) { - byName.put(logName, log = new Log(LoggerFactory.getLogger(logName))); - } - return log; - } - - private Logger logger; - - private Log(Logger logger) { - this.logger = logger; - } - - /**로그 레벨이 INFO로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. - * @param format 메시지 포맷 - * @param args 메시지 인자 - */ - public void info(String format, Object... args) { - if (logger.isInfoEnabled()) - logger.info(format, args); - } - - /**로그 레벨이 WARN로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. - * @param format 메시지 포맷 - * @param args 메시지 인자 - */ - public void warn(String format, Object... args) { - if (logger.isWarnEnabled()) - logger.warn(format, args); - } - - /**로그 레벨이 DEBUG로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. - * @param format 메시지 포맷 - * @param args 메시지 인자 - */ - public void debug(String format, Object... args) { - if (logger.isDebugEnabled()) - logger.debug(format, args); - } - - /**로그 레벨이 TRACE로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. - * @param format 메시지 포맷 - * @param args 메시지 인자 - */ - public void trace(String format, Object... args) { - if (logger.isTraceEnabled()) - logger.trace(format, args); - } - - /**로그 레벨이 ERROR로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. - * @param format 메시지 포맷 - * @param args 메시지 인자 - */ - public void error(String format, Object... args) { - if (logger.isErrorEnabled()) - logger.error(format, args); - } +package cokr.xit.foundation; + +import java.util.HashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/**org.slf4j.Logger를 wrapping하여 로그 기능을 제공한다.
+ * 로그를 활성화하려면 slf4j가 지원하는 로그 api를 정하여 해당 라이브러리를 추가하고 설정파일을 작성한다. + *

로그를 남길 문자열이 복잡할 경우 + *

  • 문자열의 '+' 연산을 하면 안된다.
  • + *
  • 대신 문자열 포맷에 '{}' 파라미터를 이용해 값을 제공한다.
  • + *
+ *
 debug("디버그 내용");
+ * debug("복잡한 디버그 내용 => {}, {}, {}", "a", "b", "c");
+ */ +public class Log { + private static final HashMap, Log> byClass = new HashMap<>(); + private static final HashMap byName = new HashMap<>(); + + /**klass와 연계된 Log를 반환한다. + * @param klass 클래스 + * @return Log + */ + public static final Log get(Class klass) { + Log log = byClass.get(klass); + if (log == null) { + byClass.put(klass, log = new Log(LoggerFactory.getLogger(klass))); + } + return log; + } + + /**지정한 이름과 연계된 Log를 반환한다. + * @param logName 로그이름 + * @return Log + */ + public static final Log get(String logName) { + Log log = byName.get(logName); + if (log == null) { + byName.put(logName, log = new Log(LoggerFactory.getLogger(logName))); + } + return log; + } + + private Logger logger; + + private Log(Logger logger) { + this.logger = logger; + } + + /**로그 레벨이 INFO로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. + * @param format 메시지 포맷 + * @param args 메시지 인자 + */ + public void info(String format, Object... args) { + if (logger.isInfoEnabled()) + logger.info(format, args); + } + + /**로그 레벨이 WARN로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. + * @param format 메시지 포맷 + * @param args 메시지 인자 + */ + public void warn(String format, Object... args) { + if (logger.isWarnEnabled()) + logger.warn(format, args); + } + + /**로그 레벨이 DEBUG로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. + * @param format 메시지 포맷 + * @param args 메시지 인자 + */ + public void debug(String format, Object... args) { + if (logger.isDebugEnabled()) + logger.debug(format, args); + } + + /**로그 레벨이 TRACE로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. + * @param format 메시지 포맷 + * @param args 메시지 인자 + */ + public void trace(String format, Object... args) { + if (logger.isTraceEnabled()) + logger.trace(format, args); + } + + /**로그 레벨이 ERROR로 설정돼있으면 주어진 포맷과 인자로 메시지를 로그한다. + * @param format 메시지 포맷 + * @param args 메시지 인자 + */ + public void error(String format, Object... args) { + if (logger.isErrorEnabled()) + logger.error(format, args); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/User.java b/src/main/java/cokr/xit/foundation/User.java index 17048b7..84e1d52 100644 --- a/src/main/java/cokr/xit/foundation/User.java +++ b/src/main/java/cokr/xit/foundation/User.java @@ -1,89 +1,89 @@ -package cokr.xit.foundation; - -import java.io.Serializable; - -/**사용자 정보. - */ -public class User implements Serializable { - private static final long serialVersionUID = 1L; - private static final String UNKNOWN = "anonymous"; - public static final User unknown = new User().seal(); - - private String - id, - name, - password; - private boolean sealed; - - /**User가 인증된(로그인한) 사용자인지 반환한다. - * @return - *
  • 인증된 사용자이면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isAuthenticated() { - return !UNKNOWN.equals(getId()); - } - - /**사용자의 아이디를 반환한다. - * @return 사용자의 아이디 - */ - public String getId() { - return id != null ? id : UNKNOWN; - } - - /**사용자의 아이디를 설정한다. - * @param id 사용자의 아이디 - */ - public void setId(String id) { - notSealed().id = id; - } - - public String getUsername() { - return getId(); - } - - /**사용자의 이름을 반환한다. - * @return 사용자의 이름 - */ - public String getName() { - return isAuthenticated() ? name : getId(); - } - - /**사용자의 이름을 설정한다. - * @param name 사용자의 이름 - */ - public void setName(String name) { - notSealed().name = name; - } - - /**사용자의 비밀번호를 반환한다. - * @return 사용자의 비밀번호 - */ - public String getPassword() { - return password; - } - - /**사용자의 비밀번호를 설정한다. - * @param password 사용자의 비밀번호 - */ - public void setPassword(String password) { - notSealed().password = password; - } - - private User seal() { - sealed = true; - return this; - } - - private User notSealed() { - if (sealed) - throw new IllegalStateException(this + " is sealed"); - return this; - } - - @Override - public String toString() { - return String.format("%s('%s', '%s')", getClass().getSimpleName(), getId(), getName()); - } +package cokr.xit.foundation; + +import java.io.Serializable; + +/**사용자 정보. + */ +public class User implements Serializable { + private static final long serialVersionUID = 1L; + private static final String UNKNOWN = "anonymous"; + public static final User unknown = new User().seal(); + + private String + id, + name, + password; + private boolean sealed; + + /**User가 인증된(로그인한) 사용자인지 반환한다. + * @return + *
  • 인증된 사용자이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isAuthenticated() { + return !UNKNOWN.equals(getId()); + } + + /**사용자의 아이디를 반환한다. + * @return 사용자의 아이디 + */ + public String getId() { + return id != null ? id : UNKNOWN; + } + + /**사용자의 아이디를 설정한다. + * @param id 사용자의 아이디 + */ + public void setId(String id) { + notSealed().id = id; + } + + public String getUsername() { + return getId(); + } + + /**사용자의 이름을 반환한다. + * @return 사용자의 이름 + */ + public String getName() { + return isAuthenticated() ? name : getId(); + } + + /**사용자의 이름을 설정한다. + * @param name 사용자의 이름 + */ + public void setName(String name) { + notSealed().name = name; + } + + /**사용자의 비밀번호를 반환한다. + * @return 사용자의 비밀번호 + */ + public String getPassword() { + return password; + } + + /**사용자의 비밀번호를 설정한다. + * @param password 사용자의 비밀번호 + */ + public void setPassword(String password) { + notSealed().password = password; + } + + private User seal() { + sealed = true; + return this; + } + + private User notSealed() { + if (sealed) + throw new IllegalStateException(this + " is sealed"); + return this; + } + + @Override + public String toString() { + return String.format("%s('%s', '%s')", getClass().getSimpleName(), getId(), getName()); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/UserInfo.java b/src/main/java/cokr/xit/foundation/UserInfo.java index 143d645..8d8f417 100644 --- a/src/main/java/cokr/xit/foundation/UserInfo.java +++ b/src/main/java/cokr/xit/foundation/UserInfo.java @@ -1,201 +1,201 @@ -package cokr.xit.foundation; - -import java.io.Serializable; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.stereotype.Component; - -import cokr.xit.foundation.data.DataObject; - -/**현재 접속한 사용자의 상세정보를 갖는 객체. - */ -public class UserInfo implements Serializable { - private static final long serialVersionUID = 1L; - public static final String - NAME = "userInfoProvider", - SEC_NAME = "securedUserInfoProvider"; - public static final List ALL_ACTIONS = Stream.of("all-actions").collect(Collectors.toList()); - - /**현재 접속한 사용자의 상세정보를 찾는 객체. - * @author mjkhan - */ - @Component(NAME) - public static class Provider { - private static Provider obj; - - /**현재 접속한 사용자의 상세정보를 찾는 Provider를 반환한다. - * @return 현재 접속한 사용자의 상세정보를 찾는 Provider - */ - public static Provider get() { - if (obj == null) { - ApplicationContainer container = ApplicationContainer.get(); - String name = container.isSecured() ? SEC_NAME : NAME; - obj = Provider.class.cast(container.getApplicationContext().getBean(name)); - } - return obj; - } - - /** 사용자 상세정보 */ - protected UserInfo userInfo; - - /**현재 접속한 사용자의 상세정보를 반환한다. - * @return 현재 접속한 사용자의 상세정보 - */ - public UserInfo currentUser() { - return Assert.ifEmpty(userInfo, () -> userInfo = new UserInfo()); - } - - /**현재 접속한 사용자의 상세정보를 설정한다. - * @param userInfo 현재 접속한 사용자의 상세정보 - */ - public void setUserInfo(UserInfo userInfo) { - this.userInfo = userInfo; - } - } - - /**현재 접속한 사용자의 상세정보를 반환한다. - * @param 사용자 상세정보 유형 - * @return 사용자 상세정보 - */ - @SuppressWarnings("unchecked") - public static T current() { - return (T)Provider.get().currentUser(); - } - - private User user; - private DataObject info; - - /**현재 접속한 사용자를 반환한다. - * @return 현재 접속한 사용자 - */ - public User getUser() { - return user != null ? user : User.unknown; - } - - /**현재 접속한 사용자를 설정한다. - * @param user 현재 접속한 사용자 - */ - public void setUser(User user) { - this.user = user; - } - - /**사용자가 인증된(로그인한) 사용자인지 반환한다. - * @return - *
  • 인증된 사용자이면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isAuthenticated() { - return getUser().isAuthenticated(); - } - - /**사용자의 아이디를 반환한다. - * @return 사용자의 아이디 - */ - public String getId() { - return getUser().getId(); - } - - /**사용자의 아이디를 설정한다. - * @param id 사용자의 아이디 - */ - public void setId(String id) { - getUser().setId(id); - } - - public String getUsername() { - return getUser().getUsername(); - } - - /**사용자의 이름을 반환한다. - * @return 사용자의 이름 - */ - public String getName() { - return getUser().getName(); - } - - /**사용자의 이름을 설정한다. - * @param name 사용자의 이름 - */ - public void setName(String name) { - getUser().setName(name); - } - - /**사용자의 비밀번호를 반환한다. - * @return 사용자의 비밀번호 - */ - public String getPassword() { - return getUser().getPassword(); - } - - /**사용자의 비밀번호를 설정한다. - * @param password 사용자의 비밀번호 - */ - public void setPassword(String password) { - getUser().setPassword(password); - } - - /**사용자의 추가정보를 갖는 map을 반환한다. - * @return 사용자의 추가정보를 갖는 map - */ - public Map getInfo() { - return info != null ? info : Collections.emptyMap(); - } - - /**사용자의 추가정보 중 key에 해당하는 값을 반환한다. - * @param key 추가정보 키 - * @return 추가정보 - */ - @SuppressWarnings("unchecked") - public T info(String key) { - return (T)getInfo().get(key); - } - - /**사용자의 추가정보를 설정한다. - * @param key 추가정보 키 - * @param value 추가정보 - * @return User - */ - public T setInfo(String key, Object value) { - if (info == null) - info = new DataObject(); - info.put(key, value); - return self(); - } - - /**map이 포함하는 정보를 사용자의 추가정보로 설정한다. - * 설정되는 추가정보의 키는 map의 키와 동일하다. - * @param map 추가정보를 갖는 map - * @return User - */ - public T setInfo(Map map) { - if (map != null) - map.forEach(this::setInfo); - return self(); - } - - /**현재 사용자가 접근할 수 있는 액션 url을 반환한다. - * @return 현재 사용자가 접근할 수 있는 액션 url - */ - public List getAccessibleActions() { - return ALL_ACTIONS; - } - - /**현재 객체를 반환한다. - * @param 현재 객체 유형 - * @return 현재 객체 - */ - @SuppressWarnings("unchecked") - protected T self() { - return (T)this; - } - - @Override - public String toString() { - return String.format("%s('%s', '%s')", getClass().getSimpleName(), getId(), getName()); - } +package cokr.xit.foundation; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.stereotype.Component; + +import cokr.xit.foundation.data.DataObject; + +/**현재 접속한 사용자의 상세정보를 갖는 객체. + */ +public class UserInfo implements Serializable { + private static final long serialVersionUID = 1L; + public static final String + NAME = "userInfoProvider", + SEC_NAME = "securedUserInfoProvider"; + public static final List ALL_ACTIONS = Stream.of("all-actions").collect(Collectors.toList()); + + /**현재 접속한 사용자의 상세정보를 찾는 객체. + * @author mjkhan + */ + @Component(NAME) + public static class Provider { + private static Provider obj; + + /**현재 접속한 사용자의 상세정보를 찾는 Provider를 반환한다. + * @return 현재 접속한 사용자의 상세정보를 찾는 Provider + */ + public static Provider get() { + if (obj == null) { + ApplicationContainer container = ApplicationContainer.get(); + String name = container.isSecured() ? SEC_NAME : NAME; + obj = Provider.class.cast(container.getApplicationContext().getBean(name)); + } + return obj; + } + + /** 사용자 상세정보 */ + protected UserInfo userInfo; + + /**현재 접속한 사용자의 상세정보를 반환한다. + * @return 현재 접속한 사용자의 상세정보 + */ + public UserInfo currentUser() { + return Assert.ifEmpty(userInfo, () -> userInfo = new UserInfo()); + } + + /**현재 접속한 사용자의 상세정보를 설정한다. + * @param userInfo 현재 접속한 사용자의 상세정보 + */ + public void setUserInfo(UserInfo userInfo) { + this.userInfo = userInfo; + } + } + + /**현재 접속한 사용자의 상세정보를 반환한다. + * @param 사용자 상세정보 유형 + * @return 사용자 상세정보 + */ + @SuppressWarnings("unchecked") + public static T current() { + return (T)Provider.get().currentUser(); + } + + private User user; + private DataObject info; + + /**현재 접속한 사용자를 반환한다. + * @return 현재 접속한 사용자 + */ + public User getUser() { + return user != null ? user : User.unknown; + } + + /**현재 접속한 사용자를 설정한다. + * @param user 현재 접속한 사용자 + */ + public void setUser(User user) { + this.user = user; + } + + /**사용자가 인증된(로그인한) 사용자인지 반환한다. + * @return + *
  • 인증된 사용자이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isAuthenticated() { + return getUser().isAuthenticated(); + } + + /**사용자의 아이디를 반환한다. + * @return 사용자의 아이디 + */ + public String getId() { + return getUser().getId(); + } + + /**사용자의 아이디를 설정한다. + * @param id 사용자의 아이디 + */ + public void setId(String id) { + getUser().setId(id); + } + + public String getUsername() { + return getUser().getUsername(); + } + + /**사용자의 이름을 반환한다. + * @return 사용자의 이름 + */ + public String getName() { + return getUser().getName(); + } + + /**사용자의 이름을 설정한다. + * @param name 사용자의 이름 + */ + public void setName(String name) { + getUser().setName(name); + } + + /**사용자의 비밀번호를 반환한다. + * @return 사용자의 비밀번호 + */ + public String getPassword() { + return getUser().getPassword(); + } + + /**사용자의 비밀번호를 설정한다. + * @param password 사용자의 비밀번호 + */ + public void setPassword(String password) { + getUser().setPassword(password); + } + + /**사용자의 추가정보를 갖는 map을 반환한다. + * @return 사용자의 추가정보를 갖는 map + */ + public Map getInfo() { + return info != null ? info : Collections.emptyMap(); + } + + /**사용자의 추가정보 중 key에 해당하는 값을 반환한다. + * @param key 추가정보 키 + * @return 추가정보 + */ + @SuppressWarnings("unchecked") + public T info(String key) { + return (T)getInfo().get(key); + } + + /**사용자의 추가정보를 설정한다. + * @param key 추가정보 키 + * @param value 추가정보 + * @return User + */ + public T setInfo(String key, Object value) { + if (info == null) + info = new DataObject(); + info.put(key, value); + return self(); + } + + /**map이 포함하는 정보를 사용자의 추가정보로 설정한다. + * 설정되는 추가정보의 키는 map의 키와 동일하다. + * @param map 추가정보를 갖는 map + * @return User + */ + public T setInfo(Map map) { + if (map != null) + map.forEach(this::setInfo); + return self(); + } + + /**현재 사용자가 접근할 수 있는 액션 url을 반환한다. + * @return 현재 사용자가 접근할 수 있는 액션 url + */ + public List getAccessibleActions() { + return ALL_ACTIONS; + } + + /**현재 객체를 반환한다. + * @param 현재 객체 유형 + * @return 현재 객체 + */ + @SuppressWarnings("unchecked") + protected T self() { + return (T)this; + } + + @Override + public String toString() { + return String.format("%s('%s', '%s')", getClass().getSimpleName(), getId(), getName()); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/AbstractDao.java b/src/main/java/cokr/xit/foundation/component/AbstractDao.java index bd059da..59bab3e 100644 --- a/src/main/java/cokr/xit/foundation/component/AbstractDao.java +++ b/src/main/java/cokr/xit/foundation/component/AbstractDao.java @@ -1,181 +1,181 @@ -package cokr.xit.foundation.component; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.annotation.Resource; - -import org.egovframe.rte.fdl.property.EgovPropertyService; -import org.egovframe.rte.psl.dataaccess.EgovAbstractMapper; -import org.springframework.context.MessageSource; - -import cokr.xit.foundation.Access; -import cokr.xit.foundation.ApplicationException; -import cokr.xit.foundation.Assert; -import cokr.xit.foundation.Log; -import cokr.xit.foundation.UserInfo; -import cokr.xit.foundation.data.DataObject; - -/**DAO(Data Access Object)의 베이스 클래스. - * @author mjkhan - */ -public abstract class AbstractDao extends EgovAbstractMapper { - /** 프로퍼티 서비스 */ - @Resource(name="propertyService") - protected EgovPropertyService properties; - /** 메시지 소스 */ - @Resource(name="messageSource") - protected MessageSource messages; - /** 로그이름 */ - private String logName; - /** SQL mapper 네임스페이스 */ - private String namespace; - - /**로그이름을 설정한다. - * @param logName 로그이름 - */ - public void setLogName(String logName) { - this.logName = logName; - } - - /**DAO가 사용하는 SQL문의 namespace를 반환한한다. - * @return DAO가 사용하는 SQL문의 namespace - */ - protected String namespace() { - return namespace; - } - - /**DAO가 사용하는 SQL문의 namespace를 설정한다. - * @param namespace DAO가 사용하는 SQL문의 namespace - */ - protected void setNamespace(String namespace) { - this.namespace = namespace; - } - - /**DAO가 사용하는 SQL문의 ID를 {@link #namespace()} + '.' + sqlID로 조합하여 반환한다. - * @param sqlID DAO가 사용하는 SQL문의 ID. '.'는 제외 - * @return {@link #namespace()} + '.' + sqlID - * @see #setNamespace(String) - */ - protected String sqlID(String sqlID) { - if (isEmpty(namespace)) - return sqlID; - - notEmpty(sqlID, "sqlID"); - if (namespace.endsWith(".") || sqlID.startsWith(".")) - return namespace + sqlID; - if (sqlID.indexOf(".") > 0) - return sqlID; - return namespace + "." + sqlID; - } - - /**현재 접근한 사용자를 반환한다. - * @param 사용자 정보 유형 - * @return 현재 접근한 사용자 - */ - protected T currentUser() { - return UserInfo.current(); - } - - /**현재 사용자의 접근정보를 반환한다. - * @return 현재 사용자의 접근정보 - */ - protected static Access currentAccess() { - return Access.current(); - } - - /**메시지 리소스 파일에서 key로 정의된 문자열과 args로 메시지를 만들어 반환한다. - * @param key 메시지 리소스 파일의 문자열 key - * @param args 해당 문자열에 전달할 인자 - * @return 메시지 - */ - protected String message(String key, String... args) { - return messages.getMessage(key, ifEmpty(args, () -> null), currentAccess().getLocale()); - } - - /**{@link Assert#isEmpty(Object)} 참고 - */ - protected static boolean isEmpty(Object obj) { - return Assert.isEmpty(obj); - } - - /**{@link Assert#ifEmpty(Object, Supplier)} 참고 - */ - protected static T ifEmpty(T t, Supplier nt) { - return Assert.ifEmpty(t, nt); - } - - /**obj가 빈 값이면 공백문자("")를 반환한다. 빈 값이 아니면 obj를 String으로 캐스팅하여 반환한다. - * @param obj 객체 - * @return - *
  • obj가 빈 값이면 공백문자("")
  • - *
  • 빈 값이 아니면 String으로 캐스팅한 obj
  • - *
- */ - protected static final String blankIfEmpty(Object obj) { - return ifEmpty((String)obj, () -> ""); - } - - /**{@link Assert#notEmpty(Object, String)} 참고 - */ - protected static T notEmpty(T t, String s) { - return Assert.notEmpty(t, s); - } - - /**{@link ApplicationException#get(Throwable)} 참고 - */ - protected static final ApplicationException applicationException(Throwable t) { - return ApplicationException.get(t); - } - - protected static >> T underscoredToCamelCase(T list) { - if (list.isEmpty()) - return list; - - Set> keymap = list.get(0).keySet().stream().collect( - Collectors.toMap(k -> k, v -> camelCase(v), (k1, k2) -> k1, LinkedHashMap::new) - ).entrySet(); - - list.forEach(row -> - keymap.forEach(entry -> row.put(entry.getValue(), row.remove(entry.getKey()))) - ); - return list; - } - - private static String camelCase(String str) { - StringBuilder buf = new StringBuilder(); - for (String token: str.split("_")) { - if (buf.length() < 1) - buf.append(token.toLowerCase()); - else - buf.append(token.substring(0, 1).toUpperCase() + token.substring(1).toLowerCase()); - } - return buf.toString(); - } - - /**파라미터를 담을 DataObject를 생성하여 반환한다. - * @return DataObject - */ - protected DataObject params() { - return new DataObject(); - } - - /**klass와 연계된 Log를 반환한다. - * @param klass - * @return Log - */ - protected static Log log(Class klass) { - return Log.get(klass); - } - - /**현재 클래스와 연계된 Log를 반환한다. - * @return Log - */ - protected Log log() { - return isEmpty(logName) ? log(getClass()) : Log.get(logName); - } +package cokr.xit.foundation.component; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.annotation.Resource; + +import org.egovframe.rte.fdl.property.EgovPropertyService; +import org.egovframe.rte.psl.dataaccess.EgovAbstractMapper; +import org.springframework.context.MessageSource; + +import cokr.xit.foundation.Access; +import cokr.xit.foundation.ApplicationException; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.Log; +import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.data.DataObject; + +/**DAO(Data Access Object)의 베이스 클래스. + * @author mjkhan + */ +public abstract class AbstractDao extends EgovAbstractMapper { + /** 프로퍼티 서비스 */ + @Resource(name="propertyService") + protected EgovPropertyService properties; + /** 메시지 소스 */ + @Resource(name="messageSource") + protected MessageSource messages; + /** 로그이름 */ + private String logName; + /** SQL mapper 네임스페이스 */ + private String namespace; + + /**로그이름을 설정한다. + * @param logName 로그이름 + */ + public void setLogName(String logName) { + this.logName = logName; + } + + /**DAO가 사용하는 SQL문의 namespace를 반환한한다. + * @return DAO가 사용하는 SQL문의 namespace + */ + protected String namespace() { + return namespace; + } + + /**DAO가 사용하는 SQL문의 namespace를 설정한다. + * @param namespace DAO가 사용하는 SQL문의 namespace + */ + protected void setNamespace(String namespace) { + this.namespace = namespace; + } + + /**DAO가 사용하는 SQL문의 ID를 {@link #namespace()} + '.' + sqlID로 조합하여 반환한다. + * @param sqlID DAO가 사용하는 SQL문의 ID. '.'는 제외 + * @return {@link #namespace()} + '.' + sqlID + * @see #setNamespace(String) + */ + protected String sqlID(String sqlID) { + if (isEmpty(namespace)) + return sqlID; + + notEmpty(sqlID, "sqlID"); + if (namespace.endsWith(".") || sqlID.startsWith(".")) + return namespace + sqlID; + if (sqlID.indexOf(".") > 0) + return sqlID; + return namespace + "." + sqlID; + } + + /**현재 접근한 사용자를 반환한다. + * @param 사용자 정보 유형 + * @return 현재 접근한 사용자 + */ + protected T currentUser() { + return UserInfo.current(); + } + + /**현재 사용자의 접근정보를 반환한다. + * @return 현재 사용자의 접근정보 + */ + protected static Access currentAccess() { + return Access.current(); + } + + /**메시지 리소스 파일에서 key로 정의된 문자열과 args로 메시지를 만들어 반환한다. + * @param key 메시지 리소스 파일의 문자열 key + * @param args 해당 문자열에 전달할 인자 + * @return 메시지 + */ + protected String message(String key, String... args) { + return messages.getMessage(key, ifEmpty(args, () -> null), currentAccess().getLocale()); + } + + /**{@link Assert#isEmpty(Object)} 참고 + */ + protected static boolean isEmpty(Object obj) { + return Assert.isEmpty(obj); + } + + /**{@link Assert#ifEmpty(Object, Supplier)} 참고 + */ + protected static T ifEmpty(T t, Supplier nt) { + return Assert.ifEmpty(t, nt); + } + + /**obj가 빈 값이면 공백문자("")를 반환한다. 빈 값이 아니면 obj를 String으로 캐스팅하여 반환한다. + * @param obj 객체 + * @return + *
  • obj가 빈 값이면 공백문자("")
  • + *
  • 빈 값이 아니면 String으로 캐스팅한 obj
  • + *
+ */ + protected static final String blankIfEmpty(Object obj) { + return ifEmpty((String)obj, () -> ""); + } + + /**{@link Assert#notEmpty(Object, String)} 참고 + */ + protected static T notEmpty(T t, String s) { + return Assert.notEmpty(t, s); + } + + /**{@link ApplicationException#get(Throwable)} 참고 + */ + protected static final ApplicationException applicationException(Throwable t) { + return ApplicationException.get(t); + } + + protected static >> T underscoredToCamelCase(T list) { + if (list.isEmpty()) + return list; + + Set> keymap = list.get(0).keySet().stream().collect( + Collectors.toMap(k -> k, v -> camelCase(v), (k1, k2) -> k1, LinkedHashMap::new) + ).entrySet(); + + list.forEach(row -> + keymap.forEach(entry -> row.put(entry.getValue(), row.remove(entry.getKey()))) + ); + return list; + } + + private static String camelCase(String str) { + StringBuilder buf = new StringBuilder(); + for (String token: str.split("_")) { + if (buf.length() < 1) + buf.append(token.toLowerCase()); + else + buf.append(token.substring(0, 1).toUpperCase() + token.substring(1).toLowerCase()); + } + return buf.toString(); + } + + /**파라미터를 담을 DataObject를 생성하여 반환한다. + * @return DataObject + */ + protected DataObject params() { + return new DataObject(); + } + + /**klass와 연계된 Log를 반환한다. + * @param klass + * @return Log + */ + protected static Log log(Class klass) { + return Log.get(klass); + } + + /**현재 클래스와 연계된 Log를 반환한다. + * @return Log + */ + protected Log log() { + return isEmpty(logName) ? log(getClass()) : Log.get(logName); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/AbstractMapper.java b/src/main/java/cokr/xit/foundation/component/AbstractMapper.java index 8bff725..fd899e8 100644 --- a/src/main/java/cokr/xit/foundation/component/AbstractMapper.java +++ b/src/main/java/cokr/xit/foundation/component/AbstractMapper.java @@ -1,101 +1,101 @@ -package cokr.xit.foundation.component; - -import cokr.xit.foundation.UserInfo; -import cokr.xit.foundation.data.DataObject; - -/**매퍼 인터페이스의 베이스 인터페이스.
- * 애플리케이션의 매퍼 인터페이스는 이 인터페이스를 상속받아 정의하고,
- * 이름은 (업무객체 이름 || 테이블 이름) + 'Mapper'로 한다. - * {@code @Mapper} 어노테이션을 적용하고 카멜 표기법으로 이름을 설정한다. 다음은 그 예다. - *
 package cokr.xit.example.business.dao;
- * import org.egovframe.rte.psl.dataaccess.mapper.Mapper;
- * {@code @Mapper("tableBMapper")}
- * public interface TableBMapper extends AbstractMapper {
- *    ...
- * }
- *

매퍼 파일의 작성
- * 매퍼 파일은 매퍼 인터페이스가 실행하는 SQL문을 저장한다. - *

  • 매퍼 파일의 이름은 매퍼 인터페이스의 이름을 카멜 표기법으로 표시하고, ‘Mapper’를 ‘-mapper‘로 분리한다.
    - * 예) TableBMapper interface의 매퍼 파일 -> tableB-mapper.xml
  • - *
  • 매퍼 파일의 네임스페이스는 매퍼 인터페이스의 전체 이름(fully-qualified name)으로 한다
  • - *
  • parameterType, resultType은 애플리케이션에서 정의한 클래스는 해당 클래스의 전체 이름을,
    - * 이미 정의된 클래스는 해당 클래스의 alias로 정의한다
  • - *
  • SQL문은 모두 대문자로 한다
  • - *
  • SQL문의 첫 문장은 해당 문장을 설명하는 다음과 같은 주석으로 한다.
    - *
    {@code  매퍼이름.매퍼메소드(설명)
    - *          예) tableBMapper.selectTableBList(업무 엔티티 조회)
    - *         }
    - *
  • - *
- *

SELECT
- *

  • 조회 컬럼 목록은 ‘*’를 지양하고, 구체적인 컬럼 이름을 세로로 작성한다.
  • - *
  • 테이블 목록은 세로로 표시한다.
  • - *
  • INNER JOIN은 ‘,’로 표시한다.
  • - *
  • SELECT문의 각 부분은 SELECT 키워드를 기준으로 다음과 같이 정렬한다.
  • - *
- *
{@code }
- *

INSERT
- * INSERT문은 INSERT 키워드를 기준으로 다음과 같이 정렬한다 - *

{@code 
- * INSERT INTO TBL_TABLE_B (
- *        ID
- *      , NAME
- *      , PHONE_NO
- *      , ...
- * ) VALUES (
- *        #{id}
- *      , #{name}
- *      , #{phoneNo}
- *      , ...
- * )
- * }
- *

UPDATE
- * UPDATE문은 UPDATE 키워드를 기준으로 다음과 같이 정렬한다 - *

{@code 
- * UPDATE TBL_TABLE_B
- *    SET NAME = #{name}
- *      , PHONE_NO = #{phoneNo}
- *      , ...
- *  WHERE ID = #{id}
- * }
- *

DELETE
- * DELETE문은 DELETE 키워드를 기준으로 다음과 같이 정렬한다 - *

{@code 
- * DELETE FROM TBL_TABLE_B
- *  WHERE ID = #{id}
- * }
- * @author mjkhan - */ -public interface AbstractMapper extends - cokr.xit.foundation.Assert.Support, - cokr.xit.foundation.data.Convert.Support { - - /**현재 접근한 사용자의 상세정보를 반환한다. - * @return 현재 접근한 사용자의 상세정보 - */ - default UserInfo currentUser() { - return UserInfo.current(); - } - - /**파라미터를 담을 DataObject을 반환한다. - * 키 "currentUser"에 현재 접근한 사용자의 정보를 설정한다. - * @return 파라미터를 담을 DataObject - */ - default DataObject params() { - return new DataObject() - .set("currentUser", currentUser()); - } +package cokr.xit.foundation.component; + +import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.data.DataObject; + +/**매퍼 인터페이스의 베이스 인터페이스.
+ * 애플리케이션의 매퍼 인터페이스는 이 인터페이스를 상속받아 정의하고,
+ * 이름은 (업무객체 이름 || 테이블 이름) + 'Mapper'로 한다. + * {@code @Mapper} 어노테이션을 적용하고 카멜 표기법으로 이름을 설정한다. 다음은 그 예다. + *
 package cokr.xit.example.business.dao;
+ * import org.egovframe.rte.psl.dataaccess.mapper.Mapper;
+ * {@code @Mapper("tableBMapper")}
+ * public interface TableBMapper extends AbstractMapper {
+ *    ...
+ * }
+ *

매퍼 파일의 작성
+ * 매퍼 파일은 매퍼 인터페이스가 실행하는 SQL문을 저장한다. + *

  • 매퍼 파일의 이름은 매퍼 인터페이스의 이름을 카멜 표기법으로 표시하고, ‘Mapper’를 ‘-mapper‘로 분리한다.
    + * 예) TableBMapper interface의 매퍼 파일 -> tableB-mapper.xml
  • + *
  • 매퍼 파일의 네임스페이스는 매퍼 인터페이스의 전체 이름(fully-qualified name)으로 한다
  • + *
  • parameterType, resultType은 애플리케이션에서 정의한 클래스는 해당 클래스의 전체 이름을,
    + * 이미 정의된 클래스는 해당 클래스의 alias로 정의한다
  • + *
  • SQL문은 모두 대문자로 한다
  • + *
  • SQL문의 첫 문장은 해당 문장을 설명하는 다음과 같은 주석으로 한다.
    + *
    {@code  매퍼이름.매퍼메소드(설명)
    + *          예) tableBMapper.selectTableBList(업무 엔티티 조회)
    + *         }
    + *
  • + *
+ *

SELECT
+ *

  • 조회 컬럼 목록은 ‘*’를 지양하고, 구체적인 컬럼 이름을 세로로 작성한다.
  • + *
  • 테이블 목록은 세로로 표시한다.
  • + *
  • INNER JOIN은 ‘,’로 표시한다.
  • + *
  • SELECT문의 각 부분은 SELECT 키워드를 기준으로 다음과 같이 정렬한다.
  • + *
+ *
{@code }
+ *

INSERT
+ * INSERT문은 INSERT 키워드를 기준으로 다음과 같이 정렬한다 + *

{@code 
+ * INSERT INTO TBL_TABLE_B (
+ *        ID
+ *      , NAME
+ *      , PHONE_NO
+ *      , ...
+ * ) VALUES (
+ *        #{id}
+ *      , #{name}
+ *      , #{phoneNo}
+ *      , ...
+ * )
+ * }
+ *

UPDATE
+ * UPDATE문은 UPDATE 키워드를 기준으로 다음과 같이 정렬한다 + *

{@code 
+ * UPDATE TBL_TABLE_B
+ *    SET NAME = #{name}
+ *      , PHONE_NO = #{phoneNo}
+ *      , ...
+ *  WHERE ID = #{id}
+ * }
+ *

DELETE
+ * DELETE문은 DELETE 키워드를 기준으로 다음과 같이 정렬한다 + *

{@code 
+ * DELETE FROM TBL_TABLE_B
+ *  WHERE ID = #{id}
+ * }
+ * @author mjkhan + */ +public interface AbstractMapper extends + cokr.xit.foundation.Assert.Support, + cokr.xit.foundation.data.Convert.Support { + + /**현재 접근한 사용자의 상세정보를 반환한다. + * @return 현재 접근한 사용자의 상세정보 + */ + default UserInfo currentUser() { + return UserInfo.current(); + } + + /**파라미터를 담을 DataObject을 반환한다. + * 키 "currentUser"에 현재 접근한 사용자의 정보를 설정한다. + * @return 파라미터를 담을 DataObject + */ + default DataObject params() { + return new DataObject() + .set("currentUser", currentUser()); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/AbstractServiceBean.java b/src/main/java/cokr/xit/foundation/component/AbstractServiceBean.java index 69f4b1d..5e9b5d3 100644 --- a/src/main/java/cokr/xit/foundation/component/AbstractServiceBean.java +++ b/src/main/java/cokr/xit/foundation/component/AbstractServiceBean.java @@ -1,157 +1,157 @@ -package cokr.xit.foundation.component; - -import java.util.function.Supplier; - -import javax.annotation.Resource; - -import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; -import org.egovframe.rte.fdl.property.EgovPropertyService; -import org.springframework.context.MessageSource; - -import cokr.xit.foundation.Access; -import cokr.xit.foundation.ApplicationException; -import cokr.xit.foundation.Assert; -import cokr.xit.foundation.Log; -import cokr.xit.foundation.UserInfo; - -/**서비스 인터페이스 구현체의 베이스 클래스 - *

업무 서비스는 인터페이스로 클라이언트에 노출된다.
- * 클라이언트가 업무 인터페이스에 서비스를 요청하면, 서비스의 실행은 서비스 구현체에게 위임된다. - *

애플리케이션의 서비스 구현체는 이 클래스를 상속받아 정의하고, 이름은 '서비스 인터페이스' + 'ServiceBean'으로 한다.
- * 그리고 @Service 어노테이션을 명시하고, 카멜 표기법으로 표시한 업무 인터페이스의 이름을 지정한다. - * 다음은 그 예이다. - *

 package cokr.xit.example.business.service.bean;
- * ...
- * {@code @Service("businessService")}
- * public class BusinessServiceBean extends AbstractServiceBean implements BusinessService {
- *     ...
- * }
- * 정의한 서비스 구현체는 다음과 같이 @Resource 어노테이션으로 이름을 명시해 주입한다. - *
 @Resource(name = "businessService")
- * BusinessService businessService;
- * @author mjkhan - */ -public abstract class AbstractServiceBean extends EgovAbstractServiceImpl { - /** 프로퍼티 서비스 */ - @Resource(name="propertyService") - protected EgovPropertyService properties; - /** 메시지 소스 */ - @Resource(name="messageSource") - protected MessageSource messages; - /** 로그이름 */ - private String logName; - - /**로그이름을 설정한다. - * @param logName 로그이름 - */ - public void setLogName(String logName) { - this.logName = logName; - } - - /**현재 접근한 사용자를 반환한다. - * @return 현재 접근한 사용자 - */ - protected T currentUser() { - return UserInfo.current(); - } - - /**현재 사용자의 접근정보를 반환한다. - * @return 현재 사용자의 접근정보 - */ - protected static Access currentAccess() { - return Access.current(); - } - - /**메시지 리소스 파일에서 key로 정의된 문자열과 args로 메시지를 만들어 반환한다. - * @param key 메시지 리소스 파일의 문자열 key - * @param args 해당 문자열에 전달할 인자 - * @return 메시지 - */ - protected String message(String key, String... args) { - return messages.getMessage(key, ifEmpty(args, () -> null), currentAccess().getLocale()); - } - - /**lv와 rv가 같은 지 반환한다. - * @param lv 좌측 값 - * @param rv 우측 값 - * @return - *
  • lv와 rv가 같으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - protected static final boolean equals(Object lv, Object rv) { - return lv == rv ? true : lv != null && lv.equals(rv); - } - - /**{@link Assert#isEmpty(Object)} 참고 - */ - protected static final boolean isEmpty(Object obj) { - return Assert.isEmpty(obj); - } - - /**{@link Assert#ifEmpty(Object, Supplier)} 참고*/ - protected static final T ifEmpty(T t, Supplier nt) { - return Assert.ifEmpty(t, nt); - } - - /**{@link Assert#ifEmpty(Object, Object)} 참고. - */ - protected static final T ifEmpty(T t, T nt) { - return Assert.ifEmpty(t, nt); - } - - /**obj가 빈 값이면 공백문자("")를 반환한다. 빈 값이 아니면 obj를 String으로 캐스팅하여 반환한다. - * @param obj - * @return - *
  • obj가 빈 값이면 공백문자("")
  • - *
  • 빈 값이 아니면 String으로 캐스팅한 obj
  • - *
- */ - protected static final String blankIfEmpty(Object obj) { - return ifEmpty((String)obj, () -> ""); - } - - /**{@link Assert#notEmpty(Object, String)} 참고. - */ - protected static final T notEmpty(T t, String name) { - return Assert.notEmpty(t, name); - } - - /**t가 공백이 아닌지 확인하고 t를 반환한다. - * t가 공백이면, NullPointerException을 발생시키며, 메시지는 message-common 프로퍼티 파일의 'valueRequired'키에 설정된 값으로 만든다. - * @param t 값이나 객체 - * @param name assertion 실패 시 오류 메시지에 사용할 이름 - * @return t - */ - protected final T required(T t, String name) { - if (!isEmpty(t)) return t; - throw new NullPointerException(message("valueRequired", name)); - } - - /**{@link Assert#rootCause(Throwable)} 참고 - */ - protected static final Throwable rootCause(Throwable t) { - return Assert.rootCause(t); - } - - /**{@link ApplicationException#get(Throwable)} 참고 - */ - protected static final ApplicationException applicationException(Throwable t) { - return ApplicationException.get(t); - } - - /**klass와 연계된 Log를 반환한다. - * @param klass - * @return Log - */ - protected static Log log(Class klass) { - return Log.get(klass); - } - - /**현재 클래스와 연계된 Log를 반환한다. - * @return Log - */ - protected Log log() { - return isEmpty(logName) ? log(getClass()) : Log.get(logName); - } +package cokr.xit.foundation.component; + +import java.util.function.Supplier; + +import javax.annotation.Resource; + +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.egovframe.rte.fdl.property.EgovPropertyService; +import org.springframework.context.MessageSource; + +import cokr.xit.foundation.Access; +import cokr.xit.foundation.ApplicationException; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.Log; +import cokr.xit.foundation.UserInfo; + +/**서비스 인터페이스 구현체의 베이스 클래스 + *

업무 서비스는 인터페이스로 클라이언트에 노출된다.
+ * 클라이언트가 업무 인터페이스에 서비스를 요청하면, 서비스의 실행은 서비스 구현체에게 위임된다. + *

애플리케이션의 서비스 구현체는 이 클래스를 상속받아 정의하고, 이름은 '서비스 인터페이스' + 'ServiceBean'으로 한다.
+ * 그리고 @Service 어노테이션을 명시하고, 카멜 표기법으로 표시한 업무 인터페이스의 이름을 지정한다. + * 다음은 그 예이다. + *

 package cokr.xit.example.business.service.bean;
+ * ...
+ * {@code @Service("businessService")}
+ * public class BusinessServiceBean extends AbstractServiceBean implements BusinessService {
+ *     ...
+ * }
+ * 정의한 서비스 구현체는 다음과 같이 @Resource 어노테이션으로 이름을 명시해 주입한다. + *
 @Resource(name = "businessService")
+ * BusinessService businessService;
+ * @author mjkhan + */ +public abstract class AbstractServiceBean extends EgovAbstractServiceImpl { + /** 프로퍼티 서비스 */ + @Resource(name="propertyService") + protected EgovPropertyService properties; + /** 메시지 소스 */ + @Resource(name="messageSource") + protected MessageSource messages; + /** 로그이름 */ + private String logName; + + /**로그이름을 설정한다. + * @param logName 로그이름 + */ + public void setLogName(String logName) { + this.logName = logName; + } + + /**현재 접근한 사용자를 반환한다. + * @return 현재 접근한 사용자 + */ + protected T currentUser() { + return UserInfo.current(); + } + + /**현재 사용자의 접근정보를 반환한다. + * @return 현재 사용자의 접근정보 + */ + protected static Access currentAccess() { + return Access.current(); + } + + /**메시지 리소스 파일에서 key로 정의된 문자열과 args로 메시지를 만들어 반환한다. + * @param key 메시지 리소스 파일의 문자열 key + * @param args 해당 문자열에 전달할 인자 + * @return 메시지 + */ + protected String message(String key, String... args) { + return messages.getMessage(key, ifEmpty(args, () -> null), currentAccess().getLocale()); + } + + /**lv와 rv가 같은 지 반환한다. + * @param lv 좌측 값 + * @param rv 우측 값 + * @return + *
  • lv와 rv가 같으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + protected static final boolean equals(Object lv, Object rv) { + return lv == rv ? true : lv != null && lv.equals(rv); + } + + /**{@link Assert#isEmpty(Object)} 참고 + */ + protected static final boolean isEmpty(Object obj) { + return Assert.isEmpty(obj); + } + + /**{@link Assert#ifEmpty(Object, Supplier)} 참고*/ + protected static final T ifEmpty(T t, Supplier nt) { + return Assert.ifEmpty(t, nt); + } + + /**{@link Assert#ifEmpty(Object, Object)} 참고. + */ + protected static final T ifEmpty(T t, T nt) { + return Assert.ifEmpty(t, nt); + } + + /**obj가 빈 값이면 공백문자("")를 반환한다. 빈 값이 아니면 obj를 String으로 캐스팅하여 반환한다. + * @param obj + * @return + *
  • obj가 빈 값이면 공백문자("")
  • + *
  • 빈 값이 아니면 String으로 캐스팅한 obj
  • + *
+ */ + protected static final String blankIfEmpty(Object obj) { + return ifEmpty((String)obj, () -> ""); + } + + /**{@link Assert#notEmpty(Object, String)} 참고. + */ + protected static final T notEmpty(T t, String name) { + return Assert.notEmpty(t, name); + } + + /**t가 공백이 아닌지 확인하고 t를 반환한다. + * t가 공백이면, NullPointerException을 발생시키며, 메시지는 message-common 프로퍼티 파일의 'valueRequired'키에 설정된 값으로 만든다. + * @param t 값이나 객체 + * @param name assertion 실패 시 오류 메시지에 사용할 이름 + * @return t + */ + protected final T required(T t, String name) { + if (!isEmpty(t)) return t; + throw new NullPointerException(message("valueRequired", name)); + } + + /**{@link Assert#rootCause(Throwable)} 참고 + */ + protected static final Throwable rootCause(Throwable t) { + return Assert.rootCause(t); + } + + /**{@link ApplicationException#get(Throwable)} 참고 + */ + protected static final ApplicationException applicationException(Throwable t) { + return ApplicationException.get(t); + } + + /**klass와 연계된 Log를 반환한다. + * @param klass + * @return Log + */ + protected static Log log(Class klass) { + return Log.get(klass); + } + + /**현재 클래스와 연계된 Log를 반환한다. + * @return Log + */ + protected Log log() { + return isEmpty(logName) ? log(getClass()) : Log.get(logName); + } } \ 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 b2d157d..c00a0e3 100644 --- a/src/main/java/cokr/xit/foundation/component/QueryRequest.java +++ b/src/main/java/cokr/xit/foundation/component/QueryRequest.java @@ -1,120 +1,120 @@ -package cokr.xit.foundation.component; - -/**조회 서비스를 요청할 때 필요정보를 전달하는 클래스. - *

pageNum, fetchSize가 - *

  • 0보다 크면 결과를 페이징하여 반환한다.
  • - *
  • 그렇지 않으면 전체 결과를 반환한다.
  • - *
- * @author mjkhan - */ -public class QueryRequest extends ServiceRequest { - private static final long serialVersionUID = 1L; - private String by; - private String term; - private String orderBy; - private int pageNum; - private int fetchSize; - private String download; - - /**조회 조건 필드를 반환한다. - * @return 조회 조건 필드 - */ - public String getBy() { - return ifEmpty(by, () -> null); - } - - /**조회 조건 필드를 설정한다. - * @param by 조회 조건 필드 - * @return QueryRequest - */ - public T setBy(String by) { - this.by = by; - return self(); - } - - /**조회 조건을 반환한다. - * @return 조회 조건 - */ - public String getTerm() { - return ifEmpty(term, () -> null); - } - - /**조회 조건을 설정한다. - * @param term 조회 조건 - * @return QueryRequest - */ - public T setTerm(String term) { - this.term = term; - return self(); - } - - /**정렬 조건을 반환한다. - * @return 정렬 조건 - */ - public String getOrderBy() { - return orderBy; - } - - /**정렬 조건을 설정한다. - * @param orderBy 정렬 조건 - * @return QueryRequest - */ - public T setOrderBy(String orderBy) { - this.orderBy = orderBy; - return self(); - } - - /**조회 결과의 시작 페이지를 반환한다. - * @return dataStart 조회 결과의 시작 페이지 - */ - public int getPageNum() { - return isEmpty(download) ? pageNum : 0; - } - - /**조회 결과의 시작 페이지를 설정한다. - * @param pageNum 조회 결과의 시작 페이지 - * @return QueryRequest - */ - public T setPageNum(int pageNum) { - this.pageNum = pageNum; - return self(); - } - - /**한 번에 가지고 올 결과 갯수를 반환한다. - * @return fetchSize 한 번에 가지고 올 결과 갯수 - */ - public int getFetchSize() { - return isEmpty(download) ? fetchSize : 0; - } - - /**한 번에 가지고 올 결과 갯수를 설정한다. - * @param fetchSize 한 번에 가지고 올 결과 갯수 - * @return QueryRequest - */ - public T setFetchSize(int fetchSize) { - this.fetchSize = fetchSize; - return self(); - } - - /**조회결과를 다운로드할 파일 유형을 반환한다. - * @return download 다운로드 파일 유형 - *
  • 엑셀 파일 - "xls"
  • - *
  • 다운로드하지 않으면 빈 값
  • - *
- */ - public String getDownload() { - return download; - } - - /**조회결과를 다운로드할 유형을 설정한다. - * @param download 다운로드 파일 유형 - *
  • 엑셀 파일 - "xls"
  • - *
  • 다운로드하지 않으면 빈 값
  • - *
- * @return QueryRequest - */ - public T setDownload(String download) { - this.download = download; - return self(); - } +package cokr.xit.foundation.component; + +/**조회 서비스를 요청할 때 필요정보를 전달하는 클래스. + *

pageNum, fetchSize가 + *

  • 0보다 크면 결과를 페이징하여 반환한다.
  • + *
  • 그렇지 않으면 전체 결과를 반환한다.
  • + *
+ * @author mjkhan + */ +public class QueryRequest extends ServiceRequest { + private static final long serialVersionUID = 1L; + private String by; + private String term; + private String orderBy; + private int pageNum; + private int fetchSize; + private String download; + + /**조회 조건 필드를 반환한다. + * @return 조회 조건 필드 + */ + public String getBy() { + return ifEmpty(by, () -> null); + } + + /**조회 조건 필드를 설정한다. + * @param by 조회 조건 필드 + * @return QueryRequest + */ + public T setBy(String by) { + this.by = by; + return self(); + } + + /**조회 조건을 반환한다. + * @return 조회 조건 + */ + public String getTerm() { + return ifEmpty(term, () -> null); + } + + /**조회 조건을 설정한다. + * @param term 조회 조건 + * @return QueryRequest + */ + public T setTerm(String term) { + this.term = term; + return self(); + } + + /**정렬 조건을 반환한다. + * @return 정렬 조건 + */ + public String getOrderBy() { + return orderBy; + } + + /**정렬 조건을 설정한다. + * @param orderBy 정렬 조건 + * @return QueryRequest + */ + public T setOrderBy(String orderBy) { + this.orderBy = orderBy; + return self(); + } + + /**조회 결과의 시작 페이지를 반환한다. + * @return dataStart 조회 결과의 시작 페이지 + */ + public int getPageNum() { + return isEmpty(download) ? pageNum : 0; + } + + /**조회 결과의 시작 페이지를 설정한다. + * @param pageNum 조회 결과의 시작 페이지 + * @return QueryRequest + */ + public T setPageNum(int pageNum) { + this.pageNum = pageNum; + return self(); + } + + /**한 번에 가지고 올 결과 갯수를 반환한다. + * @return fetchSize 한 번에 가지고 올 결과 갯수 + */ + public int getFetchSize() { + return isEmpty(download) ? fetchSize : 0; + } + + /**한 번에 가지고 올 결과 갯수를 설정한다. + * @param fetchSize 한 번에 가지고 올 결과 갯수 + * @return QueryRequest + */ + public T setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + return self(); + } + + /**조회결과를 다운로드할 파일 유형을 반환한다. + * @return download 다운로드 파일 유형 + *
  • 엑셀 파일 - "xls"
  • + *
  • 다운로드하지 않으면 빈 값
  • + *
+ */ + public String getDownload() { + return download; + } + + /**조회결과를 다운로드할 유형을 설정한다. + * @param download 다운로드 파일 유형 + *
  • 엑셀 파일 - "xls"
  • + *
  • 다운로드하지 않으면 빈 값
  • + *
+ * @return QueryRequest + */ + public T setDownload(String download) { + this.download = download; + return self(); + } } \ 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 9981751..5044ee3 100644 --- a/src/main/java/cokr/xit/foundation/component/ServiceRequest.java +++ b/src/main/java/cokr/xit/foundation/component/ServiceRequest.java @@ -1,52 +1,52 @@ -package cokr.xit.foundation.component; - -import java.io.Serializable; - -import cokr.xit.foundation.AbstractComponent; - -/**서비스 요청을 위한 정보를 갖는 객체의 베이스 클래스.
- * 서비스를 요청할 때 전달해야할 정보가 많을 경우(4개 이상) 이 클래스를 상속받아 요청을 정의한다.
- * 전달해야할 정보가 많지 않으면 서비스 요청을 정의할 필요없다. - * @author mjkhan - */ -public abstract class ServiceRequest extends AbstractComponent implements Serializable { - private static final long serialVersionUID = 1L; - - private String type; - - /**요청유형을 반환한다. 요청유형은 애플리케이션에서 필요에 따라 정의한다. - * @return 요청유형 - */ - protected String getType() { - return ifEmpty(type, ""); - } - - /**요청유형을 설정한다. 요청유형은 애플리케이션에서 필요에 따라 정의한다. - * @param type 요청유형 - * @return 현재 요청 - */ - protected T setType(String type) { - this.type = type; - return self(); - } - - /**요청의 상태값에 대한 유효성을 체크한다.
- * 상태값이 유효하지 않으면 RuntimeException을 발생시킨다. - */ - public void validate() {} - - /**요청의 상태값이 유효한지 반환한다. - * @return 요청의 상태값의 유효 여부 - *
  • 요청의 상태값이 유효하면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isValid() { - try { - validate(); - return true; - } catch (Exception e) { - return false; - } - } +package cokr.xit.foundation.component; + +import java.io.Serializable; + +import cokr.xit.foundation.AbstractComponent; + +/**서비스 요청을 위한 정보를 갖는 객체의 베이스 클래스.
+ * 서비스를 요청할 때 전달해야할 정보가 많을 경우(4개 이상) 이 클래스를 상속받아 요청을 정의한다.
+ * 전달해야할 정보가 많지 않으면 서비스 요청을 정의할 필요없다. + * @author mjkhan + */ +public abstract class ServiceRequest extends AbstractComponent implements Serializable { + private static final long serialVersionUID = 1L; + + private String type; + + /**요청유형을 반환한다. 요청유형은 애플리케이션에서 필요에 따라 정의한다. + * @return 요청유형 + */ + protected String getType() { + return ifEmpty(type, ""); + } + + /**요청유형을 설정한다. 요청유형은 애플리케이션에서 필요에 따라 정의한다. + * @param type 요청유형 + * @return 현재 요청 + */ + protected T setType(String type) { + this.type = type; + return self(); + } + + /**요청의 상태값에 대한 유효성을 체크한다.
+ * 상태값이 유효하지 않으면 RuntimeException을 발생시킨다. + */ + public void validate() {} + + /**요청의 상태값이 유효한지 반환한다. + * @return 요청의 상태값의 유효 여부 + *
  • 요청의 상태값이 유효하면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isValid() { + try { + validate(); + return true; + } catch (Exception e) { + return false; + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/ServiceResponse.java b/src/main/java/cokr/xit/foundation/component/ServiceResponse.java index 67d735b..25fdc5b 100644 --- a/src/main/java/cokr/xit/foundation/component/ServiceResponse.java +++ b/src/main/java/cokr/xit/foundation/component/ServiceResponse.java @@ -1,14 +1,14 @@ -package cokr.xit.foundation.component; - -import java.io.Serializable; - -import cokr.xit.foundation.AbstractComponent; - -/**서비스 응답으로 반환하는 정보를 갖는 객체의 베이스 클래스.
- * 서비스 결과로 전달해야할 정보가 여러 개일 경우 이 클래스를 상속받아 응답클래스를 정의한다.
- * 결과 정보가 하나일 경우는 응답클래스를 정의할 필요없다. - * @author mjkhan - */ -public abstract class ServiceResponse extends AbstractComponent implements Serializable { - private static final long serialVersionUID = 1L; +package cokr.xit.foundation.component; + +import java.io.Serializable; + +import cokr.xit.foundation.AbstractComponent; + +/**서비스 응답으로 반환하는 정보를 갖는 객체의 베이스 클래스.
+ * 서비스 결과로 전달해야할 정보가 여러 개일 경우 이 클래스를 상속받아 응답클래스를 정의한다.
+ * 결과 정보가 하나일 경우는 응답클래스를 정의할 필요없다. + * @author mjkhan + */ +public abstract class ServiceResponse extends AbstractComponent implements Serializable { + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/package-info.java b/src/main/java/cokr/xit/foundation/component/package-info.java index 4d0e151..0020f5f 100644 --- a/src/main/java/cokr/xit/foundation/component/package-info.java +++ b/src/main/java/cokr/xit/foundation/component/package-info.java @@ -1,3 +1,3 @@ -/**서비스 선언 및 구현을 위한 주요 구성요소들을 정의. - */ +/**서비스 선언 및 구현을 위한 주요 구성요소들을 정의. + */ package cokr.xit.foundation.component; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/BSTR.java b/src/main/java/cokr/xit/foundation/data/BSTR.java index 152291f..01f61ab 100644 --- a/src/main/java/cokr/xit/foundation/data/BSTR.java +++ b/src/main/java/cokr/xit/foundation/data/BSTR.java @@ -1,190 +1,190 @@ -package cokr.xit.foundation.data; - -import java.nio.charset.Charset; -import java.util.Arrays; - -import cokr.xit.foundation.AbstractComponent; - -/**문자열을 지정하는 charset의 바이트 배열로 처리 시 사용하는 유틸리티` - *

다음은 시스템 기본 문자셋의 문자열을 euc-kr의 문자열로 변환하는 예다. - *

 String str = "뿌리 깊은 나무는 바람에 아니...";
- * BSTR euc_kr = new BSTR("euc-kr").set(str);
- * String converted = euc_kr.string();
- * 
- * - *

다음은 위 예에서 변환된 문자열의 처음부터 24바이트 길이만큼 가져오는 예다. - *

 String str24 = euc_kr.string(24);
- * 
- * - *

다음은 위 예에서 변환된 문자열의 25바이트 10바이트 길이만큼 가져오는 예다. - *

 String substr = euc_kr.string(25, 10);
- * 
- * - *

다음은 위 예에서 변환된 문자열의 36바이트부터 54바이트까지 가져오는 예다. - *

 substr = euc_kr.substring(36, 54);
- * 
- * - * @author mjkhan - */ -public class BSTR extends AbstractComponent { - private static final byte[] EMPTY = new byte[0]; - private Charset charset; - private byte[] bytes; - private String str; - private StringBuilder buff; - - /**새 BSTR을 생성한다. - * @param charset 문자셋 - */ - public BSTR(Charset charset) { - this.charset = ifEmpty(charset, Charset::defaultCharset); - } - - /**새 BSTR을 생성한다. - * @param charset 문자셋 이름 - */ - public BSTR(String charset) { - this(charset != null ? Charset.forName(charset) : null); - } - - /**처리할 문자열을 설정한다. 문자열은 {@link #BSTR(Charset) 설정한 문자셋}의 바이트 배열로 변환 후 처리한다. - * @param obj 문자열 - * @return BSTR - */ - public BSTR set(Object obj) { - String s = obj != null ? obj.toString() : null; - return set(s != null ? s.getBytes(charset) : null); - } - - /**처리할 문자열의 바이트 배열을 설정한다. - * @param bytes 처리할 문자열의 바이트 배열 - * @return BSTR - */ - public BSTR set(byte[] bytes) { - this.bytes = bytes != null ? bytes : EMPTY; - str = null; - return initBuffer(); - } - - private BSTR initBuffer() { - if (buff == null) - buff = new StringBuilder(); - else - buff.delete(0, buff.length()); - return this; - } - - /**설정된 문자열의 바이트 길이를 반환한다. - * @return 설정된 문자열의 바이트 길이 - */ - public long byteLength() { - return bytes.length; - } - - /**설정된 문자열이 비어있는지 반환한다. - * @return - *
  • 문자열이 비어있으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isEmpty() { - return byteLength() < 1; - } - - /**설정된 문자셋으로 변환한 문자열을 반환한다. - * @return 설정된 문자셋으로 변환한 문자열 - */ - public String string() { - return ifEmpty(str, () -> str = new String(bytes, charset)); - } - - /**지정한 바이트 길이의 문자열을 반환한다. - * @param byteLength 바이트 길이 - * @return 지정한 바이트 길이의 문자열 - */ - public String string(int byteLength) { - long diff = byteLength - byteLength(); - - if (diff > 0) { - initBuffer(); - buff.append(string()); - for (long i = 0; i < diff; ++i) - buff.append(' '); - return buff.toString(); - } - - if (diff < 0) - return substring(0, byteLength); - - return string(); - } - - /**시작위치부터 길이의 바이트로 문자열을 생성하여 반환한다. - * @param beginIndex 시작위치(0부터 시작) - * @param byteLength 바이트 길이 - * @return 시작위치부터 길이의 바이트로 생성한 문자열 - */ - public String string(int beginIndex, int byteLength) { - return substring(beginIndex, beginIndex + byteLength); - } - - /**시작위치부터 끝위치 까지의 바이트로 문자열을 생성하여 반환한다. - * @param beginIndex 시작위치 - * @param endIndex 끝위치 - * @return 시작위치부터 끝위치 까지의 바이트로 생성한 문자열 - */ - public String substring(int beginIndex, int endIndex) { - if (endIndex > byteLength()) - throw new IndexOutOfBoundsException(String.format("beginIndex: %d, endIndex:%d", beginIndex, endIndex)); - - int size = 0; - int length = endIndex - beginIndex; - initBuffer(); - byte[] copy = Arrays.copyOfRange(bytes, beginIndex, endIndex); - - for (char ch: new String(copy, charset).toCharArray()) { - int s = String.valueOf(ch).getBytes(charset).length; - if ((size + s) > length) break; - - size += s; - buff.append(readable(ch) ? ch : ' '); - } - - for (int i = 0, diff = length - size; i < diff; ++i) - buff.append(' '); - - return buff.toString(); - } - - private static boolean readable(char ch) { - if (Character.isLetterOrDigit(ch)) - return true; - - int type = Character.getType(ch); - return type >= Character.DASH_PUNCTUATION && type <= Character.MODIFIER_SYMBOL; - } - - /**지정한 길이의 숫자열을 반환한다. - * @param length 숫자열 길이 - * @return 지정한 길이의 숫자열 - */ - public String number(int length) { - String num = string().replace(" ", "0"); - int diff = length - num.length(); - - if (diff > 0) { - initBuffer(); - for (long i = 0; i < diff; ++i) - buff.append(num); - return buff.toString(); - } - if (diff < 0) - return num.substring(0, length); - return num; - } - - @Override - public String toString() { - return string(); - } -} +package cokr.xit.foundation.data; + +import java.nio.charset.Charset; +import java.util.Arrays; + +import cokr.xit.foundation.AbstractComponent; + +/**문자열을 지정하는 charset의 바이트 배열로 처리 시 사용하는 유틸리티` + *

다음은 시스템 기본 문자셋의 문자열을 euc-kr의 문자열로 변환하는 예다. + *

 String str = "뿌리 깊은 나무는 바람에 아니...";
+ * BSTR euc_kr = new BSTR("euc-kr").set(str);
+ * String converted = euc_kr.string();
+ * 
+ * + *

다음은 위 예에서 변환된 문자열의 처음부터 24바이트 길이만큼 가져오는 예다. + *

 String str24 = euc_kr.string(24);
+ * 
+ * + *

다음은 위 예에서 변환된 문자열의 25바이트 10바이트 길이만큼 가져오는 예다. + *

 String substr = euc_kr.string(25, 10);
+ * 
+ * + *

다음은 위 예에서 변환된 문자열의 36바이트부터 54바이트까지 가져오는 예다. + *

 substr = euc_kr.substring(36, 54);
+ * 
+ * + * @author mjkhan + */ +public class BSTR extends AbstractComponent { + private static final byte[] EMPTY = new byte[0]; + private Charset charset; + private byte[] bytes; + private String str; + private StringBuilder buff; + + /**새 BSTR을 생성한다. + * @param charset 문자셋 + */ + public BSTR(Charset charset) { + this.charset = ifEmpty(charset, Charset::defaultCharset); + } + + /**새 BSTR을 생성한다. + * @param charset 문자셋 이름 + */ + public BSTR(String charset) { + this(charset != null ? Charset.forName(charset) : null); + } + + /**처리할 문자열을 설정한다. 문자열은 {@link #BSTR(Charset) 설정한 문자셋}의 바이트 배열로 변환 후 처리한다. + * @param obj 문자열 + * @return BSTR + */ + public BSTR set(Object obj) { + String s = obj != null ? obj.toString() : null; + return set(s != null ? s.getBytes(charset) : null); + } + + /**처리할 문자열의 바이트 배열을 설정한다. + * @param bytes 처리할 문자열의 바이트 배열 + * @return BSTR + */ + public BSTR set(byte[] bytes) { + this.bytes = bytes != null ? bytes : EMPTY; + str = null; + return initBuffer(); + } + + private BSTR initBuffer() { + if (buff == null) + buff = new StringBuilder(); + else + buff.delete(0, buff.length()); + return this; + } + + /**설정된 문자열의 바이트 길이를 반환한다. + * @return 설정된 문자열의 바이트 길이 + */ + public long byteLength() { + return bytes.length; + } + + /**설정된 문자열이 비어있는지 반환한다. + * @return + *
  • 문자열이 비어있으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isEmpty() { + return byteLength() < 1; + } + + /**설정된 문자셋으로 변환한 문자열을 반환한다. + * @return 설정된 문자셋으로 변환한 문자열 + */ + public String string() { + return ifEmpty(str, () -> str = new String(bytes, charset)); + } + + /**지정한 바이트 길이의 문자열을 반환한다. + * @param byteLength 바이트 길이 + * @return 지정한 바이트 길이의 문자열 + */ + public String string(int byteLength) { + long diff = byteLength - byteLength(); + + if (diff > 0) { + initBuffer(); + buff.append(string()); + for (long i = 0; i < diff; ++i) + buff.append(' '); + return buff.toString(); + } + + if (diff < 0) + return substring(0, byteLength); + + return string(); + } + + /**시작위치부터 길이의 바이트로 문자열을 생성하여 반환한다. + * @param beginIndex 시작위치(0부터 시작) + * @param byteLength 바이트 길이 + * @return 시작위치부터 길이의 바이트로 생성한 문자열 + */ + public String string(int beginIndex, int byteLength) { + return substring(beginIndex, beginIndex + byteLength); + } + + /**시작위치부터 끝위치 까지의 바이트로 문자열을 생성하여 반환한다. + * @param beginIndex 시작위치 + * @param endIndex 끝위치 + * @return 시작위치부터 끝위치 까지의 바이트로 생성한 문자열 + */ + public String substring(int beginIndex, int endIndex) { + if (endIndex > byteLength()) + throw new IndexOutOfBoundsException(String.format("beginIndex: %d, endIndex:%d", beginIndex, endIndex)); + + int size = 0; + int length = endIndex - beginIndex; + initBuffer(); + byte[] copy = Arrays.copyOfRange(bytes, beginIndex, endIndex); + + for (char ch: new String(copy, charset).toCharArray()) { + int s = String.valueOf(ch).getBytes(charset).length; + if ((size + s) > length) break; + + size += s; + buff.append(readable(ch) ? ch : ' '); + } + + for (int i = 0, diff = length - size; i < diff; ++i) + buff.append(' '); + + return buff.toString(); + } + + private static boolean readable(char ch) { + if (Character.isLetterOrDigit(ch)) + return true; + + int type = Character.getType(ch); + return type >= Character.DASH_PUNCTUATION && type <= Character.MODIFIER_SYMBOL; + } + + /**지정한 길이의 숫자열을 반환한다. + * @param length 숫자열 길이 + * @return 지정한 길이의 숫자열 + */ + public String number(int length) { + String num = string().replace(" ", "0"); + int diff = length - num.length(); + + if (diff > 0) { + initBuffer(); + for (long i = 0; i < diff; ++i) + buff.append(num); + return buff.toString(); + } + if (diff < 0) + return num.substring(0, length); + return num; + } + + @Override + public String toString() { + return string(); + } +} diff --git a/src/main/java/cokr/xit/foundation/data/Convert.java b/src/main/java/cokr/xit/foundation/data/Convert.java index 0e6b134..8f6a9fb 100644 --- a/src/main/java/cokr/xit/foundation/data/Convert.java +++ b/src/main/java/cokr/xit/foundation/data/Convert.java @@ -1,279 +1,279 @@ -package cokr.xit.foundation.data; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.HashMap; - -import cokr.xit.foundation.Assert; - -/**데이터 변환 유틸리티 - */ -public class Convert { - private Convert() {} - /**주어진 문자열을 주어진 클래스의 객체로 변환한다. - * @param klass 클래스 - * @param s 문자열 - * @return 클래스의 객체 - */ - public static final Object toObject(Class klass, String s) { - if (Assert.isEmpty(s)) return null; - if (String.class.equals(Assert.notEmpty(klass, "klass"))) return s; - - s = s.trim(); - if (isNumber(klass)) { - s = s.replace(",", ""); - if (BigDecimal.class.equals(klass)) return new BigDecimal(s); - else if (Double.class.equals(klass)) return Double.valueOf(s); - else if (Float.class.equals(klass)) return Float.valueOf(s); - else if (BigInteger.class.equals(klass)) return new BigInteger(s); - else if (Integer.class.equals(klass)) return Integer.valueOf(s); - else if (Long.class.equals(klass)) return Long.valueOf(s); - else if (Short.class.equals(klass)) return Short.valueOf(s); - else if (Byte.class.equals(klass)) return Byte.valueOf(s); - } else { - if (Boolean.class.equals(klass)) return Boolean.valueOf(s); - else if (Character.class.equals(klass)) return Character.valueOf(s.charAt(0)); -/* - if (Clob.class.isAssignableFrom(klass)) return s; - else if (Date.class.equals(klass)) return Date.valueOf(s); - else if (Time.class.equals(klass)) return Time.valueOf(s); - else if (Timestamp.class.equals(klass)) return Timestamp.valueOf(s); -*/ - } - throw inconvertible(s, klass); - } - - /**주어진 클래스가 Number 클래스인지 반환한다. - * @param klass 클래스 - * @return - *
  • 클래스가 Number 클래스면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public static final boolean isNumber(Class klass) { - return Number.class.isAssignableFrom(klass); - } - - /**주어진 객체를 문자열로 변환한다. - * @param obj 객체 - * @return 객체의 문자열 표시 - */ - public static final String toString(Object obj) { - if (Assert.isEmpty(obj)) - return ""; - if (obj instanceof String) - return String.class.cast(obj); - else - return obj.toString(); - } - - /**주어진 객체를 Number 객체로 변환한다. - * @param obj 객체 - * @return - *
  • Number 객체를 변환한 Number
  • - *
  • 객체가 비어있으면 Integer(0)
  • - *
- */ - public static final Number toNumber(Object obj) { - if (Assert.isEmpty(obj)) - return Integer.valueOf(0); - if (obj instanceof Number) - return Number.class.cast(obj); - if (obj instanceof String) { - String s = (String)obj; - return Double.valueOf(s.replace(",", "").trim()); - } - throw inconvertible(obj, Number.class); - } - - /**주어진 객체를 short값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 short값
  • - *
  • 객체가 비어있으면 0
  • - *
- */ - public static final short toShort(Object obj) { - return toNumber(obj).shortValue(); - } - - /**주어진 객체를 int값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 int값
  • - *
  • 객체가 비어있으면 0
  • - *
- */ - public static final int toInt(Object obj) { - return toNumber(obj).intValue(); - } - - /**주어진 객체를 long값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 long값
  • - *
  • 객체가 비어있으면 0
  • - *
- */ - public static final long toLong(Object obj) { - return toNumber(obj).longValue(); - } - - /**주어진 객체를 long값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 long값
  • - *
  • 객체가 비어있으면 0
  • - *
- */ - public static final double toDouble(Object obj) { - return toNumber(obj).doubleValue(); - } - - /**주어진 객체를 float값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 float값
  • - *
  • 객체가 비어있으면 0
  • - *
- */ - public static final float toFloat(Object obj) { - return toNumber(obj).floatValue(); - } - - /**주어진 객체를 byte값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 byte값
  • - *
  • 객체가 비어있으면 0
  • - *
- */ - public static final byte toByte(Object obj) { - return toNumber(obj).byteValue(); - } - - /**주어진 객체를 boolean값으로 변환한다. - * @param obj 객체 - * @return - *
  • 객체를 변환한 boolean값
  • - *
  • 객체가 비어있으면 false
  • - *
- */ - public static final boolean toBoolean(Object obj) { - if (Assert.isEmpty(obj)) - return false; - Boolean bool = obj instanceof Boolean ? Boolean.class.cast(obj) : Boolean.valueOf(obj.toString()); - return bool.booleanValue(); - } - - private static final HashMap, Object> ifnull = new HashMap<>(); - static { - ifnull.put(char.class, Character.valueOf(' ')); - ifnull.put(byte.class, Byte.valueOf("0")); - ifnull.put(short.class, Short.valueOf("0")); - ifnull.put(int.class, Integer.valueOf(0)); - ifnull.put(long.class, Long.valueOf(0)); - ifnull.put(float.class, Float.valueOf(0)); - ifnull.put(double.class, Double.valueOf(0)); - ifnull.put(boolean.class, Boolean.FALSE); - } - /**주어진 wrapper 클래스에 상응하는 기본형의 디폴트값을 반환한다. - * @param klass 기본형 데이터의 wrapper 클래스 - * @return - *
  • wrapper 클래스에 상응하는 기본형의 디폴트값
  • - *
  • 클래스가 기본형의 wrapper가 아니면 null
  • - *
- */ - public static final Object primitiveDefault(Class klass) { - return klass != null && klass.isPrimitive() ? ifnull.get(klass) : null; - } - - /**주어진 문자열의 \r, \n, \t, 큰 따옴표를 이스케이프하여 반환한다. - * @param s 문자열 - * @return \r, \n, \t, 큰 따옴표를 이스케이프한 문자열 - */ - public static final String rntq(String s) { - return Assert.isEmpty(s) ? "" : - s.replace("\\","\\\\") - .replace("\r","\\r") - .replace("\n","\\n") - .replace("\t","\\t") - .replace("\"","\\\""); - } - - private static RuntimeException inconvertible(Object obj, Class klass) { - return new RuntimeException("Inconvertible: " + obj + " to " + klass); - } - - /**{@link Convert}에 대한 인터페이스. - *

이 인터페이스의 모든 메소드는 디폴트로 Convert 클래스의 메소드를 호출하도록 구현돼 있다.
- * Assert를 상속받지 않는 클래스에서 쉽게 Convert의 메소드를 사용하도록 하기 위한 것이다. - *

- */ - public static interface Support { - /**{@link Convert#toObject(Class, String)} 참고.*/ - default Object toObject(Class klass, String s) { - return Convert.toObject(klass, s); - } - - /**{@link Convert#isNumber(Class)} 참고.*/ - default boolean isNumber(Class klass) { - return Convert.isNumber(klass); - } - - /**{@link Convert#toString(Object)} 참고.*/ - default String toString(Object obj) { - return Convert.toString(obj); - } - - /**{@link Convert#toNumber(Object)} 참고.*/ - default Number toNumber(Object obj) { - return Convert.toNumber(obj); - } - - /**{@link Convert#toShort(Object)} 참고.*/ - default short toShort(Object obj) { - return Convert.toShort(obj); - } - - /**{@link Convert#toInt(Object)} 참고.*/ - default int toInt(Object obj) { - return Convert.toInt(obj); - } - - /**{@link Convert#toLong(Object)} 참고.*/ - default long toLong(Object obj) { - return Convert.toLong(obj); - } - - /**{@link Convert#toDouble(Object)} 참고.*/ - default double toDouble(Object obj) { - return Convert.toDouble(obj); - } - - /**{@link Convert#toFloat(Object)} 참고.*/ - default float toFloat(Object obj) { - return Convert.toFloat(obj); - } - - /**{@link Convert#toByte(Object)} 참고.*/ - default byte toByte(Object obj) { - return Convert.toByte(obj); - } - - /**{@link Convert#toBoolean(Object)} 참고.*/ - default boolean toBoolean(Object obj) { - return Convert.toBoolean(obj); - } - - /**{@link Convert#primitiveDefault(Class)} 참고.*/ - default Object primitiveDefault(Class klass) { - return Convert.primitiveDefault(klass); - } - - /**{@link Convert#rntq(String)} 참고.*/ - default String rntq(String s) { - return Convert.rntq(s); - } - } +package cokr.xit.foundation.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; + +import cokr.xit.foundation.Assert; + +/**데이터 변환 유틸리티 + */ +public class Convert { + private Convert() {} + /**주어진 문자열을 주어진 클래스의 객체로 변환한다. + * @param klass 클래스 + * @param s 문자열 + * @return 클래스의 객체 + */ + public static final Object toObject(Class klass, String s) { + if (Assert.isEmpty(s)) return null; + if (String.class.equals(Assert.notEmpty(klass, "klass"))) return s; + + s = s.trim(); + if (isNumber(klass)) { + s = s.replace(",", ""); + if (BigDecimal.class.equals(klass)) return new BigDecimal(s); + else if (Double.class.equals(klass)) return Double.valueOf(s); + else if (Float.class.equals(klass)) return Float.valueOf(s); + else if (BigInteger.class.equals(klass)) return new BigInteger(s); + else if (Integer.class.equals(klass)) return Integer.valueOf(s); + else if (Long.class.equals(klass)) return Long.valueOf(s); + else if (Short.class.equals(klass)) return Short.valueOf(s); + else if (Byte.class.equals(klass)) return Byte.valueOf(s); + } else { + if (Boolean.class.equals(klass)) return Boolean.valueOf(s); + else if (Character.class.equals(klass)) return Character.valueOf(s.charAt(0)); +/* + if (Clob.class.isAssignableFrom(klass)) return s; + else if (Date.class.equals(klass)) return Date.valueOf(s); + else if (Time.class.equals(klass)) return Time.valueOf(s); + else if (Timestamp.class.equals(klass)) return Timestamp.valueOf(s); +*/ + } + throw inconvertible(s, klass); + } + + /**주어진 클래스가 Number 클래스인지 반환한다. + * @param klass 클래스 + * @return + *
  • 클래스가 Number 클래스면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public static final boolean isNumber(Class klass) { + return Number.class.isAssignableFrom(klass); + } + + /**주어진 객체를 문자열로 변환한다. + * @param obj 객체 + * @return 객체의 문자열 표시 + */ + public static final String toString(Object obj) { + if (Assert.isEmpty(obj)) + return ""; + if (obj instanceof String) + return String.class.cast(obj); + else + return obj.toString(); + } + + /**주어진 객체를 Number 객체로 변환한다. + * @param obj 객체 + * @return + *
  • Number 객체를 변환한 Number
  • + *
  • 객체가 비어있으면 Integer(0)
  • + *
+ */ + public static final Number toNumber(Object obj) { + if (Assert.isEmpty(obj)) + return Integer.valueOf(0); + if (obj instanceof Number) + return Number.class.cast(obj); + if (obj instanceof String) { + String s = (String)obj; + return Double.valueOf(s.replace(",", "").trim()); + } + throw inconvertible(obj, Number.class); + } + + /**주어진 객체를 short값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 short값
  • + *
  • 객체가 비어있으면 0
  • + *
+ */ + public static final short toShort(Object obj) { + return toNumber(obj).shortValue(); + } + + /**주어진 객체를 int값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 int값
  • + *
  • 객체가 비어있으면 0
  • + *
+ */ + public static final int toInt(Object obj) { + return toNumber(obj).intValue(); + } + + /**주어진 객체를 long값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 long값
  • + *
  • 객체가 비어있으면 0
  • + *
+ */ + public static final long toLong(Object obj) { + return toNumber(obj).longValue(); + } + + /**주어진 객체를 long값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 long값
  • + *
  • 객체가 비어있으면 0
  • + *
+ */ + public static final double toDouble(Object obj) { + return toNumber(obj).doubleValue(); + } + + /**주어진 객체를 float값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 float값
  • + *
  • 객체가 비어있으면 0
  • + *
+ */ + public static final float toFloat(Object obj) { + return toNumber(obj).floatValue(); + } + + /**주어진 객체를 byte값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 byte값
  • + *
  • 객체가 비어있으면 0
  • + *
+ */ + public static final byte toByte(Object obj) { + return toNumber(obj).byteValue(); + } + + /**주어진 객체를 boolean값으로 변환한다. + * @param obj 객체 + * @return + *
  • 객체를 변환한 boolean값
  • + *
  • 객체가 비어있으면 false
  • + *
+ */ + public static final boolean toBoolean(Object obj) { + if (Assert.isEmpty(obj)) + return false; + Boolean bool = obj instanceof Boolean ? Boolean.class.cast(obj) : Boolean.valueOf(obj.toString()); + return bool.booleanValue(); + } + + private static final HashMap, Object> ifnull = new HashMap<>(); + static { + ifnull.put(char.class, Character.valueOf(' ')); + ifnull.put(byte.class, Byte.valueOf("0")); + ifnull.put(short.class, Short.valueOf("0")); + ifnull.put(int.class, Integer.valueOf(0)); + ifnull.put(long.class, Long.valueOf(0)); + ifnull.put(float.class, Float.valueOf(0)); + ifnull.put(double.class, Double.valueOf(0)); + ifnull.put(boolean.class, Boolean.FALSE); + } + /**주어진 wrapper 클래스에 상응하는 기본형의 디폴트값을 반환한다. + * @param klass 기본형 데이터의 wrapper 클래스 + * @return + *
  • wrapper 클래스에 상응하는 기본형의 디폴트값
  • + *
  • 클래스가 기본형의 wrapper가 아니면 null
  • + *
+ */ + public static final Object primitiveDefault(Class klass) { + return klass != null && klass.isPrimitive() ? ifnull.get(klass) : null; + } + + /**주어진 문자열의 \r, \n, \t, 큰 따옴표를 이스케이프하여 반환한다. + * @param s 문자열 + * @return \r, \n, \t, 큰 따옴표를 이스케이프한 문자열 + */ + public static final String rntq(String s) { + return Assert.isEmpty(s) ? "" : + s.replace("\\","\\\\") + .replace("\r","\\r") + .replace("\n","\\n") + .replace("\t","\\t") + .replace("\"","\\\""); + } + + private static RuntimeException inconvertible(Object obj, Class klass) { + return new RuntimeException("Inconvertible: " + obj + " to " + klass); + } + + /**{@link Convert}에 대한 인터페이스. + *

이 인터페이스의 모든 메소드는 디폴트로 Convert 클래스의 메소드를 호출하도록 구현돼 있다.
+ * Assert를 상속받지 않는 클래스에서 쉽게 Convert의 메소드를 사용하도록 하기 위한 것이다. + *

+ */ + public static interface Support { + /**{@link Convert#toObject(Class, String)} 참고.*/ + default Object toObject(Class klass, String s) { + return Convert.toObject(klass, s); + } + + /**{@link Convert#isNumber(Class)} 참고.*/ + default boolean isNumber(Class klass) { + return Convert.isNumber(klass); + } + + /**{@link Convert#toString(Object)} 참고.*/ + default String toString(Object obj) { + return Convert.toString(obj); + } + + /**{@link Convert#toNumber(Object)} 참고.*/ + default Number toNumber(Object obj) { + return Convert.toNumber(obj); + } + + /**{@link Convert#toShort(Object)} 참고.*/ + default short toShort(Object obj) { + return Convert.toShort(obj); + } + + /**{@link Convert#toInt(Object)} 참고.*/ + default int toInt(Object obj) { + return Convert.toInt(obj); + } + + /**{@link Convert#toLong(Object)} 참고.*/ + default long toLong(Object obj) { + return Convert.toLong(obj); + } + + /**{@link Convert#toDouble(Object)} 참고.*/ + default double toDouble(Object obj) { + return Convert.toDouble(obj); + } + + /**{@link Convert#toFloat(Object)} 참고.*/ + default float toFloat(Object obj) { + return Convert.toFloat(obj); + } + + /**{@link Convert#toByte(Object)} 참고.*/ + default byte toByte(Object obj) { + return Convert.toByte(obj); + } + + /**{@link Convert#toBoolean(Object)} 참고.*/ + default boolean toBoolean(Object obj) { + return Convert.toBoolean(obj); + } + + /**{@link Convert#primitiveDefault(Class)} 참고.*/ + default Object primitiveDefault(Class klass) { + return Convert.primitiveDefault(klass); + } + + /**{@link Convert#rntq(String)} 참고.*/ + default String rntq(String s) { + return Convert.rntq(s); + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/DataFormat.java b/src/main/java/cokr/xit/foundation/data/DataFormat.java index 471910f..a1f6f15 100644 --- a/src/main/java/cokr/xit/foundation/data/DataFormat.java +++ b/src/main/java/cokr/xit/foundation/data/DataFormat.java @@ -1,32 +1,32 @@ -package cokr.xit.foundation.data; - -import cokr.xit.foundation.AbstractComponent; - -public class DataFormat extends AbstractComponent { - /**'-'로 구분된 법인등록번호를 반환한다. - * @param str '-'이 없는 법인등록번호 - * @return '-'로 구분된 법인등록번호 - */ - public static final String corporateNo(String str) { - return !isEmpty(str) ? str.substring(0, 6) + "-" + str.substring(6) : ""; - } - - /**'-'로 구분된 사업자등록번호를 반환한다. - * @param str '-'이 없는 사업자등록번호 - * @return '-'로 구분된 사업자등록번호 - */ - public static final String businessNo(String str) { - return !isEmpty(str) ? str.substring(0, 3) + "-" + str.substring(3, 5) + "-" + str.substring(5) : ""; - } - - - /**'-'로 구분된 날짜(yyyy-MM-dd)를 반환한다. - * @param str '-'이 없는 날짜 - * @return '-'로 구분된 날짜 - */ - public static final String yyyy_mm_dd(String str) { - if (isEmpty(str)) return ""; - - return str.substring(0, 4) + "-" + str.substring(4, 6) + "-" + str.substring(6); - } +package cokr.xit.foundation.data; + +import cokr.xit.foundation.AbstractComponent; + +public class DataFormat extends AbstractComponent { + /**'-'로 구분된 법인등록번호를 반환한다. + * @param str '-'이 없는 법인등록번호 + * @return '-'로 구분된 법인등록번호 + */ + public static final String corporateNo(String str) { + return !isEmpty(str) ? str.substring(0, 6) + "-" + str.substring(6) : ""; + } + + /**'-'로 구분된 사업자등록번호를 반환한다. + * @param str '-'이 없는 사업자등록번호 + * @return '-'로 구분된 사업자등록번호 + */ + public static final String businessNo(String str) { + return !isEmpty(str) ? str.substring(0, 3) + "-" + str.substring(3, 5) + "-" + str.substring(5) : ""; + } + + + /**'-'로 구분된 날짜(yyyy-MM-dd)를 반환한다. + * @param str '-'이 없는 날짜 + * @return '-'로 구분된 날짜 + */ + public static final String yyyy_mm_dd(String str) { + if (isEmpty(str)) return ""; + + return str.substring(0, 4) + "-" + str.substring(4, 6) + "-" + str.substring(6); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/DataObject.java b/src/main/java/cokr/xit/foundation/data/DataObject.java index 18c4bfb..5d06712 100644 --- a/src/main/java/cokr/xit/foundation/data/DataObject.java +++ b/src/main/java/cokr/xit/foundation/data/DataObject.java @@ -1,127 +1,127 @@ -package cokr.xit.foundation.data; - -import java.util.Map; -import java.util.function.Supplier; - -import cokr.xit.foundation.Assert; - -/**

기본 데이터 오브젝트. - *

- *

필드값에 접근할 때 사용하는 필드이름은 대소문자를 구분하지 않는다. - *

- */ -public class DataObject extends StringMap { - private static final long serialVersionUID = 1L; - - /**map의 key와 value로 설정된 DataObject를 생성 반환한다. - * @param map Map - * @return DataObject - */ - public static DataObject create(Map map) { - if (map == null) return null; - if (map instanceof DataObject) return (DataObject)map; - - DataObject result = new DataObject(); - map.forEach(result::put); - - return result; - } - - /**DataObject를 생성한다. - */ - public DataObject() { - caseSensitiveKey(false); - } - - /**lv와 rv가 같은지 반환한다. - * @param lv 비교항의 좌측값 - * @param rv 비교항의 우측값 - * @return - *
  • lv와 rv가 같으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - protected static boolean equals(Object lv, Object rv) { - return lv == rv ? true : lv != null && lv.equals(rv); - } - - /**{@link Assert#isEmpty(Object) Assert.isEmpty(...)} 참고. - */ - protected static boolean isEmpty(Object obj) { - return Assert.isEmpty(obj); - } - - /**{@link Assert#ifEmpty(Object, Supplier) Assert.ifEmpty(...)} 참고*/ - protected static T ifEmpty(T t, Supplier nt) { - return Assert.ifEmpty(t, nt); - } - - /**{@link Assert#notEmpty(Object, String) Assert.notEmpty(...)} 참고. - */ - protected static T notEmpty(T t, String name) { - return Assert.notEmpty(t, name); - } - - /**지정하는 이름의 값들이 빈 값인지 확인한다. - * @param names 비어있는지 확인할 값들의 이름 - * @return DataObject 자신 - */ - public DataObject notEmpty(String... names) { - for (String name: names) - notEmpty(get(name), name); - return this; - } - - @SuppressWarnings("unchecked") - /**지정하는 이름의 값을 T로 캐스팅하여 반환한다. - * @param name 필드 이름 - * @return T로 캐스팅한 값 - * @throws ClassCastException - */ - public T value(String name) { - return (T)get(name); - } - - /**지정하는 이름의 값을 String으로 변환하여 반환한다. - * @param name 필드 이름 - * @return String으로 변환한 값 - */ - public String string(String name) { - Object obj = get(name); - return isEmpty(obj) ? "" : obj instanceof String ? (String)obj : obj.toString().trim(); - } - - /**지정하는 이름의 값을 Number로 변환하여 반환한다. - * @param name 필드 이름 - * @return - *
  • Number로 변환한 값
  • - *
  • 값이 비어있을 경우 Integer(0)
  • - *
- */ - public Number number(String name) { - Object obj = get(name); - if (obj == null) - return Integer.valueOf(0); - if (obj instanceof Number) - return Number.class.cast(obj); - if (obj instanceof String) - return Double.valueOf((String)obj); - throw new RuntimeException("The Object named '" + name + "' is not a Number"); - } - - /**지정한 이름의 필드값을 boolean값으로 변환하여 반환한다. - * @param name 필드 이름 - * @return - *
  • boolean 변환한 값
  • - *
  • 빈 값이면 false
  • - *
- */ - public boolean bool(String name) { - Object obj = get(name); - if (obj == null) - return false; - - Boolean bool = obj instanceof Boolean ? Boolean.class.cast(obj) : Boolean.valueOf(obj.toString()); - return bool.booleanValue(); - } +package cokr.xit.foundation.data; + +import java.util.Map; +import java.util.function.Supplier; + +import cokr.xit.foundation.Assert; + +/**

기본 데이터 오브젝트. + *

+ *

필드값에 접근할 때 사용하는 필드이름은 대소문자를 구분하지 않는다. + *

+ */ +public class DataObject extends StringMap { + private static final long serialVersionUID = 1L; + + /**map의 key와 value로 설정된 DataObject를 생성 반환한다. + * @param map Map + * @return DataObject + */ + public static DataObject create(Map map) { + if (map == null) return null; + if (map instanceof DataObject) return (DataObject)map; + + DataObject result = new DataObject(); + map.forEach(result::put); + + return result; + } + + /**DataObject를 생성한다. + */ + public DataObject() { + caseSensitiveKey(false); + } + + /**lv와 rv가 같은지 반환한다. + * @param lv 비교항의 좌측값 + * @param rv 비교항의 우측값 + * @return + *
  • lv와 rv가 같으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + protected static boolean equals(Object lv, Object rv) { + return lv == rv ? true : lv != null && lv.equals(rv); + } + + /**{@link Assert#isEmpty(Object) Assert.isEmpty(...)} 참고. + */ + protected static boolean isEmpty(Object obj) { + return Assert.isEmpty(obj); + } + + /**{@link Assert#ifEmpty(Object, Supplier) Assert.ifEmpty(...)} 참고*/ + protected static T ifEmpty(T t, Supplier nt) { + return Assert.ifEmpty(t, nt); + } + + /**{@link Assert#notEmpty(Object, String) Assert.notEmpty(...)} 참고. + */ + protected static T notEmpty(T t, String name) { + return Assert.notEmpty(t, name); + } + + /**지정하는 이름의 값들이 빈 값인지 확인한다. + * @param names 비어있는지 확인할 값들의 이름 + * @return DataObject 자신 + */ + public DataObject notEmpty(String... names) { + for (String name: names) + notEmpty(get(name), name); + return this; + } + + @SuppressWarnings("unchecked") + /**지정하는 이름의 값을 T로 캐스팅하여 반환한다. + * @param name 필드 이름 + * @return T로 캐스팅한 값 + * @throws ClassCastException + */ + public T value(String name) { + return (T)get(name); + } + + /**지정하는 이름의 값을 String으로 변환하여 반환한다. + * @param name 필드 이름 + * @return String으로 변환한 값 + */ + public String string(String name) { + Object obj = get(name); + return isEmpty(obj) ? "" : obj instanceof String ? (String)obj : obj.toString().trim(); + } + + /**지정하는 이름의 값을 Number로 변환하여 반환한다. + * @param name 필드 이름 + * @return + *
  • Number로 변환한 값
  • + *
  • 값이 비어있을 경우 Integer(0)
  • + *
+ */ + public Number number(String name) { + Object obj = get(name); + if (obj == null) + return Integer.valueOf(0); + if (obj instanceof Number) + return Number.class.cast(obj); + if (obj instanceof String) + return Double.valueOf((String)obj); + throw new RuntimeException("The Object named '" + name + "' is not a Number"); + } + + /**지정한 이름의 필드값을 boolean값으로 변환하여 반환한다. + * @param name 필드 이름 + * @return + *
  • boolean 변환한 값
  • + *
  • 빈 값이면 false
  • + *
+ */ + public boolean bool(String name) { + Object obj = get(name); + if (obj == null) + return false; + + Boolean bool = obj instanceof Boolean ? Boolean.class.cast(obj) : Boolean.valueOf(obj.toString()); + return bool.booleanValue(); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/JSON.java b/src/main/java/cokr/xit/foundation/data/JSON.java index 938273b..c68aca2 100644 --- a/src/main/java/cokr/xit/foundation/data/JSON.java +++ b/src/main/java/cokr/xit/foundation/data/JSON.java @@ -1,87 +1,87 @@ -package cokr.xit.foundation.data; - -import java.io.InputStream; - -import com.fasterxml.jackson.core.JsonParser.Feature; -import com.fasterxml.jackson.databind.ObjectMapper; - -import cokr.xit.foundation.AbstractComponent; - -/**JSON 포맷/파싱 유틸리티. - * @author mjkhan - */ -public class JSON extends AbstractComponent { - private ObjectMapper objectMapper; - - /**JSON 포맷/파싱에 사용하는 ObjectMapper를 반환한다. - * @return JSON 포맷/파싱에 사용하는 ObjectMapper - */ - public ObjectMapper getObjectMapper() { - if (objectMapper == null) { - objectMapper = new ObjectMapper(); - objectMapper.configure(Feature.ALLOW_COMMENTS, true); - } - return objectMapper; - } - - /**JSON 포맷/파싱에 사용하는 ObjectMapper를 설정한다. - * @param objectMapper JSON 포맷/파싱에 사용하는 ObjectMapper - * @return 현재 JSON 객체 - */ - public JSON setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - return this; - } - - /**JSON 문자열을 파싱하여 주어진 클래스의 객체로 변환한다. - * @param 클래스 유형 - * @param json JSON 문자열 - * @param klass 클래스 - * @return 문자열을 파싱하여 변환한 객체 - */ - public T parse(String json, Class klass) { - try { - return getObjectMapper().readValue(json, klass); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**input의 내용을 파싱하여 주어진 클래스의 객체로 변환한다. - * @param 클래스 유형 - * @param input JSON input - * @param klass 클래스 - * @return input을 파싱하여 변환한 객체 - */ - public T parse(InputStream input, Class klass) { - try { - return getObjectMapper().readValue(input, klass); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**주어진 객체를 JSON 문자열로 변환한다. - * @param obj 객체 - * @return 객체를 변환한 JSON 문자열 - */ - public String stringify(Object obj) { - return stringify(obj, false); - } - - /**주어진 객체를 JSON 문자열로 변환한다. - * @param obj 객체 - * @param indent 띄어쓰기 여부 - *
  • 띄어쓰기를 적용하려면 true
  • - *
  • 그렇지 않으면 false
  • - *
- * @return 객체를 변환한 JSON 문자열 - */ - public String stringify(Object obj, boolean indent) { - try { - return getObjectMapper().writeValueAsString(obj); - } catch (Exception e) { - throw runtimeException(e); - } - } +package cokr.xit.foundation.data; + +import java.io.InputStream; + +import com.fasterxml.jackson.core.JsonParser.Feature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cokr.xit.foundation.AbstractComponent; + +/**JSON 포맷/파싱 유틸리티. + * @author mjkhan + */ +public class JSON extends AbstractComponent { + private ObjectMapper objectMapper; + + /**JSON 포맷/파싱에 사용하는 ObjectMapper를 반환한다. + * @return JSON 포맷/파싱에 사용하는 ObjectMapper + */ + public ObjectMapper getObjectMapper() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + objectMapper.configure(Feature.ALLOW_COMMENTS, true); + } + return objectMapper; + } + + /**JSON 포맷/파싱에 사용하는 ObjectMapper를 설정한다. + * @param objectMapper JSON 포맷/파싱에 사용하는 ObjectMapper + * @return 현재 JSON 객체 + */ + public JSON setObjectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + return this; + } + + /**JSON 문자열을 파싱하여 주어진 클래스의 객체로 변환한다. + * @param 클래스 유형 + * @param json JSON 문자열 + * @param klass 클래스 + * @return 문자열을 파싱하여 변환한 객체 + */ + public T parse(String json, Class klass) { + try { + return getObjectMapper().readValue(json, klass); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**input의 내용을 파싱하여 주어진 클래스의 객체로 변환한다. + * @param 클래스 유형 + * @param input JSON input + * @param klass 클래스 + * @return input을 파싱하여 변환한 객체 + */ + public T parse(InputStream input, Class klass) { + try { + return getObjectMapper().readValue(input, klass); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**주어진 객체를 JSON 문자열로 변환한다. + * @param obj 객체 + * @return 객체를 변환한 JSON 문자열 + */ + public String stringify(Object obj) { + return stringify(obj, false); + } + + /**주어진 객체를 JSON 문자열로 변환한다. + * @param obj 객체 + * @param indent 띄어쓰기 여부 + *
  • 띄어쓰기를 적용하려면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ * @return 객체를 변환한 JSON 문자열 + */ + public String stringify(Object obj, boolean indent) { + try { + return getObjectMapper().writeValueAsString(obj); + } catch (Exception e) { + throw runtimeException(e); + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/ProcType.java b/src/main/java/cokr/xit/foundation/data/ProcType.java index 13980d9..23aa3db 100644 --- a/src/main/java/cokr/xit/foundation/data/ProcType.java +++ b/src/main/java/cokr/xit/foundation/data/ProcType.java @@ -1,44 +1,44 @@ -package cokr.xit.foundation.data; - -import cokr.xit.foundation.Assert; - -/**데이터 작업 유형 - * @author mjkhan - */ -public enum ProcType { - /** 데이터 생성 */ - CREATE("C"), - /** 데이터 수정 */ - UPDATE("U"), - /** 데이터 삭제 */ - DELETE("D"); - - private final String code; - - private ProcType(String code) { - this.code = code; - } - - /**데이터 작업 유형의 코드를 반환한다. - * @return 데이터 작업 유형 코드 - */ - public String getCode() { - return code; - } - - /**코드에 해당하는 ProcType을 반환한다. - * @param code 데이터 작업 유형 코드 - * @return ProcType - */ - public static ProcType codeOf(String code) { - if (Assert.isEmpty(code)) - return null; - - for (ProcType procType: values()) { - if (procType.code.equals(code)) - return procType; - } - - throw new IllegalArgumentException("code: " + code); - } +package cokr.xit.foundation.data; + +import cokr.xit.foundation.Assert; + +/**데이터 작업 유형 + * @author mjkhan + */ +public enum ProcType { + /** 데이터 생성 */ + CREATE("C"), + /** 데이터 수정 */ + UPDATE("U"), + /** 데이터 삭제 */ + DELETE("D"); + + private final String code; + + private ProcType(String code) { + this.code = code; + } + + /**데이터 작업 유형의 코드를 반환한다. + * @return 데이터 작업 유형 코드 + */ + public String getCode() { + return code; + } + + /**코드에 해당하는 ProcType을 반환한다. + * @param code 데이터 작업 유형 코드 + * @return ProcType + */ + public static ProcType codeOf(String code) { + if (Assert.isEmpty(code)) + return null; + + for (ProcType procType: values()) { + if (procType.code.equals(code)) + return procType; + } + + throw new IllegalArgumentException("code: " + code); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/RowValueHandler.java b/src/main/java/cokr/xit/foundation/data/RowValueHandler.java index 54eff7b..b19d5bc 100644 --- a/src/main/java/cokr/xit/foundation/data/RowValueHandler.java +++ b/src/main/java/cokr/xit/foundation/data/RowValueHandler.java @@ -1,54 +1,54 @@ -package cokr.xit.foundation.data; - -import java.io.Reader; -import java.sql.CallableStatement; -import java.sql.Clob; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.apache.ibatis.type.BaseTypeHandler; -import org.apache.ibatis.type.JdbcType; - -import cokr.xit.foundation.Assert; - -public class RowValueHandler extends BaseTypeHandler { - @Override - public void setNonNullParameter(PreparedStatement pstmt, int i, Object parameter, JdbcType jdbcType) throws SQLException { - pstmt.setObject(i, parameter); - } - - @Override - public Object getNullableResult(ResultSet resultSet, String columnName) throws SQLException { - Object obj = resultSet.getObject(columnName); - return obj instanceof Clob ? clobToString(obj) : obj; - } - - @Override - public Object getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { - Object obj = resultSet.getObject(columnIndex); - return obj instanceof Clob ? clobToString(obj) : obj; - } - - @Override - public Object getNullableResult(CallableStatement cstmt, int columnIndex) throws SQLException { - return cstmt.getObject(columnIndex); - } - - private String clobToString(Object obj) { - if (!(obj instanceof Clob)) - return null; - - try { - Clob clob = Clob.class.cast(obj); - Reader reader = clob.getCharacterStream(); - char[] chars = new char[1024]; - StringBuilder buffer = new StringBuilder(); - while ((reader.read(chars)) != -1) - buffer.append(chars); - return buffer.toString(); - } catch (Exception e) { - throw Assert.runtimeException(e); - } - } +package cokr.xit.foundation.data; + +import java.io.Reader; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import cokr.xit.foundation.Assert; + +public class RowValueHandler extends BaseTypeHandler { + @Override + public void setNonNullParameter(PreparedStatement pstmt, int i, Object parameter, JdbcType jdbcType) throws SQLException { + pstmt.setObject(i, parameter); + } + + @Override + public Object getNullableResult(ResultSet resultSet, String columnName) throws SQLException { + Object obj = resultSet.getObject(columnName); + return obj instanceof Clob ? clobToString(obj) : obj; + } + + @Override + public Object getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { + Object obj = resultSet.getObject(columnIndex); + return obj instanceof Clob ? clobToString(obj) : obj; + } + + @Override + public Object getNullableResult(CallableStatement cstmt, int columnIndex) throws SQLException { + return cstmt.getObject(columnIndex); + } + + private String clobToString(Object obj) { + if (!(obj instanceof Clob)) + return null; + + try { + Clob clob = Clob.class.cast(obj); + Reader reader = clob.getCharacterStream(); + char[] chars = new char[1024]; + StringBuilder buffer = new StringBuilder(); + while ((reader.read(chars)) != -1) + buffer.append(chars); + return buffer.toString(); + } catch (Exception e) { + throw Assert.runtimeException(e); + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/StringMap.java b/src/main/java/cokr/xit/foundation/data/StringMap.java index 0419605..f5b8580 100644 --- a/src/main/java/cokr/xit/foundation/data/StringMap.java +++ b/src/main/java/cokr/xit/foundation/data/StringMap.java @@ -1,136 +1,136 @@ -package cokr.xit.foundation.data; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.Collectors; - -/**String 키와 E값의 Map으로 편의성 메소드들을 추가하였다.
- * {@link #caseSensitiveKey(boolean) 키의 대소문자 구분여부}를 지정할 수 있다.
- * 디폴트는 대소문자를 구분하도록 되어있다.. - * @param 값의 타입 - */ -public class StringMap extends LinkedHashMap { - /**objs들을 groupMapper가 반환하는 값을 기준으로 grouping하여 반환한다. - * @param objs 객체 목록 - * @param groupMapper 요소 중 키로 쓰일 값을 반환 - * @return groupMapper가 반환하는 값을 기준으로 grouping하여 만든 결과 Map - */ - public static Map> groupBy(Collection objs, Function groupMapper) { - return objs != null ? - objs.stream().collect(Collectors.groupingBy(groupMapper)) : - Collections.emptyMap(); - } - - private static final long serialVersionUID = 1L; - - private boolean caseSensitiveKey = true; - - /**키의 대소문자 구분여부를 반환한다. - * @return - *
  • 대소문자를 구분하면 true
  • - *
  • 대소문자를 구분하지 않으면 false
  • - *
- */ - public boolean caseSensitiveKey() { - return caseSensitiveKey; - } - - /**키의 대소문자 구분여부를 설정한다. - * @param sensitive - *
  • 대소문자를 구분하려면 true
  • - *
  • 그렇지 않으면 false
  • - *
- * @return StringMap 자신 - */ - public > T caseSensitiveKey(boolean sensitive) { - caseSensitiveKey = sensitive; - return self(); - } - - private String findKey(Object obj) { - String s = (String)obj; - if (caseSensitiveKey) - return s; - for (String key: keySet()) - if (key.equalsIgnoreCase(s)) - return key; - return s; - } - - @Override - public boolean containsKey(Object key) { - return super.containsKey(key) ? true : super.containsKey(findKey(key)); - } - - @Override - public E get(Object key) { - E t = super.get(key); - return t != null ? t : super.get(findKey(key)); - } - - @Override - public E getOrDefault(Object key, E defaultValue) { - return super.getOrDefault(findKey(key), defaultValue); - } - - @Override - public E put(String key, E value) { - return super.put(findKey(key), value); - } - - /**value를 지정하는 key의 값으로 설정한다. - * @param key 키 - * @param value 키로 연결할 값 - * @return StringMap 자신 - */ - public > T set(String key, E value) { - put(key, value); - return self(); - } - - @Override - public E remove(Object key) { - E t = super.remove(key); - return t != null ? t : super.remove(findKey(key)); - } - - @Override - public E computeIfAbsent(String key, Function mappingFunction) { - return super.computeIfAbsent(findKey(key), mappingFunction); - } - - @Override - public E computeIfPresent(String key, BiFunction remappingFunction) { - return super.computeIfPresent(findKey(key), remappingFunction); - } - - @Override - public E merge(String key, E value, BiFunction remappingFunction) { - return super.merge(findKey(key), value, remappingFunction); - } - - @Override - public E putIfAbsent(String key, E value) { - return super.putIfAbsent(findKey(key), value); - } - - /**지정하는 key에 설정된 값이 없으면 value를 설정한다. - * @param key 키 - * @param value 설정할 값 - * @return StringMap 자신 - */ - public > T setIfAbsent(String key, E value) { - putIfAbsent(key, value); - return self(); - } - - @SuppressWarnings("unchecked") - protected > T self() { - return (T)this; - } +package cokr.xit.foundation.data; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +/**String 키와 E값의 Map으로 편의성 메소드들을 추가하였다.
+ * {@link #caseSensitiveKey(boolean) 키의 대소문자 구분여부}를 지정할 수 있다.
+ * 디폴트는 대소문자를 구분하도록 되어있다.. + * @param 값의 타입 + */ +public class StringMap extends LinkedHashMap { + /**objs들을 groupMapper가 반환하는 값을 기준으로 grouping하여 반환한다. + * @param objs 객체 목록 + * @param groupMapper 요소 중 키로 쓰일 값을 반환 + * @return groupMapper가 반환하는 값을 기준으로 grouping하여 만든 결과 Map + */ + public static Map> groupBy(Collection objs, Function groupMapper) { + return objs != null ? + objs.stream().collect(Collectors.groupingBy(groupMapper)) : + Collections.emptyMap(); + } + + private static final long serialVersionUID = 1L; + + private boolean caseSensitiveKey = true; + + /**키의 대소문자 구분여부를 반환한다. + * @return + *
  • 대소문자를 구분하면 true
  • + *
  • 대소문자를 구분하지 않으면 false
  • + *
+ */ + public boolean caseSensitiveKey() { + return caseSensitiveKey; + } + + /**키의 대소문자 구분여부를 설정한다. + * @param sensitive + *
  • 대소문자를 구분하려면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ * @return StringMap 자신 + */ + public > T caseSensitiveKey(boolean sensitive) { + caseSensitiveKey = sensitive; + return self(); + } + + private String findKey(Object obj) { + String s = (String)obj; + if (caseSensitiveKey) + return s; + for (String key: keySet()) + if (key.equalsIgnoreCase(s)) + return key; + return s; + } + + @Override + public boolean containsKey(Object key) { + return super.containsKey(key) ? true : super.containsKey(findKey(key)); + } + + @Override + public E get(Object key) { + E t = super.get(key); + return t != null ? t : super.get(findKey(key)); + } + + @Override + public E getOrDefault(Object key, E defaultValue) { + return super.getOrDefault(findKey(key), defaultValue); + } + + @Override + public E put(String key, E value) { + return super.put(findKey(key), value); + } + + /**value를 지정하는 key의 값으로 설정한다. + * @param key 키 + * @param value 키로 연결할 값 + * @return StringMap 자신 + */ + public > T set(String key, E value) { + put(key, value); + return self(); + } + + @Override + public E remove(Object key) { + E t = super.remove(key); + return t != null ? t : super.remove(findKey(key)); + } + + @Override + public E computeIfAbsent(String key, Function mappingFunction) { + return super.computeIfAbsent(findKey(key), mappingFunction); + } + + @Override + public E computeIfPresent(String key, BiFunction remappingFunction) { + return super.computeIfPresent(findKey(key), remappingFunction); + } + + @Override + public E merge(String key, E value, BiFunction remappingFunction) { + return super.merge(findKey(key), value, remappingFunction); + } + + @Override + public E putIfAbsent(String key, E value) { + return super.putIfAbsent(findKey(key), value); + } + + /**지정하는 key에 설정된 값이 없으면 value를 설정한다. + * @param key 키 + * @param value 설정할 값 + * @return StringMap 자신 + */ + public > T setIfAbsent(String key, E value) { + putIfAbsent(key, value); + return self(); + } + + @SuppressWarnings("unchecked") + protected > T self() { + return (T)this; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/XML.java b/src/main/java/cokr/xit/foundation/data/XML.java index 8ed7682..f0bdfaf 100644 --- a/src/main/java/cokr/xit/foundation/data/XML.java +++ b/src/main/java/cokr/xit/foundation/data/XML.java @@ -1,193 +1,193 @@ -package cokr.xit.foundation.data; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.StringReader; -import java.io.StringWriter; -import java.nio.charset.Charset; -import java.util.HashMap; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; - -import cokr.xit.foundation.AbstractComponent; - -/**XML 포맷/파싱 유틸리티 - * @author mjkhan - */ -public class XML extends AbstractComponent { - private String charset; - private HashMap, Support> xctxes; - - /**새 XML을(를) 생성한다. - * @param charset 문자셋 이름. 지정하지 않으면 시스템 기본 문자셋을 사용한다. - */ - public XML(String charset) { - this.charset = ifEmpty(charset, () -> Charset.defaultCharset().name()); - } - - private Support jaxb(Class klass) throws Exception { - if (xctxes == null) - xctxes = new HashMap<>(); - Support support = xctxes.get(klass); - if (support == null) { - JAXBContext xctx = JAXBContext.newInstance(klass); - xctxes.put(klass, support = new Support(xctx, charset)); - } - return support; - } - - /**xml 문자열을 파싱하여 주어진 클래스의 객체로 변환한다. - * @param 클래스 유형 - * @param str xml 문자열 - * @param klass 클래스 - * @return xml 문자열을 변환하여 만든 객체 - */ - public T parse(String str, Class klass) { - try { - return klass.cast(jaxb(klass).unmarshal(str)); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**xml input의 내용을 파싱하여 주어진 클래스의 객체로 변환한다. - * @param 클래스 유형 - * @param input xml input - * @param klass 클래스 - * @return xml input의 내용을 변환하여 만든 객체 - */ - public T parse(InputStream input, Class klass) { - try { - return klass.cast(jaxb(klass).unmarshal(input)); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**xml 파일의 내용을 파싱하여 주어진 클래스의 객체로 변환한다. - * @param 클래스 유형 - * @param file xml 파일 - * @param klass 클래스 - * @return xml 파일의 내용을 변환하여 만든 객체 - */ - public T parse(File file, Class klass) { - try (FileInputStream input = new FileInputStream(file)) { - return parse(input, klass); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**주어진 객체를 xml 문자열로 변환한다.
- * 이 때 객체는 JAXB(Java Architecture for XML Binding - javax.xml.*)가 제공하는 다음과 같은 어노테이션이 적용되어 있어야 한다. - *
 @XmlRootElement
-	 * @XmlType
-	 * @XmlElement
-	 * @XmlAttribute
-	 * @XmlTransient
-	 * 
- * @param obj 객체 - * @return xml 문자열 - */ - public String stringify(Object obj) { - try { - return jaxb(obj.getClass()).stringify(obj); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**주어진 객체를 xml 포맷으로 out에 저장한다.
- * 이 때 객체는 JAXB(Java Architecture for XML Binding - javax.xml.*)가 제공하는 다음과 같은 어노테이션이 적용되어 있어야 한다. - *
 @XmlRootElement
-	 * @XmlType
-	 * @XmlElement
-	 * @XmlAttribute
-	 * @XmlTransient
-	 * 
- * @param obj 객체 - * @param out OutputStream - */ - public void write(Object obj, OutputStream out) { - try { - jaxb(obj.getClass()).marshal(obj, out); - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**주어진 객체를 xml 포맷으로 파일에 저장한다.
- * 이 때 객체는 JAXB(Java Architecture for XML Binding - javax.xml.*)가 제공하는 다음과 같은 어노테이션이 적용되어 있어야 한다. - *
 {@code @XmlRootElement}
-	 * {@code @XmlType}
-	 * {@code @XmlElement}
-	 * {@code @XmlAttribute}
-	 * {@code @XmlTransient}
- * @param obj 객체 - * @param file 파일 - */ - public void write(Object obj, File file) { - try (FileOutputStream out = new FileOutputStream(file)) { - write(obj, out); - } catch (Exception e) { - throw runtimeException(e); - } - } - - private static class Support { - String charset; - JAXBContext xctx; - Marshaller marshaller; - Unmarshaller unmarshaller; - - Support(JAXBContext xctx, String charset) { - this.xctx = xctx; - this.charset = charset; - } - - Object unmarshal(String input) throws Exception { - if (unmarshaller == null) { - unmarshaller = xctx.createUnmarshaller(); - } - try (StringReader reader = new StringReader(input)) { - return unmarshaller.unmarshal(reader); - } catch (Exception e) { - throw e; - } - } - - Object unmarshal(InputStream input) throws Exception { - if (unmarshaller == null) { - unmarshaller = xctx.createUnmarshaller(); - } - return unmarshaller.unmarshal(input); - } - - Marshaller marshaller() throws Exception { - if (marshaller == null) { - marshaller = xctx.createMarshaller(); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); - marshaller.setProperty(Marshaller.JAXB_ENCODING, charset); - } - return marshaller; - } - - String stringify(Object obj) throws Exception { - try (StringWriter writer = new StringWriter()) { - marshaller().marshal(obj, writer); - return writer.toString(); - } catch (Exception e) { - throw e; - } - } - - void marshal(Object obj, OutputStream out) throws Exception { - marshaller().marshal(obj, out); - } - } +package cokr.xit.foundation.data; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.HashMap; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import cokr.xit.foundation.AbstractComponent; + +/**XML 포맷/파싱 유틸리티 + * @author mjkhan + */ +public class XML extends AbstractComponent { + private String charset; + private HashMap, Support> xctxes; + + /**새 XML을(를) 생성한다. + * @param charset 문자셋 이름. 지정하지 않으면 시스템 기본 문자셋을 사용한다. + */ + public XML(String charset) { + this.charset = ifEmpty(charset, () -> Charset.defaultCharset().name()); + } + + private Support jaxb(Class klass) throws Exception { + if (xctxes == null) + xctxes = new HashMap<>(); + Support support = xctxes.get(klass); + if (support == null) { + JAXBContext xctx = JAXBContext.newInstance(klass); + xctxes.put(klass, support = new Support(xctx, charset)); + } + return support; + } + + /**xml 문자열을 파싱하여 주어진 클래스의 객체로 변환한다. + * @param 클래스 유형 + * @param str xml 문자열 + * @param klass 클래스 + * @return xml 문자열을 변환하여 만든 객체 + */ + public T parse(String str, Class klass) { + try { + return klass.cast(jaxb(klass).unmarshal(str)); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**xml input의 내용을 파싱하여 주어진 클래스의 객체로 변환한다. + * @param 클래스 유형 + * @param input xml input + * @param klass 클래스 + * @return xml input의 내용을 변환하여 만든 객체 + */ + public T parse(InputStream input, Class klass) { + try { + return klass.cast(jaxb(klass).unmarshal(input)); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**xml 파일의 내용을 파싱하여 주어진 클래스의 객체로 변환한다. + * @param 클래스 유형 + * @param file xml 파일 + * @param klass 클래스 + * @return xml 파일의 내용을 변환하여 만든 객체 + */ + public T parse(File file, Class klass) { + try (FileInputStream input = new FileInputStream(file)) { + return parse(input, klass); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**주어진 객체를 xml 문자열로 변환한다.
+ * 이 때 객체는 JAXB(Java Architecture for XML Binding - javax.xml.*)가 제공하는 다음과 같은 어노테이션이 적용되어 있어야 한다. + *
 @XmlRootElement
+	 * @XmlType
+	 * @XmlElement
+	 * @XmlAttribute
+	 * @XmlTransient
+	 * 
+ * @param obj 객체 + * @return xml 문자열 + */ + public String stringify(Object obj) { + try { + return jaxb(obj.getClass()).stringify(obj); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**주어진 객체를 xml 포맷으로 out에 저장한다.
+ * 이 때 객체는 JAXB(Java Architecture for XML Binding - javax.xml.*)가 제공하는 다음과 같은 어노테이션이 적용되어 있어야 한다. + *
 @XmlRootElement
+	 * @XmlType
+	 * @XmlElement
+	 * @XmlAttribute
+	 * @XmlTransient
+	 * 
+ * @param obj 객체 + * @param out OutputStream + */ + public void write(Object obj, OutputStream out) { + try { + jaxb(obj.getClass()).marshal(obj, out); + } catch (Exception e) { + throw runtimeException(e); + } + } + + /**주어진 객체를 xml 포맷으로 파일에 저장한다.
+ * 이 때 객체는 JAXB(Java Architecture for XML Binding - javax.xml.*)가 제공하는 다음과 같은 어노테이션이 적용되어 있어야 한다. + *
 {@code @XmlRootElement}
+	 * {@code @XmlType}
+	 * {@code @XmlElement}
+	 * {@code @XmlAttribute}
+	 * {@code @XmlTransient}
+ * @param obj 객체 + * @param file 파일 + */ + public void write(Object obj, File file) { + try (FileOutputStream out = new FileOutputStream(file)) { + write(obj, out); + } catch (Exception e) { + throw runtimeException(e); + } + } + + private static class Support { + String charset; + JAXBContext xctx; + Marshaller marshaller; + Unmarshaller unmarshaller; + + Support(JAXBContext xctx, String charset) { + this.xctx = xctx; + this.charset = charset; + } + + Object unmarshal(String input) throws Exception { + if (unmarshaller == null) { + unmarshaller = xctx.createUnmarshaller(); + } + try (StringReader reader = new StringReader(input)) { + return unmarshaller.unmarshal(reader); + } catch (Exception e) { + throw e; + } + } + + Object unmarshal(InputStream input) throws Exception { + if (unmarshaller == null) { + unmarshaller = xctx.createUnmarshaller(); + } + return unmarshaller.unmarshal(input); + } + + Marshaller marshaller() throws Exception { + if (marshaller == null) { + marshaller = xctx.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, charset); + } + return marshaller; + } + + String stringify(Object obj) throws Exception { + try (StringWriter writer = new StringWriter()) { + marshaller().marshal(obj, writer); + return writer.toString(); + } catch (Exception e) { + throw e; + } + } + + void marshal(Object obj, OutputStream out) throws Exception { + marshaller().marshal(obj, out); + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/hierarchy/Hierarchy.java b/src/main/java/cokr/xit/foundation/data/hierarchy/Hierarchy.java index 9f90da1..4c50705 100644 --- a/src/main/java/cokr/xit/foundation/data/hierarchy/Hierarchy.java +++ b/src/main/java/cokr/xit/foundation/data/hierarchy/Hierarchy.java @@ -1,107 +1,107 @@ -package cokr.xit.foundation.data.hierarchy; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import cokr.xit.foundation.AbstractComponent; - -/**계층구조를 이루는 요소들을 모아놓은 것으로 {@link HierarchyBuilder}로 생성한다.
- * Hierarchy는 - *
  • {@link #getElements() 계층구조의 구성요소}
  • - *
  • {@link #getIndex() 구성요소들의 인덱스}
  • - *
  • {@link #topElements() 최상위 구성요소들}
  • - *
  • {@link #getTop() 루트 요소}
  • - *
  • {@link #get(String) 특정 구성요소}
  • - *
- * 등을 제공한다. - * @param 구성요소 유형 - */ -public class Hierarchy extends AbstractComponent implements Serializable { - private static final long serialVersionUID = 1L; - - /**주어진 계층 구성요소들을 문자열로 반환한다. - * @param objs 문자열로 변환할 계층 구성요소 - * @param getChildren 특정요소의 하위요소들을 반환하는 function - * @return 계층 구성요소들의 문자열 표현 - */ - public static String toString(Iterable objs, Function> getChildren) { - return new Stringify() - .beginElement((e, level, index) -> { - String str = index == 0 && level == 0 ? "" : "\n"; - if (level > 0) - str += Stringify.indent(" ", level) + "+-"; - return str += e; - }) - .getChildren(getChildren) - .get(objs); - } - - private Collection elements; - private List tops; - private Map index; - - /**Hierarchy가 비어있는지 반환한다. - * @return - *
  • Hierarchy가 비어있으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean isEmpty() { - return isEmpty(index) || isEmpty(elements); - } - - /**Hierarchy 구성요소들의 인덱스를 반환한다. - * @return Hierarchy 구성요소들의 인덱스 - */ - public Map getIndex() { - return ifEmpty(index, () -> Collections.emptyMap()); - } - - Hierarchy setIndex(Map index) { - this.index = index; - return this; - } - - /**Hierarchy의 구성요소들을 반환한다. - * @return Hierarchy의 구성요소 - */ - public Collection getElements() { - return elements; - } - - Hierarchy setElements(Collection elements) { - this.elements = elements; - return this; - } - - /**Hierarchy의 최상위 요소들을 반환한다. - * @return Hierarchy의 최상위 요소들 - */ - public List topElements() { - return ifEmpty(tops, () -> Collections.emptyList()); - } - - /**Hierarchy의 루트요소를 반환한다. - * @return Hierarchy의 루트요소 - */ - public T getTop() { - return !isEmpty() ? tops.iterator().next() : null; - } - - Hierarchy setTops(List tops) { - this.tops = tops; - return this; - } - - /**지정하는 아이디의 구성요소를 반환한다. - * @param elementID 구성요소의 아이디 - * @return 지정하는 아이디의 구성요소 - */ - public T get(String elementID) { - return !isEmpty() ? index.get(elementID) : null; - } +package cokr.xit.foundation.data.hierarchy; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import cokr.xit.foundation.AbstractComponent; + +/**계층구조를 이루는 요소들을 모아놓은 것으로 {@link HierarchyBuilder}로 생성한다.
+ * Hierarchy는 + *
  • {@link #getElements() 계층구조의 구성요소}
  • + *
  • {@link #getIndex() 구성요소들의 인덱스}
  • + *
  • {@link #topElements() 최상위 구성요소들}
  • + *
  • {@link #getTop() 루트 요소}
  • + *
  • {@link #get(String) 특정 구성요소}
  • + *
+ * 등을 제공한다. + * @param 구성요소 유형 + */ +public class Hierarchy extends AbstractComponent implements Serializable { + private static final long serialVersionUID = 1L; + + /**주어진 계층 구성요소들을 문자열로 반환한다. + * @param objs 문자열로 변환할 계층 구성요소 + * @param getChildren 특정요소의 하위요소들을 반환하는 function + * @return 계층 구성요소들의 문자열 표현 + */ + public static String toString(Iterable objs, Function> getChildren) { + return new Stringify() + .beginElement((e, level, index) -> { + String str = index == 0 && level == 0 ? "" : "\n"; + if (level > 0) + str += Stringify.indent(" ", level) + "+-"; + return str += e; + }) + .getChildren(getChildren) + .get(objs); + } + + private Collection elements; + private List tops; + private Map index; + + /**Hierarchy가 비어있는지 반환한다. + * @return + *
  • Hierarchy가 비어있으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isEmpty() { + return isEmpty(index) || isEmpty(elements); + } + + /**Hierarchy 구성요소들의 인덱스를 반환한다. + * @return Hierarchy 구성요소들의 인덱스 + */ + public Map getIndex() { + return ifEmpty(index, () -> Collections.emptyMap()); + } + + Hierarchy setIndex(Map index) { + this.index = index; + return this; + } + + /**Hierarchy의 구성요소들을 반환한다. + * @return Hierarchy의 구성요소 + */ + public Collection getElements() { + return elements; + } + + Hierarchy setElements(Collection elements) { + this.elements = elements; + return this; + } + + /**Hierarchy의 최상위 요소들을 반환한다. + * @return Hierarchy의 최상위 요소들 + */ + public List topElements() { + return ifEmpty(tops, () -> Collections.emptyList()); + } + + /**Hierarchy의 루트요소를 반환한다. + * @return Hierarchy의 루트요소 + */ + public T getTop() { + return !isEmpty() ? tops.iterator().next() : null; + } + + Hierarchy setTops(List tops) { + this.tops = tops; + return this; + } + + /**지정하는 아이디의 구성요소를 반환한다. + * @param elementID 구성요소의 아이디 + * @return 지정하는 아이디의 구성요소 + */ + public T get(String elementID) { + return !isEmpty() ? index.get(elementID) : null; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/hierarchy/HierarchyBuilder.java b/src/main/java/cokr/xit/foundation/data/hierarchy/HierarchyBuilder.java index 94581d7..da22f0d 100644 --- a/src/main/java/cokr/xit/foundation/data/hierarchy/HierarchyBuilder.java +++ b/src/main/java/cokr/xit/foundation/data/hierarchy/HierarchyBuilder.java @@ -1,278 +1,278 @@ -package cokr.xit.foundation.data.hierarchy; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; - -/**Hierarchy를 생성한다. - *

HierarchyBuilder가 Hierarchy를 생성하려면 - *

  • 계층구조를 이루는 {@link #setElements(Collection) 구성요소들을 설정}하고
  • - *
  • {@link #setInstruction(Instruction) Instruction}을 설정해서 계층구조를 만드는 정보를 제공해야 한다.
  • - *
- * {@link Instruction}은 HierarchyBuilder가 Hierarchy를 만들 때 사용하는 일련의 정보를 제공한다. - * - *

구성요소들과 Instruction이 설정되면 HierarchyBuilder는 {@link #build() 계층구조를 생성}할 수 있다.
- * HierarchyBuilder는 디폴트로 {@link Hierarchy} 객체를 반환한다.
- * Hierarchy의 상속 클래스를 받으려면 {@link #build(Supplier)} 메소드를 사용한다.
- *

- *

다음은 HierarchyBuilder가 객체의 계층구조를 생성하는 예다. - *

{@code Collection elements = ...;
- * HierarchyBuilder.Instruction instruction = new HierarchyBuilder.Instruction<>()
- *     .getID(e -> e.getId())
- *     .getParentID(e -> e.getParentId())
- *     .addChild((parent, child) -> parent.add(child))
- *     .atTop(e -> e.getParentId() == null || e.getId().equals(e.getParentId()));
- * HierarchyBuilder builder = new HierarchyBuilder<>()
- *     .setInstruction(instruction);
- *     .setElements(elements);
- * Hierarchy hierarchy = builder.build();
- * List tops = hierarchy.topElements();
- * MyObject root = hierarchy.getTop();}
- * 위 코드는 다음처럼 간단히 작성할 수 있다: - *
{@code ...
- * Hierarchy hierarchy = new HierarchyBuilder()
- *     .getID(e -> e.getId())
- *     .getParentID(e -> e.getParentId())
- *     .addChild((parent, child) -> parent.add(child))
- *     .atTop(e -> e.getParentId() == null || e.getId().equals(e.getParentId()))
- *     .setElements(elements)
- *     .build();
- * ...}
- * - */ -public class HierarchyBuilder { - private Collection elements; - private HashMap index; - private Instruction instruction; - - /**계층구조를 이루는 구성요소들을 설정한다. - * @param elements 계층구조를 이루는 구성요소 - * @return HierarchyBuilder - */ - public HierarchyBuilder setElements(Collection elements) { - if (elements == null) - throw new IllegalArgumentException("elements: null"); - this.elements = elements; - createIndex(); - return this; - } - - private Instruction instruction() { - return instruction != null ? instruction : (instruction = new Instruction<>()); - } - - /**계층구조를 생성할 때 사용할 Instruction을 설정한다. - * @param instruction 계층구조를 생성할 때 사용할 Instruction - * @return HierarchyBuilder - */ - public HierarchyBuilder setInstruction(Instruction instruction) { - this.instruction = instruction; - return this; - } - - /**{@link Instruction#atTop(Predicate)} 참고. - * @return HierarchyBuilder - */ - public HierarchyBuilder atTop(Predicate test) { - instruction().atTop(test); - return this; - } - - /**{@link Instruction#getID(Function)} 참고. - * @return HierarchyBuilder - */ - public HierarchyBuilder getID(Function func) { - instruction().getID(func); - return this; - } - - /**{@link Instruction#getParentID(Function)} 참고. - * @return HierarchyBuilder - */ - public HierarchyBuilder getParentID(Function func) { - instruction().getParentID(func); - return this; - } - - /**{@link Instruction#addChild(BiConsumer)} 참고 - * @return HierarchyBuilder - */ - public HierarchyBuilder addChild(BiConsumer func) { - instruction().addChild(func); - return this; - } - - private void createIndex() { - index = new HashMap(); - for (T e: elements) - index.put(instruction.getID.apply(e), e); - } - - /**계층의 구성요소들의 인덱스를 반환한다. - * @return 계층의 구성요소들의 인덱스 - */ - public Map getIndex() { - return index; - } - - private void add(T parent, T child) { - if (parent == null || child == null || parent == child) return; - - instruction.addChild.accept(parent, child); - } - - /**계층구조를 생성하고 루트요소를 반환한다. - * @param rootID 루트요소의 ID - * @return 루트요소 - */ - public T get(String rootID) { - if (elements == null) return null; - - T root = index.get(rootID); - if (root == null) - throw new RuntimeException("Root element not found: " + rootID); - - for (T element: elements) { - if (element == null) continue; - String parentID = instruction.getParentID.apply(element); - T parent = parentID == null ? null : index.get(parentID); - add(parent, element); - } - return root; - } - - /**계층구조를 생성하고 최상위 요소들을 반환한다. - * @return 최상위 요소 - */ - public List get() { - if (elements == null) return Collections.emptyList(); - - ArrayList tops = new ArrayList(); - for (T e: elements) { - boolean top = instruction.atTop != null && instruction.atTop.test(e); - if (top) { - if (!tops.contains(e)) - tops.add(e); - } else { - String parentID = instruction.getParentID.apply(e); - T parent = parentID != null ? index.get(parentID) : null; - if (parent == null || parent.equals(e)) { - if (!tops.contains(e)) - tops.add(e); - } else { - add(parent, e); - } - } - } - return tops; - } - - /**Hierarchy를 생성해서 반환한다. - * @return Hierarchy - */ - public Hierarchy build() { - return build(Hierarchy::new); - } - - /**Hierarchy 상속 클래스의 객체를 생성해서 반환한다.
- * The Hierarchy is an instance of the class that the factory supplies. - * @param factory Hierarchy 객체를 반환하는 함수 - * @return Hierarchy - */ - public > H build(Supplier factory) { - List tops = get(); - H h = factory.get(); - h.setElements(elements).setIndex(index).setTops(tops); - return h; - } - - /**HierarchyBuilder를 초기화 한다. - */ - public void clear() { - if (index != null) - index.clear(); - if (instruction != null) - instruction.clear(); - elements = null; - } - - /**HierarchyBuilder가 Hierarchy를 생성할 때 사용하는 정보로 - *
  • {@link #getID(Function) 구성요소의 ID}
  • - *
  • {@link #getParentID(Function) 상위요소의 ID}
  • - *
  • {@link #addChild(BiConsumer) 구성요소를 상위요소에 추가}하는 방법
  • - *
  • {@link #atTop(Predicate) 구성요소가 최상위 요소인지 판단}하는 방법
  • - *
- * 등을 명시한다. - * @param 구성요소 유형 - */ - public static class Instruction { - private Function - getID, - getParentID; - private BiConsumer addChild; - private Predicate atTop; - - /**새 Instruction을 생성한다. - */ - public Instruction() { - clear(); - } - - /**구성요소의 ID를 반환하는 함수를 설정한다.
- * ID는 구성요소의 실제 ID일 수도, 아닐 수도 있다.
- * HierarchyBuilder가 구성요소를 식별할 수만 있으면 된다.
- * @param func 구성요소의 ID를 반환하는 함수 - * @return Instruction - */ - public Instruction getID(Function func) { - this.getID = func; - return this; - } - - /**상위 구성요소의 ID를 반환하는 함수를 설정한다. - * @param func 상위 구성요소의 ID를 반환하는 함수 - * @return Instruction - */ - public Instruction getParentID(Function func) { - this.getParentID = func; - return this; - } - - /**구성요소를 상위요소에 추가하는 함수를 설정한다. - * @param consumer 구성요소를 상위요소에 추가하는 함수로, 첫번째 인자가 상위요소, 두번째 인자가 하위요소다. - * @return Instruction - */ - public Instruction addChild(BiConsumer consumer) { - this.addChild = consumer; - return this; - } - - /**구성요소가 최상위 요소인지 판단하는 함수를 설정한다.
- * HierarchyBuilder는 이 함수의 결과가 true이거나 상위요소를 찾지못한 요소를 최상위 요소로 간주한다. - * @param test 구성요소가 최상위 요소인지 판단하는 함수 - * @return Instruction - */ - public Instruction atTop(Predicate test) { - this.atTop = test; - return this; - } - - /**Instruction을 초기화한다. - * @return Instruction - */ - public Instruction clear() { - getID = (t) -> ""; - getParentID = (t) -> ""; - addChild = (t1, t2) -> {}; - atTop = null; - return this; - } - } +package cokr.xit.foundation.data.hierarchy; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/**Hierarchy를 생성한다. + *

HierarchyBuilder가 Hierarchy를 생성하려면 + *

  • 계층구조를 이루는 {@link #setElements(Collection) 구성요소들을 설정}하고
  • + *
  • {@link #setInstruction(Instruction) Instruction}을 설정해서 계층구조를 만드는 정보를 제공해야 한다.
  • + *
+ * {@link Instruction}은 HierarchyBuilder가 Hierarchy를 만들 때 사용하는 일련의 정보를 제공한다. + * + *

구성요소들과 Instruction이 설정되면 HierarchyBuilder는 {@link #build() 계층구조를 생성}할 수 있다.
+ * HierarchyBuilder는 디폴트로 {@link Hierarchy} 객체를 반환한다.
+ * Hierarchy의 상속 클래스를 받으려면 {@link #build(Supplier)} 메소드를 사용한다.
+ *

+ *

다음은 HierarchyBuilder가 객체의 계층구조를 생성하는 예다. + *

{@code Collection elements = ...;
+ * HierarchyBuilder.Instruction instruction = new HierarchyBuilder.Instruction<>()
+ *     .getID(e -> e.getId())
+ *     .getParentID(e -> e.getParentId())
+ *     .addChild((parent, child) -> parent.add(child))
+ *     .atTop(e -> e.getParentId() == null || e.getId().equals(e.getParentId()));
+ * HierarchyBuilder builder = new HierarchyBuilder<>()
+ *     .setInstruction(instruction);
+ *     .setElements(elements);
+ * Hierarchy hierarchy = builder.build();
+ * List tops = hierarchy.topElements();
+ * MyObject root = hierarchy.getTop();}
+ * 위 코드는 다음처럼 간단히 작성할 수 있다: + *
{@code ...
+ * Hierarchy hierarchy = new HierarchyBuilder()
+ *     .getID(e -> e.getId())
+ *     .getParentID(e -> e.getParentId())
+ *     .addChild((parent, child) -> parent.add(child))
+ *     .atTop(e -> e.getParentId() == null || e.getId().equals(e.getParentId()))
+ *     .setElements(elements)
+ *     .build();
+ * ...}
+ * + */ +public class HierarchyBuilder { + private Collection elements; + private HashMap index; + private Instruction instruction; + + /**계층구조를 이루는 구성요소들을 설정한다. + * @param elements 계층구조를 이루는 구성요소 + * @return HierarchyBuilder + */ + public HierarchyBuilder setElements(Collection elements) { + if (elements == null) + throw new IllegalArgumentException("elements: null"); + this.elements = elements; + createIndex(); + return this; + } + + private Instruction instruction() { + return instruction != null ? instruction : (instruction = new Instruction<>()); + } + + /**계층구조를 생성할 때 사용할 Instruction을 설정한다. + * @param instruction 계층구조를 생성할 때 사용할 Instruction + * @return HierarchyBuilder + */ + public HierarchyBuilder setInstruction(Instruction instruction) { + this.instruction = instruction; + return this; + } + + /**{@link Instruction#atTop(Predicate)} 참고. + * @return HierarchyBuilder + */ + public HierarchyBuilder atTop(Predicate test) { + instruction().atTop(test); + return this; + } + + /**{@link Instruction#getID(Function)} 참고. + * @return HierarchyBuilder + */ + public HierarchyBuilder getID(Function func) { + instruction().getID(func); + return this; + } + + /**{@link Instruction#getParentID(Function)} 참고. + * @return HierarchyBuilder + */ + public HierarchyBuilder getParentID(Function func) { + instruction().getParentID(func); + return this; + } + + /**{@link Instruction#addChild(BiConsumer)} 참고 + * @return HierarchyBuilder + */ + public HierarchyBuilder addChild(BiConsumer func) { + instruction().addChild(func); + return this; + } + + private void createIndex() { + index = new HashMap(); + for (T e: elements) + index.put(instruction.getID.apply(e), e); + } + + /**계층의 구성요소들의 인덱스를 반환한다. + * @return 계층의 구성요소들의 인덱스 + */ + public Map getIndex() { + return index; + } + + private void add(T parent, T child) { + if (parent == null || child == null || parent == child) return; + + instruction.addChild.accept(parent, child); + } + + /**계층구조를 생성하고 루트요소를 반환한다. + * @param rootID 루트요소의 ID + * @return 루트요소 + */ + public T get(String rootID) { + if (elements == null) return null; + + T root = index.get(rootID); + if (root == null) + throw new RuntimeException("Root element not found: " + rootID); + + for (T element: elements) { + if (element == null) continue; + String parentID = instruction.getParentID.apply(element); + T parent = parentID == null ? null : index.get(parentID); + add(parent, element); + } + return root; + } + + /**계층구조를 생성하고 최상위 요소들을 반환한다. + * @return 최상위 요소 + */ + public List get() { + if (elements == null) return Collections.emptyList(); + + ArrayList tops = new ArrayList(); + for (T e: elements) { + boolean top = instruction.atTop != null && instruction.atTop.test(e); + if (top) { + if (!tops.contains(e)) + tops.add(e); + } else { + String parentID = instruction.getParentID.apply(e); + T parent = parentID != null ? index.get(parentID) : null; + if (parent == null || parent.equals(e)) { + if (!tops.contains(e)) + tops.add(e); + } else { + add(parent, e); + } + } + } + return tops; + } + + /**Hierarchy를 생성해서 반환한다. + * @return Hierarchy + */ + public Hierarchy build() { + return build(Hierarchy::new); + } + + /**Hierarchy 상속 클래스의 객체를 생성해서 반환한다.
+ * The Hierarchy is an instance of the class that the factory supplies. + * @param factory Hierarchy 객체를 반환하는 함수 + * @return Hierarchy + */ + public > H build(Supplier factory) { + List tops = get(); + H h = factory.get(); + h.setElements(elements).setIndex(index).setTops(tops); + return h; + } + + /**HierarchyBuilder를 초기화 한다. + */ + public void clear() { + if (index != null) + index.clear(); + if (instruction != null) + instruction.clear(); + elements = null; + } + + /**HierarchyBuilder가 Hierarchy를 생성할 때 사용하는 정보로 + *
  • {@link #getID(Function) 구성요소의 ID}
  • + *
  • {@link #getParentID(Function) 상위요소의 ID}
  • + *
  • {@link #addChild(BiConsumer) 구성요소를 상위요소에 추가}하는 방법
  • + *
  • {@link #atTop(Predicate) 구성요소가 최상위 요소인지 판단}하는 방법
  • + *
+ * 등을 명시한다. + * @param 구성요소 유형 + */ + public static class Instruction { + private Function + getID, + getParentID; + private BiConsumer addChild; + private Predicate atTop; + + /**새 Instruction을 생성한다. + */ + public Instruction() { + clear(); + } + + /**구성요소의 ID를 반환하는 함수를 설정한다.
+ * ID는 구성요소의 실제 ID일 수도, 아닐 수도 있다.
+ * HierarchyBuilder가 구성요소를 식별할 수만 있으면 된다.
+ * @param func 구성요소의 ID를 반환하는 함수 + * @return Instruction + */ + public Instruction getID(Function func) { + this.getID = func; + return this; + } + + /**상위 구성요소의 ID를 반환하는 함수를 설정한다. + * @param func 상위 구성요소의 ID를 반환하는 함수 + * @return Instruction + */ + public Instruction getParentID(Function func) { + this.getParentID = func; + return this; + } + + /**구성요소를 상위요소에 추가하는 함수를 설정한다. + * @param consumer 구성요소를 상위요소에 추가하는 함수로, 첫번째 인자가 상위요소, 두번째 인자가 하위요소다. + * @return Instruction + */ + public Instruction addChild(BiConsumer consumer) { + this.addChild = consumer; + return this; + } + + /**구성요소가 최상위 요소인지 판단하는 함수를 설정한다.
+ * HierarchyBuilder는 이 함수의 결과가 true이거나 상위요소를 찾지못한 요소를 최상위 요소로 간주한다. + * @param test 구성요소가 최상위 요소인지 판단하는 함수 + * @return Instruction + */ + public Instruction atTop(Predicate test) { + this.atTop = test; + return this; + } + + /**Instruction을 초기화한다. + * @return Instruction + */ + public Instruction clear() { + getID = (t) -> ""; + getParentID = (t) -> ""; + addChild = (t1, t2) -> {}; + atTop = null; + return this; + } + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/hierarchy/Stringify.java b/src/main/java/cokr/xit/foundation/data/hierarchy/Stringify.java index 9ff94bf..c4b3415 100644 --- a/src/main/java/cokr/xit/foundation/data/hierarchy/Stringify.java +++ b/src/main/java/cokr/xit/foundation/data/hierarchy/Stringify.java @@ -1,235 +1,235 @@ -package cokr.xit.foundation.data.hierarchy; - -import java.util.Collections; -import java.util.function.Function; - -import cokr.xit.foundation.AbstractComponent; - -/**Utility to convert objects of a hierarchy to string representation. - *

For a Stringify to convert objects of a hierarchy, you should - *

  • Set an {@link #setInstruction(Instruction) Instruction} for steps to convert the objects
  • - *
  • Call {@link #get(Iterable)} with the objects as an argument
  • - *
- *

- *

An {@link Instruction} is a set of steps for a Stringify to perform the conversion.
- * For convenience, a Stringify has methods that sets the steps to an internal instruction. - *

- */ -class Stringify extends AbstractComponent { - /**A functional interface that converts an object to a string - * @param an object type - */ - @FunctionalInterface - public interface ToString { - /**Converts t to a string. - * @param t an object - * @param level 0-based level or depth of t in a hierarchy - * @param index 0-based index of t - * @return string representation of t - */ - String get(T t, int level, int index); - } - - private Instruction instruction; - - /**Returns indentation with s appended n times. - * @param s indentation string - * @param n number of times to append indentation string - * @return indentation string appended n time - */ - public static String indent(String s, int n) { - String result = ""; - for (int i = 0; i < n; ++i) { - result += s; - } - return result; - } - - private Instruction instruction() { - return instruction != null ? instruction : (instruction = new Instruction<>()); - } - - /**Sets an instruction to convert objects to string representation. - * @param instruction instruction to convert objects to string representation - * @return this Stringify - */ - public Stringify setInstruction(Instruction instruction) { - this.instruction = instruction; - return this; - } - - /**See {@link Instruction#beginElement(ToString)} - * @return this Stringify - */ - public Stringify beginElement(ToString func) { - instruction().beginElement(func); - return this; - } - - /**See {@link Instruction#endElement(ToString)} - * @return this Stringify - */ - public Stringify endElement(ToString func) { - instruction().endElement(func); - return this; - } - - /**See {@link Instruction#beginChildren(ToString)} - * @return this Stringify - */ - public Stringify beginChildren(ToString func) { - instruction().beginChildren(func); - return this; - } - - /**See {@link Instruction#endChildren(ToString)} - * @return this Stringify - */ - public Stringify endChildren(ToString func) { - instruction().endChildren(func); - return this; - } - - /**See {@link Instruction#getChildren(Function)} - * @return this Stringify - */ - public Stringify getChildren(Function> func) { - instruction().getChildren(func); - return this; - } - - /**Converts elements to string representation. - * @param elements objects of a hierarchy - * @return string representation of the elements - */ - public String get(Iterable elements) { - if (isEmpty(elements)) return ""; - - StringBuilder buff = new StringBuilder(); - toString(elements, buff, 0); - return buff.toString(); - } - - private void toString(Iterable elements, StringBuilder buff, int level) { - Instruction instr = instruction(); - int index = 0; - - for (T e: elements) { - buff.append(instr.beginElement().get(e, level, index)); - - Iterable children = instr.getChildren().apply(e); - int sublevel = level + 1; - if (!isEmpty(children)) { - buff.append(instr.beginChildren().get(e, sublevel, index)); - toString(children, buff, sublevel); - buff.append(instr.endChildren().get(e, sublevel, index)); - } - buff.append(instr.endElement().get(e, level, index)); - ++index; - } - } - - /**Is a set of steps to convert objects of a hierarchy to string representation. - *

With an Instruction, you specify how to - *

  • {@link #beginElement(ToString) Mark the beginning of an element} with a string
  • - *
  • {@link #endElement(ToString) Mark the end of an element} with a string
  • - *
  • {@link #getChildren(Function) Get child elements} of an element
  • - *
  • {@link #beginChildren(ToString) Mark the beginning of child elements} with a string
  • - *
  • {@link #endChildren(ToString) Mark the end of child elements} with a string
  • - *
- *

- * @param an object type - */ - public static class Instruction { - private ToString - beginElement, - endElement, - beginChildren, - endChildren; - private Function> getChildren; - - /**Creates a new Instruction. - */ - public Instruction() { - clear(); - } - - ToString beginElement() { - return beginElement; - } - - /**Sets a function that returns a string to mark the beginning of an element. - * @param func function that returns a string to mark the beginning of an element - * @return this Instruction - */ - public Instruction beginElement(ToString func) { - this.beginElement = func; - return this; - } - - ToString endElement() { - return endElement; - } - - /**Sets a function that returns a string to mark the end of an element. - * @param func function that returns a string to mark the end of an element - * @return this Instruction - */ - public Instruction endElement(ToString func) { - this.endElement = func; - return this; - } - - ToString beginChildren() { - return beginChildren; - } - - /**Sets a function that returns a string to mark the beginning of child elements. - * @param func function that returns a string to mark the beginning of child elements - * @return this Instruction - */ - public Instruction beginChildren(ToString func) { - this.beginChildren = func; - return this; - } - - ToString endChildren() { - return endChildren; - } - - /**Sets a function that returns a string to mark the end of child elements. - * @param func function that returns a string to mark the end of child elements - * @return this Instruction - */ - public Instruction endChildren(ToString func) { - this.endChildren = func; - return this; - } - - Function> getChildren() { - return getChildren; - } - - /**Sets a function that returns child elements of the current element. - * @param getChildren function that returns child elements of the current element - * @return this Instruction - */ - public Instruction getChildren(Function> getChildren) { - this.getChildren = getChildren; - return this; - } - - /**Clears the Instruction to the initial state. - * @return this Instruction - */ - public Instruction clear() { - ToString empty = (e, level, index) -> ""; - beginElement = empty; - endElement = empty; - beginChildren = empty; - endChildren = empty; - getChildren = t -> Collections.emptyList(); - return this; - } - } -} +package cokr.xit.foundation.data.hierarchy; + +import java.util.Collections; +import java.util.function.Function; + +import cokr.xit.foundation.AbstractComponent; + +/**Utility to convert objects of a hierarchy to string representation. + *

For a Stringify to convert objects of a hierarchy, you should + *

  • Set an {@link #setInstruction(Instruction) Instruction} for steps to convert the objects
  • + *
  • Call {@link #get(Iterable)} with the objects as an argument
  • + *
+ *

+ *

An {@link Instruction} is a set of steps for a Stringify to perform the conversion.
+ * For convenience, a Stringify has methods that sets the steps to an internal instruction. + *

+ */ +class Stringify extends AbstractComponent { + /**A functional interface that converts an object to a string + * @param an object type + */ + @FunctionalInterface + public interface ToString { + /**Converts t to a string. + * @param t an object + * @param level 0-based level or depth of t in a hierarchy + * @param index 0-based index of t + * @return string representation of t + */ + String get(T t, int level, int index); + } + + private Instruction instruction; + + /**Returns indentation with s appended n times. + * @param s indentation string + * @param n number of times to append indentation string + * @return indentation string appended n time + */ + public static String indent(String s, int n) { + String result = ""; + for (int i = 0; i < n; ++i) { + result += s; + } + return result; + } + + private Instruction instruction() { + return instruction != null ? instruction : (instruction = new Instruction<>()); + } + + /**Sets an instruction to convert objects to string representation. + * @param instruction instruction to convert objects to string representation + * @return this Stringify + */ + public Stringify setInstruction(Instruction instruction) { + this.instruction = instruction; + return this; + } + + /**See {@link Instruction#beginElement(ToString)} + * @return this Stringify + */ + public Stringify beginElement(ToString func) { + instruction().beginElement(func); + return this; + } + + /**See {@link Instruction#endElement(ToString)} + * @return this Stringify + */ + public Stringify endElement(ToString func) { + instruction().endElement(func); + return this; + } + + /**See {@link Instruction#beginChildren(ToString)} + * @return this Stringify + */ + public Stringify beginChildren(ToString func) { + instruction().beginChildren(func); + return this; + } + + /**See {@link Instruction#endChildren(ToString)} + * @return this Stringify + */ + public Stringify endChildren(ToString func) { + instruction().endChildren(func); + return this; + } + + /**See {@link Instruction#getChildren(Function)} + * @return this Stringify + */ + public Stringify getChildren(Function> func) { + instruction().getChildren(func); + return this; + } + + /**Converts elements to string representation. + * @param elements objects of a hierarchy + * @return string representation of the elements + */ + public String get(Iterable elements) { + if (isEmpty(elements)) return ""; + + StringBuilder buff = new StringBuilder(); + toString(elements, buff, 0); + return buff.toString(); + } + + private void toString(Iterable elements, StringBuilder buff, int level) { + Instruction instr = instruction(); + int index = 0; + + for (T e: elements) { + buff.append(instr.beginElement().get(e, level, index)); + + Iterable children = instr.getChildren().apply(e); + int sublevel = level + 1; + if (!isEmpty(children)) { + buff.append(instr.beginChildren().get(e, sublevel, index)); + toString(children, buff, sublevel); + buff.append(instr.endChildren().get(e, sublevel, index)); + } + buff.append(instr.endElement().get(e, level, index)); + ++index; + } + } + + /**Is a set of steps to convert objects of a hierarchy to string representation. + *

With an Instruction, you specify how to + *

  • {@link #beginElement(ToString) Mark the beginning of an element} with a string
  • + *
  • {@link #endElement(ToString) Mark the end of an element} with a string
  • + *
  • {@link #getChildren(Function) Get child elements} of an element
  • + *
  • {@link #beginChildren(ToString) Mark the beginning of child elements} with a string
  • + *
  • {@link #endChildren(ToString) Mark the end of child elements} with a string
  • + *
+ *

+ * @param an object type + */ + public static class Instruction { + private ToString + beginElement, + endElement, + beginChildren, + endChildren; + private Function> getChildren; + + /**Creates a new Instruction. + */ + public Instruction() { + clear(); + } + + ToString beginElement() { + return beginElement; + } + + /**Sets a function that returns a string to mark the beginning of an element. + * @param func function that returns a string to mark the beginning of an element + * @return this Instruction + */ + public Instruction beginElement(ToString func) { + this.beginElement = func; + return this; + } + + ToString endElement() { + return endElement; + } + + /**Sets a function that returns a string to mark the end of an element. + * @param func function that returns a string to mark the end of an element + * @return this Instruction + */ + public Instruction endElement(ToString func) { + this.endElement = func; + return this; + } + + ToString beginChildren() { + return beginChildren; + } + + /**Sets a function that returns a string to mark the beginning of child elements. + * @param func function that returns a string to mark the beginning of child elements + * @return this Instruction + */ + public Instruction beginChildren(ToString func) { + this.beginChildren = func; + return this; + } + + ToString endChildren() { + return endChildren; + } + + /**Sets a function that returns a string to mark the end of child elements. + * @param func function that returns a string to mark the end of child elements + * @return this Instruction + */ + public Instruction endChildren(ToString func) { + this.endChildren = func; + return this; + } + + Function> getChildren() { + return getChildren; + } + + /**Sets a function that returns child elements of the current element. + * @param getChildren function that returns child elements of the current element + * @return this Instruction + */ + public Instruction getChildren(Function> getChildren) { + this.getChildren = getChildren; + return this; + } + + /**Clears the Instruction to the initial state. + * @return this Instruction + */ + public Instruction clear() { + ToString empty = (e, level, index) -> ""; + beginElement = empty; + endElement = empty; + beginChildren = empty; + endChildren = empty; + getChildren = t -> Collections.emptyList(); + return this; + } + } +} diff --git a/src/main/java/cokr/xit/foundation/data/hierarchy/package-info.java b/src/main/java/cokr/xit/foundation/data/hierarchy/package-info.java index fad99b6..c37a755 100644 --- a/src/main/java/cokr/xit/foundation/data/hierarchy/package-info.java +++ b/src/main/java/cokr/xit/foundation/data/hierarchy/package-info.java @@ -1,3 +1,3 @@ -/**계층구조를 이루는 데이터 지원 - */ +/**계층구조를 이루는 데이터 지원 + */ package cokr.xit.foundation.data.hierarchy; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/package-info.java b/src/main/java/cokr/xit/foundation/data/package-info.java index 3d28a41..01f409e 100644 --- a/src/main/java/cokr/xit/foundation/data/package-info.java +++ b/src/main/java/cokr/xit/foundation/data/package-info.java @@ -1,3 +1,3 @@ -/**데이터 전달 및 변환을 위한 컴포넌트 - */ +/**데이터 전달 및 변환을 위한 컴포넌트 + */ package cokr.xit.foundation.data; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/paging/BoundedList.java b/src/main/java/cokr/xit/foundation/data/paging/BoundedList.java index 36736f8..49ca6b8 100644 --- a/src/main/java/cokr/xit/foundation/data/paging/BoundedList.java +++ b/src/main/java/cokr/xit/foundation/data/paging/BoundedList.java @@ -1,224 +1,224 @@ -package cokr.xit.foundation.data.paging; - -import java.util.ArrayList; -import java.util.Map; - -/**페이징을 위한 정보를 설정하기 위한 리스트. - *

BoundedList는 대량의 요소를 한 번에 원하는 수만큼 제공할 때 사용한다.
- * 예를 들어, 대량의 데이터베이스 조회 결과 데이터를 한 번에 20개씩 제공하고자 할 경우 BoundedList를 사용할 수 있다.

- *

이를 위해 개발자는 필요한 수만큼의 데이터를 추출하고 BoundedList에 이 데이터를 담아야 한다.
- * 그리고 나서 BoundedList에 다음 정보를 설정한다. - *

  • 원래 데이터의 {@link #setTotalSize(int) 전체 갯수}
  • - *
  • {@link #setFetchSize(int) 한 번에 가지고 온 데이터 갯수}
  • - *
  • 현재 가지고 온 데이터의 {@link #setStart(int) 시작 인덱스}(0부터 시작)
  • - *
- *

BoundedList가 가지고 온 요소를 접근할 때는 0부터 시작하는 인덱스를 사용한다.

- *

BoundedList는 {@link #hasMore() 더 가져올 수 있는} 요소가 {@link #hasNext() 앞으로} 또는 {@link #hasPrevious() 뒤로} 있는지 반환한다.

- * @param 요소 타입 - */ -public class BoundedList extends ArrayList { - private static final long serialVersionUID = 1L; - - private int - current, - fetchSize, - start; - private long totalSize; - - /**한 번에 가지고 올 요소의 갯수를 반환한다. - * @return 한 번에 가지고 올 요소의 갯수 - */ - public int getFetchSize() { - return fetchSize; - } - /**한 번에 가지고 올 요소의 갯수를 설정한다. - * @param size 한 번에 가지고 올 요소의 갯수 - * @return BoundedList 자신 - */ - public BoundedList setFetchSize(int size) { - if (size < 0) - throw new IllegalArgumentException("fetchSize < 0"); - this.fetchSize = size; - return this; - } - /**원래 요소의 전체 갯수를 반환한다. - * @return 원래 요소의 전체 갯수 - */ - public long getTotalSize() { - if (isEmpty()) - return 0; - if (totalSize > 0) - return totalSize; - - if (totalSize < 1) { - E first = get(0); - if (first instanceof Map) { - Map row = (Map)first; - totalSize = Pageable.getTotalSize(row); - } else if (first instanceof Pageable) { - Pageable row = (Pageable)first; - totalSize = row.getTotalSize(); - } - } - return totalSize < 1 ? totalSize = size() : totalSize; - } - /**원래 요소의 전체 갯수를 설정한다. - * @param totalSize 원래 요소의 전체 갯수 - */ - public BoundedList setTotalSize(long totalSize) { - this.totalSize = totalSize; - return this; - } - /**현재 가지고 온 요소의 시작 인덱스를 반환한다. - * @return 현재 가지고 온 요소의 시작 인덱스(0부터 시작) - */ - public int getStart() {return isEmpty() ? -1 : start;} - /**현재 가지고 온 요소의 시작 인덱스를 설정한다. - * @param start 현재 가지고 온 요소의 시작 인덱스(0부터 시작) - */ - public BoundedList setStart(int start) { - this.start = start; -// this.start = totalSize < 1 ? -1 : start; - return this; - } - /**현재 가지고 온 요소의 마지막 인덱스를 반환한다. - * @return 현재 가지고 온 요소의 마지막 인덱스(0부터 시작) - */ - public int getEnd() { - return isEmpty() ? -1 : start + size() - 1; - } - /**현재 선택된 요소의 인덱스를 반환한다. - * @return 현재 선택된 요소의 인덱스 - */ - public int current() { - return isEmpty() ? -1 : current; - } - /**지정하는 인덱스의 요소를 현재 요소로 설정한다. - * @param index 요소의 인덱스 - */ - public BoundedList setCurrent(int index) { - if (index < 0) { - current = index; - return this; - } - else if (get(index) != null) current = index; - return this; - } - /**더 가져올 수 있는 요소가 있는지 반환한다. - * @return - *
  • 더 가져올 수 있는 요소가 있으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean hasMore() { - return !isEmpty() && size() < getTotalSize(); - } - /**뒤쪽으로 가져올 수 있는 요소가 있는지 반환한다. - * @return - *
  • 가져올 수 있는 요소가 있으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean hasPrevious() { - return hasMore() && start > 0; - } - /**앞쪽으로 가져올 수 있는 요소가 있는지 반환한다. - * @return - *
  • 가져올 수 있는 요소가 있으면 true
  • - *
  • 그렇지 않으면 false
  • - *
- */ - public boolean hasNext() { - return hasMore() && getEnd() < getTotalSize() - 1; - } - /**현재 선택된 요소를 반환한다. - * @return 현재 선택된 요소 - */ - public E get() { - return get(current); - } - @Override - public boolean add(E e) { - if (e == null || contains(e)) return false; - - boolean result = super.add(e); - ++totalSize; - return result; - } - @Override - public void add(int index, E e) { - super.add(index, e); - ++totalSize; - } - @Override - public E remove(int index) { - E e = super.remove(index); - if (e != null) - --totalSize; - return e; - } - @Override - public void clear() { - super.clear(); - totalSize = 0; - start = -1; - } - - /**BoundedList를 초기화 한다. - */ - public BoundedList init() { - totalSize = size(); - return this; - } - - /**페이징 처리를 위한 값들을 계산하기 위한 유틸리티*/ - public static class Fetch { - public static final int - ALL = 0, - NONE = -1; - - private Fetch() {} - /**Returns the number of fetches needed to provide all elements. - * @param elementCount number of all elements - * @param size size or number of elements in a fetch - * @return number of fetches needed to provide all elements - */ - public static final int count(int elementCount, int size) { - if (elementCount == 0 || size == ALL) return 1; - return (elementCount / size) + ((elementCount % size) == 0 ? 0 : 1); - } - /**Returns the end index of the elements starting from the start index. - * @param elementCount number of all elements - * @param size size or number of elements in a fetch - * @param start 0-based start index - * @return 0-based end index of the - */ - public static final int end(int elementCount, int size, int start) { - if (size < ALL) - throw new IllegalArgumentException("Invalid size: " + size); - if (elementCount < 0) - throw new IllegalArgumentException("Invalid elementCount: " + elementCount); - - int last = elementCount - 1; - if (size == ALL) return last; - return Math.min(last, start + size -1); - } - /** - * @param current - * @param count - * @return - */ - public static final int page(int current, int count) { - return count < 1 ? 0 : current / count; - } - /** - * @param page - * @param visibleLinks - * @return - */ - public static final int band(int page, int visibleLinks) { - return visibleLinks < 1 ? 0 : page / visibleLinks; - } - } - +package cokr.xit.foundation.data.paging; + +import java.util.ArrayList; +import java.util.Map; + +/**페이징을 위한 정보를 설정하기 위한 리스트. + *

BoundedList는 대량의 요소를 한 번에 원하는 수만큼 제공할 때 사용한다.
+ * 예를 들어, 대량의 데이터베이스 조회 결과 데이터를 한 번에 20개씩 제공하고자 할 경우 BoundedList를 사용할 수 있다.

+ *

이를 위해 개발자는 필요한 수만큼의 데이터를 추출하고 BoundedList에 이 데이터를 담아야 한다.
+ * 그리고 나서 BoundedList에 다음 정보를 설정한다. + *

  • 원래 데이터의 {@link #setTotalSize(int) 전체 갯수}
  • + *
  • {@link #setFetchSize(int) 한 번에 가지고 온 데이터 갯수}
  • + *
  • 현재 가지고 온 데이터의 {@link #setStart(int) 시작 인덱스}(0부터 시작)
  • + *
+ *

BoundedList가 가지고 온 요소를 접근할 때는 0부터 시작하는 인덱스를 사용한다.

+ *

BoundedList는 {@link #hasMore() 더 가져올 수 있는} 요소가 {@link #hasNext() 앞으로} 또는 {@link #hasPrevious() 뒤로} 있는지 반환한다.

+ * @param 요소 타입 + */ +public class BoundedList extends ArrayList { + private static final long serialVersionUID = 1L; + + private int + current, + fetchSize, + start; + private long totalSize; + + /**한 번에 가지고 올 요소의 갯수를 반환한다. + * @return 한 번에 가지고 올 요소의 갯수 + */ + public int getFetchSize() { + return fetchSize; + } + /**한 번에 가지고 올 요소의 갯수를 설정한다. + * @param size 한 번에 가지고 올 요소의 갯수 + * @return BoundedList 자신 + */ + public BoundedList setFetchSize(int size) { + if (size < 0) + throw new IllegalArgumentException("fetchSize < 0"); + this.fetchSize = size; + return this; + } + /**원래 요소의 전체 갯수를 반환한다. + * @return 원래 요소의 전체 갯수 + */ + public long getTotalSize() { + if (isEmpty()) + return 0; + if (totalSize > 0) + return totalSize; + + if (totalSize < 1) { + E first = get(0); + if (first instanceof Map) { + Map row = (Map)first; + totalSize = Pageable.getTotalSize(row); + } else if (first instanceof Pageable) { + Pageable row = (Pageable)first; + totalSize = row.getTotalSize(); + } + } + return totalSize < 1 ? totalSize = size() : totalSize; + } + /**원래 요소의 전체 갯수를 설정한다. + * @param totalSize 원래 요소의 전체 갯수 + */ + public BoundedList setTotalSize(long totalSize) { + this.totalSize = totalSize; + return this; + } + /**현재 가지고 온 요소의 시작 인덱스를 반환한다. + * @return 현재 가지고 온 요소의 시작 인덱스(0부터 시작) + */ + public int getStart() {return isEmpty() ? -1 : start;} + /**현재 가지고 온 요소의 시작 인덱스를 설정한다. + * @param start 현재 가지고 온 요소의 시작 인덱스(0부터 시작) + */ + public BoundedList setStart(int start) { + this.start = start; +// this.start = totalSize < 1 ? -1 : start; + return this; + } + /**현재 가지고 온 요소의 마지막 인덱스를 반환한다. + * @return 현재 가지고 온 요소의 마지막 인덱스(0부터 시작) + */ + public int getEnd() { + return isEmpty() ? -1 : start + size() - 1; + } + /**현재 선택된 요소의 인덱스를 반환한다. + * @return 현재 선택된 요소의 인덱스 + */ + public int current() { + return isEmpty() ? -1 : current; + } + /**지정하는 인덱스의 요소를 현재 요소로 설정한다. + * @param index 요소의 인덱스 + */ + public BoundedList setCurrent(int index) { + if (index < 0) { + current = index; + return this; + } + else if (get(index) != null) current = index; + return this; + } + /**더 가져올 수 있는 요소가 있는지 반환한다. + * @return + *
  • 더 가져올 수 있는 요소가 있으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean hasMore() { + return !isEmpty() && size() < getTotalSize(); + } + /**뒤쪽으로 가져올 수 있는 요소가 있는지 반환한다. + * @return + *
  • 가져올 수 있는 요소가 있으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean hasPrevious() { + return hasMore() && start > 0; + } + /**앞쪽으로 가져올 수 있는 요소가 있는지 반환한다. + * @return + *
  • 가져올 수 있는 요소가 있으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean hasNext() { + return hasMore() && getEnd() < getTotalSize() - 1; + } + /**현재 선택된 요소를 반환한다. + * @return 현재 선택된 요소 + */ + public E get() { + return get(current); + } + @Override + public boolean add(E e) { + if (e == null || contains(e)) return false; + + boolean result = super.add(e); + ++totalSize; + return result; + } + @Override + public void add(int index, E e) { + super.add(index, e); + ++totalSize; + } + @Override + public E remove(int index) { + E e = super.remove(index); + if (e != null) + --totalSize; + return e; + } + @Override + public void clear() { + super.clear(); + totalSize = 0; + start = -1; + } + + /**BoundedList를 초기화 한다. + */ + public BoundedList init() { + totalSize = size(); + return this; + } + + /**페이징 처리를 위한 값들을 계산하기 위한 유틸리티*/ + public static class Fetch { + public static final int + ALL = 0, + NONE = -1; + + private Fetch() {} + /**Returns the number of fetches needed to provide all elements. + * @param elementCount number of all elements + * @param size size or number of elements in a fetch + * @return number of fetches needed to provide all elements + */ + public static final int count(int elementCount, int size) { + if (elementCount == 0 || size == ALL) return 1; + return (elementCount / size) + ((elementCount % size) == 0 ? 0 : 1); + } + /**Returns the end index of the elements starting from the start index. + * @param elementCount number of all elements + * @param size size or number of elements in a fetch + * @param start 0-based start index + * @return 0-based end index of the + */ + public static final int end(int elementCount, int size, int start) { + if (size < ALL) + throw new IllegalArgumentException("Invalid size: " + size); + if (elementCount < 0) + throw new IllegalArgumentException("Invalid elementCount: " + elementCount); + + int last = elementCount - 1; + if (size == ALL) return last; + return Math.min(last, start + size -1); + } + /** + * @param current + * @param count + * @return + */ + public static final int page(int current, int count) { + return count < 1 ? 0 : current / count; + } + /** + * @param page + * @param visibleLinks + * @return + */ + public static final int band(int page, int visibleLinks) { + return visibleLinks < 1 ? 0 : page / visibleLinks; + } + } + } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/paging/Pageable.java b/src/main/java/cokr/xit/foundation/data/paging/Pageable.java index d24e437..4dee505 100644 --- a/src/main/java/cokr/xit/foundation/data/paging/Pageable.java +++ b/src/main/java/cokr/xit/foundation/data/paging/Pageable.java @@ -1,71 +1,71 @@ -package cokr.xit.foundation.data.paging; - -import java.util.Map; - -import cokr.xit.foundation.data.Convert; - -/**조회 결과를 페이징 처리하여 받고자 할 경우, - *
  • 조회 요청 정보 객체나
  • - *
  • 조회 결과 객체
  • - *
- * 가 구현해야 하는 인터페이스 - * @author mjkhan - */ -public interface Pageable { - /**조회 요청 정보 객체가 Map일 경우 요청 페이지 번호를 반환한다.
- * 조회 요청 정보 맵은 "pageNum" 키에 값을 가지고 있어야 한다. - * @param map 조회 요청 정보 객체 - * @return 요청 페이지 번호 - */ - static int getPageNum(Map map) { - return Convert.toInt(map.get("pageNum")); - } - - /**조회 요청 정보 객체가 Map일 경우 한 번에 가지고 올 결과값들의 갯수를 반환한다.
- * 조회 요청 정보 맵은 "fetchSize" 키에 값을 가지고 있어야 한다. - * @param map 조회 요청 정보 객체 - * @return 요청 한 번에 가지고 올 결과값들의 갯수 - */ - static int getFetchSize(Map map) { - return Convert.toInt(map.get("fetchSize")); - } - - /**조회 결과 객체가 Map일 경우 조회 결과의 전체 갯수를 반환한다.
- * 조회 결과 객체는 "TOT_CNT" 키에 값을 가지고 있어야 한다. - * @param map 조회 결과 객체 - * @return 조회 결과의 전체 갯수 - */ - static long getTotalSize(Map map) { - return Convert.toLong(map.get("TOT_CNT")); - } - - /**조회 요청 정보 객체가 구현하는 메소드로, 요청 페이지 번호를 반환한다. - * @return 요청 페이지 번호 - */ - default int getPageNum() {return 0;} - - /**조회 요청 정보 객체가 구현하는 메소드로, 요청 페이지 번호를 설정한다. - * @param pageNum 요청 페이지 번호 - */ - default void setPageNum(int pageNum) {} - - /**조회 요청 정보 객체가 구현하는 메소드로, 한 번에 가지고 올 결과값들의 갯수를 반환한다. - * @return 한 번에 가지고 올 결과값들의 갯수 - */ - default int getFetchSize() {return 0;} - - /**조회 요청 정보 객체가 구현하는 메소드로, 한 번에 가지고 올 결과값들의 갯수를 설정한다. - * @param fetchSize 한 번에 가지고 올 결과값들의 갯수 - */ - default void setFetchSize(int fetchSize) {} - - /**조회 결과 객체가 구현하는 메소드로, 조회 결과의 전체 갯수를 반환한다. - * @return 조회 결과의 전체 갯수 - */ - default long getTotalSize() {return 0;} - - /**조회 결과 객체가 구현하는 메소드로, 조회 결과의 전체 갯수를 반환한다. - * @param totalSize 조회 결과의 전체 갯수 - */ - default void setTotalSize(long totalSize) {} +package cokr.xit.foundation.data.paging; + +import java.util.Map; + +import cokr.xit.foundation.data.Convert; + +/**조회 결과를 페이징 처리하여 받고자 할 경우, + *
  • 조회 요청 정보 객체나
  • + *
  • 조회 결과 객체
  • + *
+ * 가 구현해야 하는 인터페이스 + * @author mjkhan + */ +public interface Pageable { + /**조회 요청 정보 객체가 Map일 경우 요청 페이지 번호를 반환한다.
+ * 조회 요청 정보 맵은 "pageNum" 키에 값을 가지고 있어야 한다. + * @param map 조회 요청 정보 객체 + * @return 요청 페이지 번호 + */ + static int getPageNum(Map map) { + return Convert.toInt(map.get("pageNum")); + } + + /**조회 요청 정보 객체가 Map일 경우 한 번에 가지고 올 결과값들의 갯수를 반환한다.
+ * 조회 요청 정보 맵은 "fetchSize" 키에 값을 가지고 있어야 한다. + * @param map 조회 요청 정보 객체 + * @return 요청 한 번에 가지고 올 결과값들의 갯수 + */ + static int getFetchSize(Map map) { + return Convert.toInt(map.get("fetchSize")); + } + + /**조회 결과 객체가 Map일 경우 조회 결과의 전체 갯수를 반환한다.
+ * 조회 결과 객체는 "TOT_CNT" 키에 값을 가지고 있어야 한다. + * @param map 조회 결과 객체 + * @return 조회 결과의 전체 갯수 + */ + static long getTotalSize(Map map) { + return Convert.toLong(map.get("TOT_CNT")); + } + + /**조회 요청 정보 객체가 구현하는 메소드로, 요청 페이지 번호를 반환한다. + * @return 요청 페이지 번호 + */ + default int getPageNum() {return 0;} + + /**조회 요청 정보 객체가 구현하는 메소드로, 요청 페이지 번호를 설정한다. + * @param pageNum 요청 페이지 번호 + */ + default void setPageNum(int pageNum) {} + + /**조회 요청 정보 객체가 구현하는 메소드로, 한 번에 가지고 올 결과값들의 갯수를 반환한다. + * @return 한 번에 가지고 올 결과값들의 갯수 + */ + default int getFetchSize() {return 0;} + + /**조회 요청 정보 객체가 구현하는 메소드로, 한 번에 가지고 올 결과값들의 갯수를 설정한다. + * @param fetchSize 한 번에 가지고 올 결과값들의 갯수 + */ + default void setFetchSize(int fetchSize) {} + + /**조회 결과 객체가 구현하는 메소드로, 조회 결과의 전체 갯수를 반환한다. + * @return 조회 결과의 전체 갯수 + */ + default long getTotalSize() {return 0;} + + /**조회 결과 객체가 구현하는 메소드로, 조회 결과의 전체 갯수를 반환한다. + * @param totalSize 조회 결과의 전체 갯수 + */ + default void setTotalSize(long totalSize) {} } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/paging/PagingSupport.java b/src/main/java/cokr/xit/foundation/data/paging/PagingSupport.java index 4463bc6..77005c2 100644 --- a/src/main/java/cokr/xit/foundation/data/paging/PagingSupport.java +++ b/src/main/java/cokr/xit/foundation/data/paging/PagingSupport.java @@ -1,96 +1,96 @@ -package cokr.xit.foundation.data.paging; - -import java.sql.Statement; -import java.util.List; -import java.util.Map; - -import org.apache.ibatis.executor.Executor; -import org.apache.ibatis.executor.resultset.ResultSetHandler; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.plugin.Interceptor; -import org.apache.ibatis.plugin.Intercepts; -import org.apache.ibatis.plugin.Invocation; -import org.apache.ibatis.plugin.Signature; -import org.apache.ibatis.session.ResultHandler; -import org.apache.ibatis.session.RowBounds; - -import cokr.xit.foundation.AbstractComponent; -import cokr.xit.foundation.component.QueryRequest; - -/**조회 결과의 페이징 처리를 지원하는 MyBatis Interceptor - * @author mjkhan - */ -@Intercepts({ - @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), - @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) -}) -public class PagingSupport extends AbstractComponent implements Interceptor { - private boolean paging; - private int pageNum; - private int fetchSize; - - @Override - public Object intercept(Invocation invocation) throws Throwable { - switch (getStep(invocation)) { - case "Executor.query": return setPaging(invocation); - case "ResultSetHandler.handleResultSets": return setPagingInfo(invocation); - default: return invocation.proceed(); - } - } - - private static final Class[] handlers = {Executor.class, ResultSetHandler.class}; - - private String getStep(Invocation invocation) { - Object obj = invocation.getTarget(); - String result = ""; - for (Class klass: handlers) { - if (klass.isInstance(obj)) { - result = klass.getSimpleName(); - break; - } - } - return result += "." + invocation.getMethod().getName(); - } - - private Object setPaging(Invocation invocation) throws Exception { - Object[] args = invocation.getArgs(); - Object arg = args[1]; - if (arg instanceof Map) { - @SuppressWarnings("unchecked") - Map params = (Map)arg; - fetchSize = Pageable.getFetchSize(params); - pageNum = Pageable.getPageNum(params); - } else if (arg instanceof QueryRequest) { - QueryRequest req = (QueryRequest)arg; - fetchSize = req.getFetchSize(); - pageNum = req.getPageNum(); - } else if (arg instanceof Pageable) { - Pageable pageable = (Pageable)arg; - fetchSize = pageable.getFetchSize(); - pageNum = pageable.getPageNum(); - } - paging = pageNum > 0 && fetchSize > 0; - - return invocation.proceed(); - } - - private Object setPagingInfo(Invocation invocation) throws Exception { - Object obj = invocation.proceed(); - - if (paging && (obj instanceof List)) { - List list = (List)obj; - BoundedList boundedList = new BoundedList<>(); - boundedList.setFetchSize(fetchSize); - boundedList.addAll(list); - if (!boundedList.isEmpty()) { - boundedList.setStart((pageNum - 1) * fetchSize); - } - obj = boundedList; - } - - paging = false; - pageNum = fetchSize = 0; - - return obj; - } +package cokr.xit.foundation.data.paging; + +import java.sql.Statement; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.component.QueryRequest; + +/**조회 결과의 페이징 처리를 지원하는 MyBatis Interceptor + * @author mjkhan + */ +@Intercepts({ + @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), + @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) +}) +public class PagingSupport extends AbstractComponent implements Interceptor { + private boolean paging; + private int pageNum; + private int fetchSize; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + switch (getStep(invocation)) { + case "Executor.query": return setPaging(invocation); + case "ResultSetHandler.handleResultSets": return setPagingInfo(invocation); + default: return invocation.proceed(); + } + } + + private static final Class[] handlers = {Executor.class, ResultSetHandler.class}; + + private String getStep(Invocation invocation) { + Object obj = invocation.getTarget(); + String result = ""; + for (Class klass: handlers) { + if (klass.isInstance(obj)) { + result = klass.getSimpleName(); + break; + } + } + return result += "." + invocation.getMethod().getName(); + } + + private Object setPaging(Invocation invocation) throws Exception { + Object[] args = invocation.getArgs(); + Object arg = args[1]; + if (arg instanceof Map) { + @SuppressWarnings("unchecked") + Map params = (Map)arg; + fetchSize = Pageable.getFetchSize(params); + pageNum = Pageable.getPageNum(params); + } else if (arg instanceof QueryRequest) { + QueryRequest req = (QueryRequest)arg; + fetchSize = req.getFetchSize(); + pageNum = req.getPageNum(); + } else if (arg instanceof Pageable) { + Pageable pageable = (Pageable)arg; + fetchSize = pageable.getFetchSize(); + pageNum = pageable.getPageNum(); + } + paging = pageNum > 0 && fetchSize > 0; + + return invocation.proceed(); + } + + private Object setPagingInfo(Invocation invocation) throws Exception { + Object obj = invocation.proceed(); + + if (paging && (obj instanceof List)) { + List list = (List)obj; + BoundedList boundedList = new BoundedList<>(); + boundedList.setFetchSize(fetchSize); + boundedList.addAll(list); + if (!boundedList.isEmpty()) { + boundedList.setStart((pageNum - 1) * fetchSize); + } + obj = boundedList; + } + + paging = false; + pageNum = fetchSize = 0; + + return obj; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/paging/package-info.java b/src/main/java/cokr/xit/foundation/data/paging/package-info.java index 1f76310..5772a59 100644 --- a/src/main/java/cokr/xit/foundation/data/paging/package-info.java +++ b/src/main/java/cokr/xit/foundation/data/paging/package-info.java @@ -1,62 +1,62 @@ -/**조회 결과의 페이징 처리 지원 - *

조회 결과가 대량의 데이터를 반환할 때, 이를 작은 갯수로 여러 번에 나누어 반환하는 것을 페이징이라 한다.
- * 조회 결과를 페이징 처리하려면 다음과 같은 절차를 거쳐야 한다. - *

{@link PagingSupport} 설정 - *

PagingSupport를 sql/mybatis-config.xml에 다음과 같이 등록한다. - *

{@code  
- *     
- * 
- * }
- *

SELECT문 작성
- * SELECT문을 작성하고 다음과 같이 - *

  • {@code }
  • - *
  • {@code }
  • - *
- * 를 include한다. - *
{@code  }의 parameterType에 올 수 있는 클래스는 다음과 같다.
- *         
  • map
  • - *
  • {@link cokr.xit.foundation.component.QueryRequest} 또는 그것을 상속 받는 클래스
  • - *
  • {@link cokr.xit.foundation.data.paging.Pageable}을 구현하는 클래스
  • - *
- * - *
  • resultType - {@code + * + * SELECT * + * FROM A_TABLE + * WHERE ... + * ORDER BY ... + * }
  • + *
    • parameterType - {@code }의 resultType에 올 수 있는 클래스는 다음과 같다. + *
      • map
      • + *
      • {@link cokr.xit.foundation.data.paging.Pageable}을 구현하는 클래스
      • + *
      + *
    • + *
    + *

    조회 실행
    + * 파라미터가 map일 경우 + *

    {@code  DataObject params = new DataObject()
    + *     .set("pageNum", 1)
    + *     .set("fetchSize", 10);
    + * List list = aTableMapper.selectXXX(params);
    + * }
    + * 파라미터가 QueryRequest 또는 상속 받은 클래스일 경우 + *
    {@code  QueryRequest req = new QueryRequest();
    + * req.setPageNum(1)
    + * req.setFetchSize(10);
    + * List list = aTableMapper.selectXXX(req);
    + * }
    + * 파라미터가 Pageable의 구현 클래스일 경우 + *
    {@code  class PageableObject implements Pageable {
    + *     ....
    + * };
    + * PageableObject req = new PageableObject();
    + * req.setPageNum(1)
    + * req.setFetchSize(10);
    + * List list = aTableMapper.selectXXX(req);
    + * }
    + *

    컨트롤러에서 조회 결과와 페이징 정보 전달
    + * {@link cokr.xit.foundation.web.AbstractController}를 상속 받아 정의한 컨트롤러는
    + * setPagingInfo(ModelAndView, List, String) 메소드로 조회 결과를 ModelAndView에 전달한다. + *

    {@code setPagingInfo(new ModelAndView("viewName"), list, "example");}
    + */ package cokr.xit.foundation.data.paging; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/package-info.java b/src/main/java/cokr/xit/foundation/package-info.java index b9190af..d711e87 100644 --- a/src/main/java/cokr/xit/foundation/package-info.java +++ b/src/main/java/cokr/xit/foundation/package-info.java @@ -1,105 +1,105 @@ -/**기반 기술 지원 및 공통 기능, 주요 구성요소 개발 지원 - *

    로그

    - *

    로그는 애플리케이션 개발 단계에는 디버깅 시 소스 추적에 사용되며,
    - * 운영단계에서는 로그 자료를 애플리케이션의 튜닝이나 troubleshooting의 근거로 사용한다. - *

    x-base는 slf4j를 포장한 {@link Log}를 로그에 사용한다.
    - * x-base의 {@link AbstractComponent}를 상속받아 만든 클래스에서는
    - * {@link AbstractComponent#log()} 메소드를 호출해서 로그를 남길 수 있다. - *

    사용자 세션 정보

    - * x-base는 현재 사용자의 접속과 관련해서 다음 정보를 제공한다. - *
    • {@link Access}
    • - *
    • {@link UserInfo}
    • - *
    • {@link ApplicationContainer}
    • - *
    - * 이중 Access와 UserInfo는 AbstractComponent이나 AbstractServiceBean을 상속받아 만든 클래스에서 - *
    • {@link AbstractComponent#currentAccess()}
    • - *
    • {@link AbstractComponent#currentUser()}
    • - *
    - * 메소드로 받을 수 있다. - *

    ApplicationContainer가 필요한 클래스에서 다음과 같이 받을 수 있다. - *

    {@code  @Resource(name = "applicationContainer")
    - * private ApplicationContainer applicationContainer;
    - * }
    - *

    프로퍼티 사용

    - * 프로퍼티는 *.properties 파일에 key=value 쌍으로 된 정보를 저장하고
    - * 애플리케이션에서 프로퍼티 파일을 읽어들여 필요한 키의 값을 사용하는 기능이다. - *

    x-base는 전자정부 프레임워크의 EgovPropertyService를 사용해 프로퍼티를 읽어들인다.
    - * EgovPropertyService는 spring/context-common.xml 파일에 다음과 같이 설정되어있다. - *

    {@code 
    - *     
    - *         
    - *             
    - *
    - *             
    - *             
    - *         
    - *     
    - *     
    - *         
    - *             
    - *                 
    - *                 
    - *             
    - *             
    - *                 
    - *                 
    - *             
    - *         
    - *     
    - * 
    - * }
    - * 프로퍼티를 설정할 때는 프로퍼티의 키가 중복되지 않도록 한다. - *

    x-base의 AbstractComponent이나 AbstractServiceBean을 상속받아 만든 클래스에서는
    - * {@link AbstractComponent#properties} 필드로 프로퍼티를 사용할 수 있다. - *

    트랜잭션 처리

    - *
    • 트랜잭션은 완료 또는 취소할 수 있는 작업의 최소단위다.
    • - *
    • 애플리케이션 개발자는 트랜잭션의 적용에 개입하지 않아야 한다.
    • - *
    • 애플리케이션에서 트랜잭션의 대상은 서비스 인터페이스의 각각의 메소드다.
    • - *
    • 서비스 인터페이스 메소드를 대상으로 하는 트랜잭션의 설정은
      - * spring/context-datasource.xml에 다음과 같이 한다.
    • - *
    - *
    {@code  
    - *     
    - * 
    - *
    - * 
    - *     
    - *         
    - *     
    - * 
    - *
    - * 
    - *     
    - *     
    - *     
    - * 
    - * }
    - *

    Lombok 라이브러리를 이용한 엔티티 정의

    - *
    • 엔티티는 자신의 상태를 파일이나 데이터베이스 테이블에 저장하는 객체를 말한다.
    • - *
    • 엔티티 클래스의 필드들에 대한 getter와 setter 메소드는 - *
      • 가급적 직접 작성을 우선한다.
      • - *
      • Lombok 라이브러리로 생성하는 것도 허용한다.
      • - *
      • Lombok 라이브러리로 생성할 경우 가급적 @Getter와 @Setter만 사용하고 다른 어노테이션의 사용은 자제한다.
      • - *
      - *
    • - *
    - *

    조회 결과의 엑셀 다운로드

    - * 추가 예정 - *

    예외 처리

    - * 애플리케이션 동작 중 발생한 오류는 {@link ApplicationException}으로 처리한다.
    - * ApplicationException을 발생시키려면 - *
    {@code  if (result == null)
    - *     throw ApplicationException
    - *         .get(null)
    - *         .setCode("NPE")
    - *         .setMessage("결과값이 없습니다.")
    - *         .info("reason", "알 수 없음"); // 추가 정보 설정
    - * }
    - * 다른 오류를 ApplicationException으로 바꾸어 발생시키려면 - *
    {@code  try {
    - *     ...
    - * } catch(Exception e) {
    - *     throw ApplicationException.get(e);
    - * }}
    - */ +/**기반 기술 지원 및 공통 기능, 주요 구성요소 개발 지원 + *

    로그

    + *

    로그는 애플리케이션 개발 단계에는 디버깅 시 소스 추적에 사용되며,
    + * 운영단계에서는 로그 자료를 애플리케이션의 튜닝이나 troubleshooting의 근거로 사용한다. + *

    x-base는 slf4j를 포장한 {@link Log}를 로그에 사용한다.
    + * x-base의 {@link AbstractComponent}를 상속받아 만든 클래스에서는
    + * {@link AbstractComponent#log()} 메소드를 호출해서 로그를 남길 수 있다. + *

    사용자 세션 정보

    + * x-base는 현재 사용자의 접속과 관련해서 다음 정보를 제공한다. + *
    • {@link Access}
    • + *
    • {@link UserInfo}
    • + *
    • {@link ApplicationContainer}
    • + *
    + * 이중 Access와 UserInfo는 AbstractComponent이나 AbstractServiceBean을 상속받아 만든 클래스에서 + *
    • {@link AbstractComponent#currentAccess()}
    • + *
    • {@link AbstractComponent#currentUser()}
    • + *
    + * 메소드로 받을 수 있다. + *

    ApplicationContainer가 필요한 클래스에서 다음과 같이 받을 수 있다. + *

    {@code  @Resource(name = "applicationContainer")
    + * private ApplicationContainer applicationContainer;
    + * }
    + *

    프로퍼티 사용

    + * 프로퍼티는 *.properties 파일에 key=value 쌍으로 된 정보를 저장하고
    + * 애플리케이션에서 프로퍼티 파일을 읽어들여 필요한 키의 값을 사용하는 기능이다. + *

    x-base는 전자정부 프레임워크의 EgovPropertyService를 사용해 프로퍼티를 읽어들인다.
    + * EgovPropertyService는 spring/context-common.xml 파일에 다음과 같이 설정되어있다. + *

    {@code 
    + *     
    + *         
    + *             
    + *
    + *             
    + *             
    + *         
    + *     
    + *     
    + *         
    + *             
    + *                 
    + *                 
    + *             
    + *             
    + *                 
    + *                 
    + *             
    + *         
    + *     
    + * 
    + * }
    + * 프로퍼티를 설정할 때는 프로퍼티의 키가 중복되지 않도록 한다. + *

    x-base의 AbstractComponent이나 AbstractServiceBean을 상속받아 만든 클래스에서는
    + * {@link AbstractComponent#properties} 필드로 프로퍼티를 사용할 수 있다. + *

    트랜잭션 처리

    + *
    • 트랜잭션은 완료 또는 취소할 수 있는 작업의 최소단위다.
    • + *
    • 애플리케이션 개발자는 트랜잭션의 적용에 개입하지 않아야 한다.
    • + *
    • 애플리케이션에서 트랜잭션의 대상은 서비스 인터페이스의 각각의 메소드다.
    • + *
    • 서비스 인터페이스 메소드를 대상으로 하는 트랜잭션의 설정은
      + * spring/context-datasource.xml에 다음과 같이 한다.
    • + *
    + *
    {@code  
    + *     
    + * 
    + *
    + * 
    + *     
    + *         
    + *     
    + * 
    + *
    + * 
    + *     
    + *     
    + *     
    + * 
    + * }
    + *

    Lombok 라이브러리를 이용한 엔티티 정의

    + *
    • 엔티티는 자신의 상태를 파일이나 데이터베이스 테이블에 저장하는 객체를 말한다.
    • + *
    • 엔티티 클래스의 필드들에 대한 getter와 setter 메소드는 + *
      • 가급적 직접 작성을 우선한다.
      • + *
      • Lombok 라이브러리로 생성하는 것도 허용한다.
      • + *
      • Lombok 라이브러리로 생성할 경우 가급적 @Getter와 @Setter만 사용하고 다른 어노테이션의 사용은 자제한다.
      • + *
      + *
    • + *
    + *

    조회 결과의 엑셀 다운로드

    + * 추가 예정 + *

    예외 처리

    + * 애플리케이션 동작 중 발생한 오류는 {@link ApplicationException}으로 처리한다.
    + * ApplicationException을 발생시키려면 + *
    {@code  if (result == null)
    + *     throw ApplicationException
    + *         .get(null)
    + *         .setCode("NPE")
    + *         .setMessage("결과값이 없습니다.")
    + *         .info("reason", "알 수 없음"); // 추가 정보 설정
    + * }
    + * 다른 오류를 ApplicationException으로 바꾸어 발생시키려면 + *
    {@code  try {
    + *     ...
    + * } catch(Exception e) {
    + *     throw ApplicationException.get(e);
    + * }}
    + */ package cokr.xit.foundation; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/test/TestDao.java b/src/main/java/cokr/xit/foundation/test/TestDao.java index a947223..86ef43c 100644 --- a/src/main/java/cokr/xit/foundation/test/TestDao.java +++ b/src/main/java/cokr/xit/foundation/test/TestDao.java @@ -1,43 +1,43 @@ -package cokr.xit.foundation.test; - -import org.springframework.stereotype.Repository; - -import cokr.xit.foundation.component.AbstractDao; - -/**JUnit 테스트 프로그램에서 SQL문을 실행할 때 사용하는 DAO.
    - * 주로 테스트 데이터를 지우는데 사용한다.
    - * TestDao의 매퍼 파일은 test.xml이다. - * @author mjkhan - */ -@Repository("testDao") -public class TestDao extends AbstractDao { - /**주어진 INSERT문을 실행한다. - * @param sql INSERT SQL - * @return 저장된 데이터 수 - */ - public int execInsert(String sql) { - return insert("test.insert", params().set("sql", sql)); - } - - /**주어진 UPDATE문을 실행한다. - * @param sql UPDATE SQL - * @return 저장된 데이터 수 - */ - public int execUpdate(String sql) { - return insert("test.update", params().set("sql", sql)); - } - - /**주어진 DELETE문을 실행한다. - * @param sql DELETE SQL - * @return 저장된 데이터 수 - */ - public int execDelete(String sql) { - return insert("test.delete", params().set("sql", sql)); - } - - /**실행된 SQL문을 커밋한다. - */ - public void commit() { - update("test.commit"); - } +package cokr.xit.foundation.test; + +import org.springframework.stereotype.Repository; + +import cokr.xit.foundation.component.AbstractDao; + +/**JUnit 테스트 프로그램에서 SQL문을 실행할 때 사용하는 DAO.
    + * 주로 테스트 데이터를 지우는데 사용한다.
    + * TestDao의 매퍼 파일은 test.xml이다. + * @author mjkhan + */ +@Repository("testDao") +public class TestDao extends AbstractDao { + /**주어진 INSERT문을 실행한다. + * @param sql INSERT SQL + * @return 저장된 데이터 수 + */ + public int execInsert(String sql) { + return insert("test.insert", params().set("sql", sql)); + } + + /**주어진 UPDATE문을 실행한다. + * @param sql UPDATE SQL + * @return 저장된 데이터 수 + */ + public int execUpdate(String sql) { + return insert("test.update", params().set("sql", sql)); + } + + /**주어진 DELETE문을 실행한다. + * @param sql DELETE SQL + * @return 저장된 데이터 수 + */ + public int execDelete(String sql) { + return insert("test.delete", params().set("sql", sql)); + } + + /**실행된 SQL문을 커밋한다. + */ + public void commit() { + update("test.commit"); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/test/TestSupport.java b/src/main/java/cokr/xit/foundation/test/TestSupport.java index 75e128d..3cfc0e6 100644 --- a/src/main/java/cokr/xit/foundation/test/TestSupport.java +++ b/src/main/java/cokr/xit/foundation/test/TestSupport.java @@ -1,60 +1,60 @@ -package cokr.xit.foundation.test; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.annotation.Resource; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import cokr.xit.foundation.AbstractComponent; -import cokr.xit.foundation.UserInfo; -import cokr.xit.foundation.data.DataObject; - -/**스프링 프레임워크에서 동작하는 클래스들의 단위 테스트 작성을 위한 베이스 클래스.
    - * TestSupport는 - *
    • JUnit5를 사용하며
    • - *
    • 설정파일들은 클래스패스의 spring 디렉토리(spring/context-*.xml)에 위치해야 하며
    • - *
    • "test" 프로필을 설정한다.
    • - *
    • AbstractComponent의 기능을 사용할 수 있다.
    • - *
    - *

    단위 테스트 클래스는 src/test/java 디렉토리 아래에 작성하되 테스트 대상이 되는 클래스와 같은 패키지에 위치하도록 한다. - *

    예를 들어 cokr.xit.example.business.service.BusinessService라는 인터페이스의 단위 테스트 클래스는
    - * src/test/java 디렉토리 아래에 cokr.xit.example.business.service 패키지를 만들고
    - * 그 아래에 BusinessServiceTest라는 이름으로 생성한다. - * @author mjkhan - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration("classpath:spring/context-*.xml") -@ActiveProfiles("test") -public class TestSupport extends AbstractComponent { - @Resource(name="testDao") - protected TestDao testDao; - - protected DataObject dataObject() { - return new DataObject(); - } - - /**user를 현재 사용자로 설정한다. - * @param user 사용자 객체 - */ - protected void currentUser(UserInfo user) { - UserInfo.Provider.get().setUserInfo(user); - } - - /**주어진 인자들을 각각 SQL 문자 리터럴로 변환하고, 컴마(,)로 연결하여 반환한다.
    - * SQL의 IN (...) 문장에 조건을 명시하는 것을 돕기 위한 것이다. - * @param params 인자 - * @return 컴마(,)로 연결된 SQL 문자 리터럴 - */ - protected String join(Object... params) { - return Stream.of(params).map(obj -> "'" + obj + "'").collect(Collectors.joining(",")); - } - - @AfterEach - void tearDown() {} +package cokr.xit.foundation.test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Resource; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.data.DataObject; + +/**스프링 프레임워크에서 동작하는 클래스들의 단위 테스트 작성을 위한 베이스 클래스.
    + * TestSupport는 + *

    • JUnit5를 사용하며
    • + *
    • 설정파일들은 클래스패스의 spring 디렉토리(spring/context-*.xml)에 위치해야 하며
    • + *
    • "test" 프로필을 설정한다.
    • + *
    • AbstractComponent의 기능을 사용할 수 있다.
    • + *
    + *

    단위 테스트 클래스는 src/test/java 디렉토리 아래에 작성하되 테스트 대상이 되는 클래스와 같은 패키지에 위치하도록 한다. + *

    예를 들어 cokr.xit.example.business.service.BusinessService라는 인터페이스의 단위 테스트 클래스는
    + * src/test/java 디렉토리 아래에 cokr.xit.example.business.service 패키지를 만들고
    + * 그 아래에 BusinessServiceTest라는 이름으로 생성한다. + * @author mjkhan + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration("classpath:spring/context-*.xml") +@ActiveProfiles("test") +public class TestSupport extends AbstractComponent { + @Resource(name="testDao") + protected TestDao testDao; + + protected DataObject dataObject() { + return new DataObject(); + } + + /**user를 현재 사용자로 설정한다. + * @param user 사용자 객체 + */ + protected void currentUser(UserInfo user) { + UserInfo.Provider.get().setUserInfo(user); + } + + /**주어진 인자들을 각각 SQL 문자 리터럴로 변환하고, 컴마(,)로 연결하여 반환한다.
    + * SQL의 IN (...) 문장에 조건을 명시하는 것을 돕기 위한 것이다. + * @param params 인자 + * @return 컴마(,)로 연결된 SQL 문자 리터럴 + */ + protected String join(Object... params) { + return Stream.of(params).map(obj -> "'" + obj + "'").collect(Collectors.joining(",")); + } + + @AfterEach + void tearDown() {} } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/test/package-info.java b/src/main/java/cokr/xit/foundation/test/package-info.java index 7ba23e5..b2fe99b 100644 --- a/src/main/java/cokr/xit/foundation/test/package-info.java +++ b/src/main/java/cokr/xit/foundation/test/package-info.java @@ -1,3 +1,3 @@ -/**JUnit5를 통한 단위 테스트 지원 - */ +/**JUnit5를 통한 단위 테스트 지원 + */ package cokr.xit.foundation.test; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/util/CharsEncoder.java b/src/main/java/cokr/xit/foundation/util/CharsEncoder.java index f8cf39c..24fb78e 100644 --- a/src/main/java/cokr/xit/foundation/util/CharsEncoder.java +++ b/src/main/java/cokr/xit/foundation/util/CharsEncoder.java @@ -1,74 +1,74 @@ -package cokr.xit.foundation.util; - -import java.security.MessageDigest; - -import org.apache.commons.codec.binary.Base64; - -import cokr.xit.foundation.AbstractComponent; - -/**문자열 인코더. - * @author mjkhan - */ -public class CharsEncoder extends AbstractComponent { - private String algorithm; - private boolean plainText; - - /**문자열 인크립션에 사용할 알고리즘을 반환한다. 디폴트는 SHA-256. - * @return 문자열 인크립션에 사용할 알고리즘 - */ - public String getAlgorithm() { - return ifEmpty(algorithm, "SHA-256"); - } - - /**문자열 인크립션에 사용할 알고리즘을 설정한다. 디폴트는 SHA-256. - * @param algorithm 문자열 인크립션에 사용할 알고리즘 - */ - public void setAlgorithm(String algorithm) { - this.algorithm = algorithm; - } - - /**인코딩 여부를 설정한다. - * @param plainText 인코딩 여부 - *

    • 문자열을 인코딩하지 않으려면 true
    • - *
    • 문자열을 인코딩하려면 false(디폴트)
    • - *
    - */ - public void setPlainText(boolean plainText) { - this.plainText = plainText; - } - - /**문자열을 설정된 알고리즘으로 인크립션 후 BASE64로 인코딩하여 반환한다. - * @param rawChars 문자열 - * @return 인코딩한 문자열 - */ - public String encode(CharSequence rawChars) { - if (isEmpty(rawChars)) return ""; - - String str = rawChars.toString(); - if (plainText) return str; - - try { - MessageDigest md = MessageDigest.getInstance(getAlgorithm()); - md.reset(); - md.update(str.getBytes()); - - byte[] hashValue = md.digest(str.getBytes()); - return new String(Base64.encodeBase64(hashValue)); - } catch (Exception e) { - throw applicationException(e); - } - } - - /**주어진 문자열을 인코딩한 결과가 인코딩한 문자열과 일치하는지 반환한다. - * @param rawChars 인코딩하지 않은 일반 문자열 - * @param encodedChars 인코딩한 문자열 - * @return - *
    • 두 문자열의 인코딩 결과가 같으면 true
    • - *
    • 그렇지 않으면 false
    • - *
    - */ - protected boolean matches(CharSequence rawChars, String encodedChars) { - String encoded = encode(rawChars); - return encoded.equals(encodedChars); - } +package cokr.xit.foundation.util; + +import java.security.MessageDigest; + +import org.apache.commons.codec.binary.Base64; + +import cokr.xit.foundation.AbstractComponent; + +/**문자열 인코더. + * @author mjkhan + */ +public class CharsEncoder extends AbstractComponent { + private String algorithm; + private boolean plainText; + + /**문자열 인크립션에 사용할 알고리즘을 반환한다. 디폴트는 SHA-256. + * @return 문자열 인크립션에 사용할 알고리즘 + */ + public String getAlgorithm() { + return ifEmpty(algorithm, "SHA-256"); + } + + /**문자열 인크립션에 사용할 알고리즘을 설정한다. 디폴트는 SHA-256. + * @param algorithm 문자열 인크립션에 사용할 알고리즘 + */ + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + /**인코딩 여부를 설정한다. + * @param plainText 인코딩 여부 + *
    • 문자열을 인코딩하지 않으려면 true
    • + *
    • 문자열을 인코딩하려면 false(디폴트)
    • + *
    + */ + public void setPlainText(boolean plainText) { + this.plainText = plainText; + } + + /**문자열을 설정된 알고리즘으로 인크립션 후 BASE64로 인코딩하여 반환한다. + * @param rawChars 문자열 + * @return 인코딩한 문자열 + */ + public String encode(CharSequence rawChars) { + if (isEmpty(rawChars)) return ""; + + String str = rawChars.toString(); + if (plainText) return str; + + try { + MessageDigest md = MessageDigest.getInstance(getAlgorithm()); + md.reset(); + md.update(str.getBytes()); + + byte[] hashValue = md.digest(str.getBytes()); + return new String(Base64.encodeBase64(hashValue)); + } catch (Exception e) { + throw applicationException(e); + } + } + + /**주어진 문자열을 인코딩한 결과가 인코딩한 문자열과 일치하는지 반환한다. + * @param rawChars 인코딩하지 않은 일반 문자열 + * @param encodedChars 인코딩한 문자열 + * @return + *
    • 두 문자열의 인코딩 결과가 같으면 true
    • + *
    • 그렇지 않으면 false
    • + *
    + */ + protected boolean matches(CharSequence rawChars, String encodedChars) { + String encoded = encode(rawChars); + return encoded.equals(encodedChars); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/util/RandomNumber.java b/src/main/java/cokr/xit/foundation/util/RandomNumber.java index 4037191..2a93188 100644 --- a/src/main/java/cokr/xit/foundation/util/RandomNumber.java +++ b/src/main/java/cokr/xit/foundation/util/RandomNumber.java @@ -1,25 +1,25 @@ -package cokr.xit.foundation.util; - -import java.util.concurrent.ThreadLocalRandom; - -/**난수 발생기 - * @author mjkhan - */ -public class RandomNumber { - /**지정한 자릿수의 난수를 반환한다. - * @param digit 자릿수 - * @return 지정한 자릿수의 난수 - */ - public static final String get(int digit) { - if (digit < 1) - throw new IllegalArgumentException("digit < 1"); - - ThreadLocalRandom random = ThreadLocalRandom.current(); - StringBuilder buff = new StringBuilder(); - for (int i = 0; i < digit; ++i) { - buff.append(Integer.toString(random.nextInt(10))); - } - - return buff.toString(); - } +package cokr.xit.foundation.util; + +import java.util.concurrent.ThreadLocalRandom; + +/**난수 발생기 + * @author mjkhan + */ +public class RandomNumber { + /**지정한 자릿수의 난수를 반환한다. + * @param digit 자릿수 + * @return 지정한 자릿수의 난수 + */ + public static final String get(int digit) { + if (digit < 1) + throw new IllegalArgumentException("digit < 1"); + + ThreadLocalRandom random = ThreadLocalRandom.current(); + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < digit; ++i) { + buff.append(Integer.toString(random.nextInt(10))); + } + + return buff.toString(); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/util/package-info.java b/src/main/java/cokr/xit/foundation/util/package-info.java index f22e4d6..1ad633e 100644 --- a/src/main/java/cokr/xit/foundation/util/package-info.java +++ b/src/main/java/cokr/xit/foundation/util/package-info.java @@ -1,3 +1,3 @@ -/**인코딩 및 난수 발생 유틸리티 - */ +/**인코딩 및 난수 발생 유틸리티 + */ package cokr.xit.foundation.util; \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/AbstractController.java b/src/main/java/cokr/xit/foundation/web/AbstractController.java index a5c8ba6..0767aa7 100644 --- a/src/main/java/cokr/xit/foundation/web/AbstractController.java +++ b/src/main/java/cokr/xit/foundation/web/AbstractController.java @@ -1,202 +1,202 @@ -package cokr.xit.foundation.web; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.net.URLEncoder; -import java.util.Collection; -import java.util.List; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.util.FileCopyUtils; -import org.springframework.web.servlet.ModelAndView; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import cokr.xit.foundation.AbstractComponent; -import cokr.xit.foundation.Access; -import cokr.xit.foundation.component.QueryRequest; -import cokr.xit.foundation.data.paging.BoundedList; - -/**스프링 MVC에서 동작할 컨트롤러의 베이스 클래스.
    - * 애플리케이션의 컨트롤러 클래스는 이 클래스를 상속받아 정의한다. - *

    AbstractController를 상속받아 정의하는 컨트롤러는 - *

    • AbstractComponent의 기능
    • - *
    • {@link #ajaxRequest() 요청이 AJAX에 의한 것인지} 확인
    • - *
    • {@link #jsonResponse() 응답을 JSON으로 해야 하는지} 확인
    • - *
    • {@link #toJson(Object) JSON 문자열}이나 {@link #fromJson(String, Class) 객체}로의 변환
    • - *
    • {@link #setCollectionInfo(ModelAndView, List, String) List의 ModelAndView 설정}
    • - *
    • {@link #download(InputStream, String, String, long, String, HttpServletResponse) 파일 다운로드}
    • - *
    - * 등의 기능을 물려 받는다. - *

    그리고 '업무 용어' + Controller와 같이 이름을 붙이고, @Controller 어노테이션을 명시한다. - *

    컨트롤러의 클래스와 메소드에 명시하는 @RequestMapping, @PostMapping 어노테이션에는 이름과 url을 설정한다.
    - *

     @RequestMapping(name = "홈", value = "index.do")
    - * public String index() {return "index";}
    - *

    컨트롤러의 메소드가 - *

    • 읽기 기능이면 @RequestMapping
    • - *
    • 쓰기 기능이면 @PostMapping
    • - *
    - * 을 명시한다. - * @author mjkhan - */ -public abstract class AbstractController extends AbstractComponent { - @Resource(name="objectMapper") - private ObjectMapper objectMapper; - - /**현재 요청이 AJAX로 전달된 것인지 반환한다. - * @return - *
    • AJAX 요청이면 true
    • - *
    • 그렇지 않으면 false
    • - *
    - */ - protected boolean ajaxRequest() { - return Access.current().isAjaxRequest(); - } - - /**현재 요청의 응답을 JSON으로 응답하는지 반환한다. - * @return - *
    • JSON 응답이면 true
    • - *
    • 그렇지 않으면 false
    • - *
    - */ - protected boolean jsonResponse() { - return Access.current().isJsonResponse(); - } - - /**obj를 JSON 문자열로 변환하여 반환한다. - * @param obj 객체 - * @return JSON 문자열 - */ - protected String toJson(Object obj) { - try { - return objectMapper.writeValueAsString(obj); - } catch (Exception e) { - throw applicationException(e); - } - } - - /**JSON 문자열을 읽어 klass의 객체를 반환한다. - * @param 객체 타입 - * @param content JSON 문자열 - * @param klass 클래스 - * @return klass의 객체 - */ - protected T fromJson(String content, Class klass) { - try { - return objectMapper.readValue(content, klass); - } catch (Exception e) { - throw applicationException(e); - } - } - - /**ModelAndView에 list와 관련정보를 설정한다.
    - * 예를 들어 setCollectionInfo(mav, list, "obj")와 같이 호출하면 - * mav에 다음과 같이 설정한다. - *
    {
    -	 *     "infoPrefix": "obj",
    -	 *     "objList": list,
    -	 *     "objStart": list.getStart(),
    -	 *     "objFetch": list.getFetchSize(),
    -	 *     "objTotal": list.getTotalSize(),
    -	 * }
    - * @param mav ModelAndView - * @param collection Collection - * @param prefix 접두어 - * @return ModelAndView - */ - protected ModelAndView setCollectionInfo(ModelAndView mav, Collection collection, String prefix) { - return setCollectionInfo(mav, collection, "infoPrefix", prefix); - } - - /**ModelAndView에 list와 관련정보를 설정한다.
    - * 예를 들어 setCollectionInfo(mav, list, "yourPrefix", "obj")와 같이 호출하면 - * mav에 다음과 같이 설정한다. - *
    {
    -	 *     "yourPrefix": "obj",
    -	 *     "objList": list,
    -	 *     "objStart": list.getStart(),
    -	 *     "objFetch": list.getFetchSize(),
    -	 *     "objTotal": list.getTotalSize(),
    -	 * }
    - * @param mav ModelAndView - * @param collection Collection - * @param prefixKey 접두어를 저장할 키이름 - * @param prefix 접두어 - * @return ModelAndView - */ - protected ModelAndView setCollectionInfo(ModelAndView mav, Collection collection, String prefixKey, String prefix) { - mav.addObject(prefixKey, prefix) - .addObject(prefix + "List", collection) - .addObject(prefix + "Start", 0) - .addObject(prefix + "Fetch", collection.size()) - .addObject(prefix + "Total", collection.size()); - if (!(collection instanceof BoundedList)) - return mav; - - BoundedList bounded = (BoundedList)collection; - return mav.addObject(prefix + "Start", bounded.getStart()) - .addObject(prefix + "Fetch", bounded.getFetchSize()) - .addObject(prefix + "Total", bounded.getTotalSize()); - } - - protected T setFetchSize(T req) { - if (req.getFetchSize() > 0 - || !isEmpty(req.getDownload())) return req; - - return req.setFetchSize(properties.getInt("pageSize")); - } - - /**InputStream을 지정한 유형과 이름으로 다운로드 한다. - * @param input 다운로드할 파일의 InputStream - * @param contentType 컨텐트 유형. 지정하지 않으면 application/octet-stream - * @param filename 다운로드됐을 때 사용자가 보게되는 파일 이름 - * @param length 다운로드할 파일의 길이 - * @param disposition 'attachment', 또는 'inline'. 지정하지 않으면 attachment - * @param hresp HttpServletResponse - * @throws Exception - */ - protected void download(InputStream input, String contentType, String filename, long length, String disposition, HttpServletResponse hresp) throws Exception { - if (input == null) { - hresp.setStatus(HttpServletResponse.SC_NOT_FOUND); - hresp.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - hresp.setCharacterEncoding("UTF-8"); - hresp.setContentType(ifEmpty(contentType, "application/octet-stream")); - hresp.setHeader("Content-Disposition", ifEmpty(disposition, "attachment") + "; filename=\"" + URLEncoder.encode(filename, "UTF-8") +"\""); - hresp.setContentLengthLong(length); - FileCopyUtils.copy(input, hresp.getOutputStream()); - } - - /**file을 지정한 유형과 이름으로 다운로드한다. - * @param file 다운로드할 파일 - * @param contentType 컨텐트 유형. 지정하지 않으면 application/octet-stream - * @param filename 다운로드됐을 때 사용자가 보게되는 파일 이름. 지정하지 않으면 file의 이름을 사용한다. - * @param disposition 'attachment', 또는 'inline'. 지정하지 않으면 attachment - * @param hresp HttpServletResponse - * @throws Exception - */ - protected void download(File file, String contentType, String filename, String disposition, HttpServletResponse hresp) throws Exception { - if (file == null) { - download(null, null, null, 0, null, hresp); - return; - } - try (FileInputStream in = new FileInputStream(file)) { - download(in, contentType, ifEmpty(filename, file.getName()), file.length(), disposition, hresp); - } - } - - /**임시 디렉토리 경로에 파일 경로를 추가하여 반환한다.
    - * 임시 디렉토리 경로는 프로퍼티 파일에 tempDir 키로 설정한다.
    - * tempDir 프로퍼티가 설정되어 있지 않으면 시스템 프로퍼티(java.io.tmpdir)의 값을 사용한다. - * @param path 파일 경로 - * @return 임시 디렉토리 경로 + 파일 경로 - */ - protected String tempPath(String path) { - String dir = ifEmpty(properties.getString("tempDir"), System.getProperty("java.io.tmpdir")).replace("\\", "/"); - return dir.endsWith("/") || path.startsWith("/") ? dir + path : dir + "/" + path; - } +package cokr.xit.foundation.web; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.List; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.FileCopyUtils; +import org.springframework.web.servlet.ModelAndView; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.Access; +import cokr.xit.foundation.component.QueryRequest; +import cokr.xit.foundation.data.paging.BoundedList; + +/**스프링 MVC에서 동작할 컨트롤러의 베이스 클래스.
    + * 애플리케이션의 컨트롤러 클래스는 이 클래스를 상속받아 정의한다. + *

    AbstractController를 상속받아 정의하는 컨트롤러는 + *

    • AbstractComponent의 기능
    • + *
    • {@link #ajaxRequest() 요청이 AJAX에 의한 것인지} 확인
    • + *
    • {@link #jsonResponse() 응답을 JSON으로 해야 하는지} 확인
    • + *
    • {@link #toJson(Object) JSON 문자열}이나 {@link #fromJson(String, Class) 객체}로의 변환
    • + *
    • {@link #setCollectionInfo(ModelAndView, List, String) List의 ModelAndView 설정}
    • + *
    • {@link #download(InputStream, String, String, long, String, HttpServletResponse) 파일 다운로드}
    • + *
    + * 등의 기능을 물려 받는다. + *

    그리고 '업무 용어' + Controller와 같이 이름을 붙이고, @Controller 어노테이션을 명시한다. + *

    컨트롤러의 클래스와 메소드에 명시하는 @RequestMapping, @PostMapping 어노테이션에는 이름과 url을 설정한다.
    + *

     @RequestMapping(name = "홈", value = "index.do")
    + * public String index() {return "index";}
    + *

    컨트롤러의 메소드가 + *

    • 읽기 기능이면 @RequestMapping
    • + *
    • 쓰기 기능이면 @PostMapping
    • + *
    + * 을 명시한다. + * @author mjkhan + */ +public abstract class AbstractController extends AbstractComponent { + @Resource(name="objectMapper") + private ObjectMapper objectMapper; + + /**현재 요청이 AJAX로 전달된 것인지 반환한다. + * @return + *
    • AJAX 요청이면 true
    • + *
    • 그렇지 않으면 false
    • + *
    + */ + protected boolean ajaxRequest() { + return Access.current().isAjaxRequest(); + } + + /**현재 요청의 응답을 JSON으로 응답하는지 반환한다. + * @return + *
    • JSON 응답이면 true
    • + *
    • 그렇지 않으면 false
    • + *
    + */ + protected boolean jsonResponse() { + return Access.current().isJsonResponse(); + } + + /**obj를 JSON 문자열로 변환하여 반환한다. + * @param obj 객체 + * @return JSON 문자열 + */ + protected String toJson(Object obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (Exception e) { + throw applicationException(e); + } + } + + /**JSON 문자열을 읽어 klass의 객체를 반환한다. + * @param 객체 타입 + * @param content JSON 문자열 + * @param klass 클래스 + * @return klass의 객체 + */ + protected T fromJson(String content, Class klass) { + try { + return objectMapper.readValue(content, klass); + } catch (Exception e) { + throw applicationException(e); + } + } + + /**ModelAndView에 list와 관련정보를 설정한다.
    + * 예를 들어 setCollectionInfo(mav, list, "obj")와 같이 호출하면 + * mav에 다음과 같이 설정한다. + *
    {
    +	 *     "infoPrefix": "obj",
    +	 *     "objList": list,
    +	 *     "objStart": list.getStart(),
    +	 *     "objFetch": list.getFetchSize(),
    +	 *     "objTotal": list.getTotalSize(),
    +	 * }
    + * @param mav ModelAndView + * @param collection Collection + * @param prefix 접두어 + * @return ModelAndView + */ + protected ModelAndView setCollectionInfo(ModelAndView mav, Collection collection, String prefix) { + return setCollectionInfo(mav, collection, "infoPrefix", prefix); + } + + /**ModelAndView에 list와 관련정보를 설정한다.
    + * 예를 들어 setCollectionInfo(mav, list, "yourPrefix", "obj")와 같이 호출하면 + * mav에 다음과 같이 설정한다. + *
    {
    +	 *     "yourPrefix": "obj",
    +	 *     "objList": list,
    +	 *     "objStart": list.getStart(),
    +	 *     "objFetch": list.getFetchSize(),
    +	 *     "objTotal": list.getTotalSize(),
    +	 * }
    + * @param mav ModelAndView + * @param collection Collection + * @param prefixKey 접두어를 저장할 키이름 + * @param prefix 접두어 + * @return ModelAndView + */ + protected ModelAndView setCollectionInfo(ModelAndView mav, Collection collection, String prefixKey, String prefix) { + mav.addObject(prefixKey, prefix) + .addObject(prefix + "List", collection) + .addObject(prefix + "Start", 0) + .addObject(prefix + "Fetch", collection.size()) + .addObject(prefix + "Total", collection.size()); + if (!(collection instanceof BoundedList)) + return mav; + + BoundedList bounded = (BoundedList)collection; + return mav.addObject(prefix + "Start", bounded.getStart()) + .addObject(prefix + "Fetch", bounded.getFetchSize()) + .addObject(prefix + "Total", bounded.getTotalSize()); + } + + protected T setFetchSize(T req) { + if (req.getFetchSize() > 0 + || !isEmpty(req.getDownload())) return req; + + return req.setFetchSize(properties.getInt("pageSize")); + } + + /**InputStream을 지정한 유형과 이름으로 다운로드 한다. + * @param input 다운로드할 파일의 InputStream + * @param contentType 컨텐트 유형. 지정하지 않으면 application/octet-stream + * @param filename 다운로드됐을 때 사용자가 보게되는 파일 이름 + * @param length 다운로드할 파일의 길이 + * @param disposition 'attachment', 또는 'inline'. 지정하지 않으면 attachment + * @param hresp HttpServletResponse + * @throws Exception + */ + protected void download(InputStream input, String contentType, String filename, long length, String disposition, HttpServletResponse hresp) throws Exception { + if (input == null) { + hresp.setStatus(HttpServletResponse.SC_NOT_FOUND); + hresp.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + hresp.setCharacterEncoding("UTF-8"); + hresp.setContentType(ifEmpty(contentType, "application/octet-stream")); + hresp.setHeader("Content-Disposition", ifEmpty(disposition, "attachment") + "; filename=\"" + URLEncoder.encode(filename, "UTF-8") +"\""); + hresp.setContentLengthLong(length); + FileCopyUtils.copy(input, hresp.getOutputStream()); + } + + /**file을 지정한 유형과 이름으로 다운로드한다. + * @param file 다운로드할 파일 + * @param contentType 컨텐트 유형. 지정하지 않으면 application/octet-stream + * @param filename 다운로드됐을 때 사용자가 보게되는 파일 이름. 지정하지 않으면 file의 이름을 사용한다. + * @param disposition 'attachment', 또는 'inline'. 지정하지 않으면 attachment + * @param hresp HttpServletResponse + * @throws Exception + */ + protected void download(File file, String contentType, String filename, String disposition, HttpServletResponse hresp) throws Exception { + if (file == null) { + download(null, null, null, 0, null, hresp); + return; + } + try (FileInputStream in = new FileInputStream(file)) { + download(in, contentType, ifEmpty(filename, file.getName()), file.length(), disposition, hresp); + } + } + + /**임시 디렉토리 경로에 파일 경로를 추가하여 반환한다.
    + * 임시 디렉토리 경로는 프로퍼티 파일에 tempDir 키로 설정한다.
    + * tempDir 프로퍼티가 설정되어 있지 않으면 시스템 프로퍼티(java.io.tmpdir)의 값을 사용한다. + * @param path 파일 경로 + * @return 임시 디렉토리 경로 + 파일 경로 + */ + protected String tempPath(String path) { + String dir = ifEmpty(properties.getString("tempDir"), System.getProperty("java.io.tmpdir")).replace("\\", "/"); + return dir.endsWith("/") || path.startsWith("/") ? dir + path : dir + "/" + path; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/AccessFilter.java b/src/main/java/cokr/xit/foundation/web/AccessFilter.java index b16bcd1..b237704 100644 --- a/src/main/java/cokr/xit/foundation/web/AccessFilter.java +++ b/src/main/java/cokr/xit/foundation/web/AccessFilter.java @@ -1,49 +1,49 @@ -package cokr.xit.foundation.web; - -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -import cokr.xit.foundation.Access; - -/**사용자의 {@link Access 접근정보}를 추출하는 필터.
    - * 추출한 접근정보는 - *
    • 현재 스레드에 바인딩하고
    • - *
    • 서블릿 요청에 "currentAccess"라는 이름의 애트리뷰트로 저장한다.
    • - *
    - * @author mjkhan - */ -public class AccessFilter implements Filter { - @Override - public void init(FilterConfig filterConfig) throws ServletException {} - - @Override - public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException, ServletException { - HttpServletRequest hreq = (HttpServletRequest)sreq; - String action = hreq.getRequestURI().replace(hreq.getContextPath(), ""); - HttpSession session = hreq.getSession(false); - - Access access = new Access() - .setAction(action) - .setNewSession(session == null || session.isNew()) - .setMobile(hreq.getHeader("User-Agent").contains("Mobi")) - .setSessionID(session != null ? session.getId() : null) - .setAjaxRequest(hreq.getHeader("X-Requested-With")) - .setJsonResponse(hreq.getHeader("accept")) - .setCurrent(); - - hreq.setAttribute("currentAccess", access); - - chain.doFilter(sreq, sresp); - } - - @Override - public void destroy() {} +package cokr.xit.foundation.web; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import cokr.xit.foundation.Access; + +/**사용자의 {@link Access 접근정보}를 추출하는 필터.
    + * 추출한 접근정보는 + *
    • 현재 스레드에 바인딩하고
    • + *
    • 서블릿 요청에 "currentAccess"라는 이름의 애트리뷰트로 저장한다.
    • + *
    + * @author mjkhan + */ +public class AccessFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException {} + + @Override + public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException, ServletException { + HttpServletRequest hreq = (HttpServletRequest)sreq; + String action = hreq.getRequestURI().replace(hreq.getContextPath(), ""); + HttpSession session = hreq.getSession(false); + + Access access = new Access() + .setAction(action) + .setNewSession(session == null || session.isNew()) + .setMobile(hreq.getHeader("User-Agent").contains("Mobi")) + .setSessionID(session != null ? session.getId() : null) + .setAjaxRequest(hreq.getHeader("X-Requested-With")) + .setJsonResponse(hreq.getHeader("accept")) + .setCurrent(); + + hreq.setAttribute("currentAccess", access); + + chain.doFilter(sreq, sresp); + } + + @Override + public void destroy() {} } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/AccessInitializer.java b/src/main/java/cokr/xit/foundation/web/AccessInitializer.java index 96aa917..f16230a 100644 --- a/src/main/java/cokr/xit/foundation/web/AccessInitializer.java +++ b/src/main/java/cokr/xit/foundation/web/AccessInitializer.java @@ -1,108 +1,108 @@ -package cokr.xit.foundation.web; - -import java.text.SimpleDateFormat; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.ModelAndView; - -import cokr.xit.foundation.Access; -import cokr.xit.foundation.ApplicationContainer; -import cokr.xit.foundation.UserInfo; -import cokr.xit.foundation.component.ServiceRequest; - -/**클라이언트가 시스템에 접근했을 때 메뉴정보, 클라이언트의 접근정보를 HttpServletRequest의 애트리뷰트로 설정하여, - * 시스템의 다른 컴포넌트(예:JSP)에서 사용할 수 있도록 한다. - *
    • "applicationContainer" - {@link ApplicationContainer}
    • - *
    • "currentUser" - 접근한 ({@link UserInfo 클라이언트})
    • - *
    • "currentAccess" - 클라이언트의 {@link Access 접근정보}
    • - *
    - * - *

    AccessInitializer가 동작하려면 인터셉터로 설정해야 한다.

    - */ -public class AccessInitializer extends RequestInterceptor { - @Resource(name="applicationContainer") - protected ApplicationContainer applicationContainer; - @Resource(name="localeResolver") - protected LocaleResolver localeResolver; - - private String version; - - @Override - public boolean preHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception { - log().debug("Initializing access information for the current request..."); - hreq.setAttribute("applicationContainer", applicationContainer); - - setVersion(hreq); - initUserAccess(hreq); - return super.preHandle(hreq, hresp, handler); - } - - @Override - public void postHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, ModelAndView mav) throws Exception { - Map model = mav.getModel(); - model.entrySet().stream() - .filter(entry -> entry.getValue() instanceof ServiceRequest) - .map(entry -> entry.getKey()) - .collect(Collectors.toList()) - .forEach(model::remove); - } - - /**애플리케이션의 시작시간으로 "ver=yyMMddHHmm"이라는 문자열을 만들어 서블릿 요청에 "ver"라는 이름의 애트리뷰트로 저장한다. - * @param hreq 서블릿 요청 - */ - protected void setVersion(HttpServletRequest hreq) { - if (isEmpty(version)) - version = "ver=" + new SimpleDateFormat("yyMMddHHmm").format(applicationContainer.getStartupDate()); - hreq.setAttribute("ver", version); - } - - /**현재 접근한 사용자의 정보를 추출하여 서블릿 요청에 "currentUser"라는 이름의 애트리뷰트로 저장한다. - * @param hreq - */ - protected void initUserAccess(HttpServletRequest hreq) { - UserInfo currentUser = UserInfo.current(); - hreq.setAttribute("currentUser", currentUser); - hreq.setAttribute("production", applicationContainer.isProfileActive("production")); - log().debug("{} accessing the system.", currentUser); - - Locale locale = localeResolver.resolveLocale(hreq); - - Access.current() - .setIpAddress(getClientAddress(hreq)) - .setLocale(locale); - } - - private static final String UNKNOWN = "unknown"; - private static final String[] HEADERS = {"x-forwarded-for", "X-FORWARDED-FOR", "WL-Proxy-Client-IP", "HTTP_X_FORWARDED_FOR"}; - - private String getClientAddress(HttpServletRequest hreq) { - for (String header: HEADERS) { - String addr = hreq.getHeader(header); - if (!isEmpty(addr) && !UNKNOWN.equalsIgnoreCase(addr)) - return addr; - } - - return Access.getClientAddress(hreq.getRemoteAddr(), applicationContainer.getHostAddress()); - } - - @Override - public void afterCompletion(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, Exception ex) throws Exception { - super.afterCompletion(hreq, hresp, handler, ex); - Access.release(); - log().debug("Access information cleared for the current request."); - } - - @Override - public void afterConcurrentHandlingStarted(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception { - super.afterConcurrentHandlingStarted(hreq, hresp, handler); - Access.release(); - log().debug("Access information cleared for the current request."); - } +package cokr.xit.foundation.web; + +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ModelAndView; + +import cokr.xit.foundation.Access; +import cokr.xit.foundation.ApplicationContainer; +import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.component.ServiceRequest; + +/**클라이언트가 시스템에 접근했을 때 메뉴정보, 클라이언트의 접근정보를 HttpServletRequest의 애트리뷰트로 설정하여, + * 시스템의 다른 컴포넌트(예:JSP)에서 사용할 수 있도록 한다. + *
    • "applicationContainer" - {@link ApplicationContainer}
    • + *
    • "currentUser" - 접근한 ({@link UserInfo 클라이언트})
    • + *
    • "currentAccess" - 클라이언트의 {@link Access 접근정보}
    • + *
    + * + *

    AccessInitializer가 동작하려면 인터셉터로 설정해야 한다.

    + */ +public class AccessInitializer extends RequestInterceptor { + @Resource(name="applicationContainer") + protected ApplicationContainer applicationContainer; + @Resource(name="localeResolver") + protected LocaleResolver localeResolver; + + private String version; + + @Override + public boolean preHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception { + log().debug("Initializing access information for the current request..."); + hreq.setAttribute("applicationContainer", applicationContainer); + + setVersion(hreq); + initUserAccess(hreq); + return super.preHandle(hreq, hresp, handler); + } + + @Override + public void postHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, ModelAndView mav) throws Exception { + Map model = mav.getModel(); + model.entrySet().stream() + .filter(entry -> entry.getValue() instanceof ServiceRequest) + .map(entry -> entry.getKey()) + .collect(Collectors.toList()) + .forEach(model::remove); + } + + /**애플리케이션의 시작시간으로 "ver=yyMMddHHmm"이라는 문자열을 만들어 서블릿 요청에 "ver"라는 이름의 애트리뷰트로 저장한다. + * @param hreq 서블릿 요청 + */ + protected void setVersion(HttpServletRequest hreq) { + if (isEmpty(version)) + version = "ver=" + new SimpleDateFormat("yyMMddHHmm").format(applicationContainer.getStartupDate()); + hreq.setAttribute("ver", version); + } + + /**현재 접근한 사용자의 정보를 추출하여 서블릿 요청에 "currentUser"라는 이름의 애트리뷰트로 저장한다. + * @param hreq + */ + protected void initUserAccess(HttpServletRequest hreq) { + UserInfo currentUser = UserInfo.current(); + hreq.setAttribute("currentUser", currentUser); + hreq.setAttribute("production", applicationContainer.isProfileActive("production")); + log().debug("{} accessing the system.", currentUser); + + Locale locale = localeResolver.resolveLocale(hreq); + + Access.current() + .setIpAddress(getClientAddress(hreq)) + .setLocale(locale); + } + + private static final String UNKNOWN = "unknown"; + private static final String[] HEADERS = {"x-forwarded-for", "X-FORWARDED-FOR", "WL-Proxy-Client-IP", "HTTP_X_FORWARDED_FOR"}; + + private String getClientAddress(HttpServletRequest hreq) { + for (String header: HEADERS) { + String addr = hreq.getHeader(header); + if (!isEmpty(addr) && !UNKNOWN.equalsIgnoreCase(addr)) + return addr; + } + + return Access.getClientAddress(hreq.getRemoteAddr(), applicationContainer.getHostAddress()); + } + + @Override + public void afterCompletion(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, Exception ex) throws Exception { + super.afterCompletion(hreq, hresp, handler, ex); + Access.release(); + log().debug("Access information cleared for the current request."); + } + + @Override + public void afterConcurrentHandlingStarted(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception { + super.afterConcurrentHandlingStarted(hreq, hresp, handler); + Access.release(); + log().debug("Access information cleared for the current request."); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/ExceptionController.java b/src/main/java/cokr/xit/foundation/web/ExceptionController.java index d7adec9..0a9ab20 100644 --- a/src/main/java/cokr/xit/foundation/web/ExceptionController.java +++ b/src/main/java/cokr/xit/foundation/web/ExceptionController.java @@ -1,109 +1,109 @@ -package cokr.xit.foundation.web; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import cokr.xit.foundation.Access; -import cokr.xit.foundation.ApplicationException; - -/**예외 처리를 위한 정보를 갖는 ModelAndView를 생성하여 반환한다. - * @author mjkhan - */ -@Controller -@ControllerAdvice -public class ExceptionController extends AbstractController { - /**사용자가 실행을 요청한 uri를 반환한다. - * @param hreq 서블릿 요청 - * @return 사용자가 실행을 요청한 uri - */ - protected String getAction(HttpServletRequest hreq) { - return (String)hreq.getAttribute("javax.servlet.forward.request_uri"); - } - - /**pageNotFound 오류의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. - * @param hreq 서블릿 요청 - * @return ModelAndView - */ - @RequestMapping("/error/pageNotFound.do") - public ModelAndView pageNotFound(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", getAction(hreq)) - .addObject("failed", true) - .addObject("status", 404) - .addObject("message", message("pageNotFound")); - } - - /**세션 만료 시 처리를 위한 정보를 갖는 ModelAndView를 반환한다. - * @param hreq 서블릿 요청 - * @return ModelAndView - */ - @RequestMapping("/error/sessionExpired.do") - public ModelAndView sessionExpired(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", getAction(hreq)) - .addObject("failed", true) - .addObject("status", "sessionExpired") - .addObject("message", message("sessionExpired")) - .addObject("home", true); - } - - /**접근 거부 오류의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. - * @param hreq 서블릿 요청 - * @return ModelAndView - */ - @RequestMapping("/error/accessDenied.do") - public ModelAndView accessDenied(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", Access.current().getAction()) - .addObject("failed", true) - .addObject("status", "accessDenied") - .addObject("message", message("accessDenied")); - } - - /**ApplicationException 처리를 위한 정보를 갖는 ModelAndView를 반환한다. - * @param e ApplicationException - * @return ModelAndView - */ - @ExceptionHandler(ApplicationException.class) - public ModelAndView onException(ApplicationException e) { - return onException((Throwable)e) - .addObject("errorCode", e.getCode()); - } - - /**Throwable의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. - * @param e Throwable - * @return ModelAndView - */ - @ExceptionHandler(Throwable.class) - public ModelAndView onException(Throwable e) { - Throwable cause = rootCause(e); - - String action = currentAccess().getAction(), - description = cause.getClass().getSimpleName() + " from " + currentAccess(), - stacktrace = getStackTrace(cause); - log().error(description); - log().error(stacktrace); - - return new ModelAndView(ajaxRequest() || jsonResponse() ? "jsonView" : "error/errorPage") - .addObject("status", 500) - .addObject("failed", true) - .addObject("description", description) - .addObject("path", action) - .addObject("message", cause.getMessage()) - .addObject("stacktrace", stacktrace); - } - - private static String getStackTrace(Throwable throwable) { - StringWriter sw = new StringWriter(); - throwable.printStackTrace(new PrintWriter(sw, true)); - return sw.getBuffer().toString(); - } +package cokr.xit.foundation.web; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import cokr.xit.foundation.Access; +import cokr.xit.foundation.ApplicationException; + +/**예외 처리를 위한 정보를 갖는 ModelAndView를 생성하여 반환한다. + * @author mjkhan + */ +@Controller +@ControllerAdvice +public class ExceptionController extends AbstractController { + /**사용자가 실행을 요청한 uri를 반환한다. + * @param hreq 서블릿 요청 + * @return 사용자가 실행을 요청한 uri + */ + protected String getAction(HttpServletRequest hreq) { + return (String)hreq.getAttribute("javax.servlet.forward.request_uri"); + } + + /**pageNotFound 오류의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. + * @param hreq 서블릿 요청 + * @return ModelAndView + */ + @RequestMapping("/error/pageNotFound.do") + public ModelAndView pageNotFound(HttpServletRequest hreq) { + return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") + .addObject("path", getAction(hreq)) + .addObject("failed", true) + .addObject("status", 404) + .addObject("message", message("pageNotFound")); + } + + /**세션 만료 시 처리를 위한 정보를 갖는 ModelAndView를 반환한다. + * @param hreq 서블릿 요청 + * @return ModelAndView + */ + @RequestMapping("/error/sessionExpired.do") + public ModelAndView sessionExpired(HttpServletRequest hreq) { + return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") + .addObject("path", getAction(hreq)) + .addObject("failed", true) + .addObject("status", "sessionExpired") + .addObject("message", message("sessionExpired")) + .addObject("home", true); + } + + /**접근 거부 오류의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. + * @param hreq 서블릿 요청 + * @return ModelAndView + */ + @RequestMapping("/error/accessDenied.do") + public ModelAndView accessDenied(HttpServletRequest hreq) { + return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") + .addObject("path", Access.current().getAction()) + .addObject("failed", true) + .addObject("status", "accessDenied") + .addObject("message", message("accessDenied")); + } + + /**ApplicationException 처리를 위한 정보를 갖는 ModelAndView를 반환한다. + * @param e ApplicationException + * @return ModelAndView + */ + @ExceptionHandler(ApplicationException.class) + public ModelAndView onException(ApplicationException e) { + return onException((Throwable)e) + .addObject("errorCode", e.getCode()); + } + + /**Throwable의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. + * @param e Throwable + * @return ModelAndView + */ + @ExceptionHandler(Throwable.class) + public ModelAndView onException(Throwable e) { + Throwable cause = rootCause(e); + + String action = currentAccess().getAction(), + description = cause.getClass().getSimpleName() + " from " + currentAccess(), + stacktrace = getStackTrace(cause); + log().error(description); + log().error(stacktrace); + + return new ModelAndView(ajaxRequest() || jsonResponse() ? "jsonView" : "error/errorPage") + .addObject("status", 500) + .addObject("failed", true) + .addObject("description", description) + .addObject("path", action) + .addObject("message", cause.getMessage()) + .addObject("stacktrace", stacktrace); + } + + private static String getStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sw, true)); + return sw.getBuffer().toString(); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/RequestHandlerReader.java b/src/main/java/cokr/xit/foundation/web/RequestHandlerReader.java index 6c7c9cf..381e9e7 100644 --- a/src/main/java/cokr/xit/foundation/web/RequestHandlerReader.java +++ b/src/main/java/cokr/xit/foundation/web/RequestHandlerReader.java @@ -1,77 +1,77 @@ -package cokr.xit.foundation.web; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -import cokr.xit.foundation.AbstractComponent; -import cokr.xit.foundation.data.DataObject; -import cokr.xit.foundation.data.hierarchy.Hierarchy; -import cokr.xit.foundation.data.hierarchy.HierarchyBuilder; - -/**컨트롤러에 명시된 {@code @RequestMapping} 어노테이션을 읽어들인다. - * @author mjkhan - */ -public class RequestHandlerReader extends AbstractComponent { - /**컨트롤러에 명시된 {@code @RequestMapping} 어노테이션을 읽어들여, {"name", "url", "parentID"} 정보를 추출하여 목록으로 반환한다. - * @param requestHandlerMappings 요청 매핑 - * @return {"name", "url", "parentID"} 정보 목록 - */ - public List read(RequestMappingHandlerMapping requestHandlerMappings) { - Map handlers = requestHandlerMappings.getHandlerMethods(); - ArrayList nameURLs = new ArrayList<>(); - - handlers.forEach((info, methods) -> { - String name = blankIfEmpty(info.getName()); - if (!isEmpty(name)) { - int pos = name.lastIndexOf("#"); - if (pos > -1) - name = name.substring(pos + 1); - } - - Object bean = methods.getBean(); - for (String url: info.getPatternsCondition().getPatterns()) { - DataObject row = menuItem(name, url); - nameURLs.add(row.set("bean", bean)); - } - }); - - Set parentIDs = nameURLs.stream() - .filter(row -> !isEmpty(row.get("parentID"))) - .map(row -> row.string("parentID")) - .collect(Collectors.toSet()); - parentIDs.forEach(parentID -> { - nameURLs.add(menuItem(null, parentID)); - }); - - Hierarchy tree = new HierarchyBuilder() - .getID(row -> row.string("url")) - .getParentID(row -> row.string("parentID")) - .atTop(row -> "/".equals(row.string("parentID"))) - .addChild((parent, child) -> { - ArrayList children = (ArrayList)parent.get("children"); - if (children == null) - parent.put("children", children = new ArrayList<>()); - children.add(child); - }) - .setElements(nameURLs) - .build(); - return tree.topElements(); - } - - private static DataObject menuItem(String name, String path) { - int pos = Math.max(path.lastIndexOf("/"), 0); - String parentID = ifEmpty(path.substring(0, pos), "/"); - - return new DataObject() - .set("name", ifEmpty(name, path)) - .set("url", path) - .set("parentID", parentID); - } +package cokr.xit.foundation.web; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.data.DataObject; +import cokr.xit.foundation.data.hierarchy.Hierarchy; +import cokr.xit.foundation.data.hierarchy.HierarchyBuilder; + +/**컨트롤러에 명시된 {@code @RequestMapping} 어노테이션을 읽어들인다. + * @author mjkhan + */ +public class RequestHandlerReader extends AbstractComponent { + /**컨트롤러에 명시된 {@code @RequestMapping} 어노테이션을 읽어들여, {"name", "url", "parentID"} 정보를 추출하여 목록으로 반환한다. + * @param requestHandlerMappings 요청 매핑 + * @return {"name", "url", "parentID"} 정보 목록 + */ + public List read(RequestMappingHandlerMapping requestHandlerMappings) { + Map handlers = requestHandlerMappings.getHandlerMethods(); + ArrayList nameURLs = new ArrayList<>(); + + handlers.forEach((info, methods) -> { + String name = blankIfEmpty(info.getName()); + if (!isEmpty(name)) { + int pos = name.lastIndexOf("#"); + if (pos > -1) + name = name.substring(pos + 1); + } + + Object bean = methods.getBean(); + for (String url: info.getPatternsCondition().getPatterns()) { + DataObject row = menuItem(name, url); + nameURLs.add(row.set("bean", bean)); + } + }); + + Set parentIDs = nameURLs.stream() + .filter(row -> !isEmpty(row.get("parentID"))) + .map(row -> row.string("parentID")) + .collect(Collectors.toSet()); + parentIDs.forEach(parentID -> { + nameURLs.add(menuItem(null, parentID)); + }); + + Hierarchy tree = new HierarchyBuilder() + .getID(row -> row.string("url")) + .getParentID(row -> row.string("parentID")) + .atTop(row -> "/".equals(row.string("parentID"))) + .addChild((parent, child) -> { + ArrayList children = (ArrayList)parent.get("children"); + if (children == null) + parent.put("children", children = new ArrayList<>()); + children.add(child); + }) + .setElements(nameURLs) + .build(); + return tree.topElements(); + } + + private static DataObject menuItem(String name, String path) { + int pos = Math.max(path.lastIndexOf("/"), 0); + String parentID = ifEmpty(path.substring(0, pos), "/"); + + return new DataObject() + .set("name", ifEmpty(name, path)) + .set("url", path) + .set("parentID", parentID); + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/RequestInterceptor.java b/src/main/java/cokr/xit/foundation/web/RequestInterceptor.java index 75141ea..383d72c 100644 --- a/src/main/java/cokr/xit/foundation/web/RequestInterceptor.java +++ b/src/main/java/cokr/xit/foundation/web/RequestInterceptor.java @@ -1,28 +1,28 @@ -package cokr.xit.foundation.web; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.AsyncHandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -import cokr.xit.foundation.AbstractComponent; - -/**인터셉터 인터페이스의 구현을 돕기 위한 베이스 클래스. - * @author mjkhan - */ -public class RequestInterceptor extends AbstractComponent implements AsyncHandlerInterceptor { - @Override - public boolean preHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception { - return true; - } - - @Override - public void postHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, ModelAndView mav) throws Exception {} - - @Override - public void afterCompletion(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, Exception ex) throws Exception {} - - @Override - public void afterConcurrentHandlingStarted(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception {} +package cokr.xit.foundation.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.servlet.AsyncHandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import cokr.xit.foundation.AbstractComponent; + +/**인터셉터 인터페이스의 구현을 돕기 위한 베이스 클래스. + * @author mjkhan + */ +public class RequestInterceptor extends AbstractComponent implements AsyncHandlerInterceptor { + @Override + public boolean preHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception { + return true; + } + + @Override + public void postHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, ModelAndView mav) throws Exception {} + + @Override + public void afterCompletion(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, Exception ex) throws Exception {} + + @Override + public void afterConcurrentHandlingStarted(HttpServletRequest hreq, HttpServletResponse hresp, Object handler) throws Exception {} } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/package-info.java b/src/main/java/cokr/xit/foundation/web/package-info.java index 7e983d7..817b90d 100644 --- a/src/main/java/cokr/xit/foundation/web/package-info.java +++ b/src/main/java/cokr/xit/foundation/web/package-info.java @@ -1,3 +1,3 @@ -/**베이스 컨트롤러, 웹 계층에서의 접근 정보 추출 및 예외 처리 지원 - */ +/**베이스 컨트롤러, 웹 계층에서의 접근 정보 추출 및 예외 처리 지원 + */ package cokr.xit.foundation.web; \ No newline at end of file