radian 1 gadu atpakaļ
revīzija
31adccb585
72 mainītis faili ar 10554 papildinājumiem un 0 dzēšanām
  1. 5 0
      .gitignore
  2. 261 0
      pom.xml
  3. 186 0
      src/main/java/kr/co/iya/base/advice/ExceptionAdvice.java
  4. 55 0
      src/main/java/kr/co/iya/base/advice/MaskAdvice.java
  5. 51 0
      src/main/java/kr/co/iya/base/advice/PageAdvice.java
  6. 12 0
      src/main/java/kr/co/iya/base/annotation/MaskHandler.java
  7. 12 0
      src/main/java/kr/co/iya/base/annotation/PageHandler.java
  8. 36 0
      src/main/java/kr/co/iya/base/aspect/KafkaListenerAspect.java
  9. 50 0
      src/main/java/kr/co/iya/base/aspect/PageAspect.java
  10. 25 0
      src/main/java/kr/co/iya/base/config/AgentConfig.java
  11. 251 0
      src/main/java/kr/co/iya/base/config/AidBaseConfig.java
  12. 59 0
      src/main/java/kr/co/iya/base/config/DataSourceConfig.java
  13. 225 0
      src/main/java/kr/co/iya/base/config/KafkaConfig.java
  14. 65 0
      src/main/java/kr/co/iya/base/config/MessageSourceConfig.java
  15. 88 0
      src/main/java/kr/co/iya/base/config/MybatisConfig.java
  16. 82 0
      src/main/java/kr/co/iya/base/config/PropertiesConfig.java
  17. 80 0
      src/main/java/kr/co/iya/base/config/SchedulerConfig.java
  18. 44 0
      src/main/java/kr/co/iya/base/config/SwaggerConfig.java
  19. 349 0
      src/main/java/kr/co/iya/base/config/TomcatClusterConfig.java
  20. 75 0
      src/main/java/kr/co/iya/base/config/TransactionConfig.java
  21. 173 0
      src/main/java/kr/co/iya/base/config/WebMvcConfig.java
  22. 6 0
      src/main/java/kr/co/iya/base/context/BaseToken.java
  23. 370 0
      src/main/java/kr/co/iya/base/context/GcpContext.java
  24. 327 0
      src/main/java/kr/co/iya/base/context/MaskContext.java
  25. 181 0
      src/main/java/kr/co/iya/base/context/PageContext.java
  26. 70 0
      src/main/java/kr/co/iya/base/context/ProjectContext.java
  27. 195 0
      src/main/java/kr/co/iya/base/context/SchedulerContext.java
  28. 41 0
      src/main/java/kr/co/iya/base/exception/BusinessException.java
  29. 41 0
      src/main/java/kr/co/iya/base/exception/SystemException.java
  30. 25 0
      src/main/java/kr/co/iya/base/support/AbstractMeta.java
  31. 65 0
      src/main/java/kr/co/iya/base/support/AbstractMetaBatch.java
  32. 70 0
      src/main/java/kr/co/iya/base/support/AbstractMetaOnline.java
  33. 88 0
      src/main/java/kr/co/iya/base/support/AbstractUtil.java
  34. 48 0
      src/main/java/kr/co/iya/base/support/aid/AwareAidPack.java
  35. 97 0
      src/main/java/kr/co/iya/base/support/aid/BeanAidPack.java
  36. 54 0
      src/main/java/kr/co/iya/base/support/aid/ContextAid.java
  37. 108 0
      src/main/java/kr/co/iya/base/support/aid/RequestAttributesAidPack.java
  38. 74 0
      src/main/java/kr/co/iya/base/support/etc/PathCheck.java
  39. 135 0
      src/main/java/kr/co/iya/base/support/etc/Sweeper.java
  40. 193 0
      src/main/java/kr/co/iya/base/support/linker/HealthCheckLinker.java
  41. 51 0
      src/main/java/kr/co/iya/base/support/linker/SessionCheckLinker.java
  42. 84 0
      src/main/java/kr/co/iya/base/support/util/Base64UtilPack.java
  43. 413 0
      src/main/java/kr/co/iya/base/support/util/BashProcessUtilPack.java
  44. 176 0
      src/main/java/kr/co/iya/base/support/util/CompressUtilPack.java
  45. 182 0
      src/main/java/kr/co/iya/base/support/util/CryptoUtilPack.java
  46. 539 0
      src/main/java/kr/co/iya/base/support/util/FileUtilPack.java
  47. 1146 0
      src/main/java/kr/co/iya/base/support/util/GcpUtilPack.java
  48. 337 0
      src/main/java/kr/co/iya/base/support/util/HttpRequestUtilPack.java
  49. 307 0
      src/main/java/kr/co/iya/base/support/util/JsonUtilPack.java
  50. 156 0
      src/main/java/kr/co/iya/base/support/util/KafkaUtilPack.java
  51. 176 0
      src/main/java/kr/co/iya/base/support/util/LobConverterUtilPack.java
  52. 59 0
      src/main/java/kr/co/iya/base/support/util/LocaleUtilPack.java
  53. 434 0
      src/main/java/kr/co/iya/base/support/util/MaskUtilPack.java
  54. 68 0
      src/main/java/kr/co/iya/base/support/util/MessageUtilPack.java
  55. 80 0
      src/main/java/kr/co/iya/base/support/util/ObjectMapperUtilPack.java
  56. 201 0
      src/main/java/kr/co/iya/base/support/util/PropertiesUtilPack.java
  57. 321 0
      src/main/java/kr/co/iya/base/support/util/RestTemplateUtilPack.java
  58. 96 0
      src/main/java/kr/co/iya/base/support/util/SessionUtilPack.java
  59. 348 0
      src/main/java/kr/co/iya/base/support/util/SftpUtilPack.java
  60. 203 0
      src/main/java/kr/co/iya/base/support/util/SystemUtilPack.java
  61. 130 0
      src/main/java/kr/co/iya/base/support/util/ThreadUtilPack.java
  62. 268 0
      src/main/java/kr/co/iya/base/support/util/TimeUtilPack.java
  63. 110 0
      src/main/java/kr/co/iya/base/support/util/TransactionUtilPack.java
  64. 37 0
      src/main/java/kr/co/iya/base/support/view/AttachFileView.java
  65. 7 0
      src/main/resources/log4jdbc.log4j2.properties
  66. 13 0
      src/main/resources/lombok.config
  67. 33 0
      src/main/resources/mask/mask-default.properties
  68. 53 0
      src/main/resources/message/message-default.properties
  69. 51 0
      src/main/resources/message/message-default_en.properties
  70. 51 0
      src/main/resources/message/message-default_ko.properties
  71. 14 0
      src/main/resources/mybatis-config.xml
  72. 38 0
      src/test/java/com/lguplus/utcb_base/AppTest.java

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+/.classpath
+/.factorypath
+/.project
+/.settings/
+/target/

+ 261 - 0
pom.xml

@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	
+	<groupId>kr.co.iya</groupId>
+	<artifactId>iya-base</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>jar</packaging>
+	<description>iya 공통프레임웍</description>
+
+	<parent>
+		<groupId>kr.co.iya.boot</groupId>
+		<artifactId>iya-base-parent</artifactId>
+		<version>1.0.0-SNAPSHOT</version>
+	</parent>
+	
+	<scm>
+		<connection>scm:git:${iya.git.url}/utcb-base.git</connection>
+	</scm>
+	
+	<build>
+		<finalName>${project.artifactId}-${project.version}</finalName>
+		
+		<resources>
+			<resource>
+				<directory>src/main/resources</directory>
+				<filtering>true</filtering>
+			</resource>
+			<resource>
+				<directory>src/main/java</directory>
+			</resource>						
+		</resources>
+		
+		<plugins>
+
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>${java.version}</source>
+					<target>${java.version}</target>
+					<encoding>UTF-8</encoding>
+					<verbose>false</verbose>
+					<staleMillis>1</staleMillis>
+					<useIncrementalCompilation>${maven.compiler.fullReCompile}</useIncrementalCompilation>			
+				</configuration>			
+			</plugin>
+
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>buildnumber-maven-plugin</artifactId>
+				<configuration>
+					<!-- <shortRevisionLength>5</shortRevisionLength> -->
+					<revisionOnScmFailure>noScm</revisionOnScmFailure>
+					<buildNumberPropertyName>current.build.revision</buildNumberPropertyName>
+					<scmBranchPropertyName>current.build.branch</scmBranchPropertyName>
+					<timestampFormat>{0,date,yyyy/MM/dd HH:mm:ss}</timestampFormat>
+					<timestampPropertyName>current.build.timestamp</timestampPropertyName>
+				</configuration>
+				<executions>
+					<execution>
+						<phase>validate</phase>
+						<goals>
+							<goal>create</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<manifest>
+							<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+						</manifest>
+						<manifestEntries>
+							<Implementation-Revision>${current.build.revision}</Implementation-Revision>
+							<Implementation-ScmBranch>${current.build.branch}</Implementation-ScmBranch>
+							<Implementation-BuildTime>${current.build.timestamp}</Implementation-BuildTime>
+						</manifestEntries>
+					</archive>
+				</configuration>
+			</plugin>
+
+		</plugins>
+	</build>
+
+	<dependencies>
+
+		<!-- org.springframework.boot/configuration -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-configuration-processor</artifactId>
+		</dependency>
+
+		<!-- org.springframework.boot/starter -->		
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-aop</artifactId>
+		</dependency>
+		<dependency>
+		    <groupId>org.springframework.boot</groupId>
+		    <artifactId>spring-boot-starter-tomcat</artifactId>
+		</dependency>
+
+		<!-- org.mybatis.spring.boot/starter -->
+		<dependency>
+			<groupId>org.mybatis.spring.boot</groupId>
+			<artifactId>mybatis-spring-boot-starter</artifactId>
+		</dependency>
+
+		<dependency>
+		    <groupId>org.springframework.data</groupId>
+		    <artifactId>spring-data-commons</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.kafka</groupId>
+			<artifactId>spring-kafka</artifactId>
+		</dependency>
+
+		<!-- org.apache.tomcat -->
+		<dependency>
+		    <groupId>org.apache.tomcat</groupId>
+		    <artifactId>tomcat-catalina-ha</artifactId>
+		</dependency>
+		
+		<!-- mariadb -->
+		<dependency>
+		    <groupId>org.mariadb.jdbc</groupId>
+		    <artifactId>mariadb-java-client</artifactId>
+		</dependency>
+					
+		<!-- postgresql -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+            		
+		<!-- h2database -->
+		<dependency>
+			<groupId>com.h2database</groupId>
+			<artifactId>h2</artifactId>
+		</dependency>
+
+		<!-- log4jdbc -->
+		<dependency>
+			<groupId>org.bgee.log4jdbc-log4j2</groupId>
+			<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
+		</dependency>
+		
+		<!-- projectlombok -->
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<optional>true</optional>
+		</dependency>
+		
+		<!-- commons-xxx -->	
+		<dependency>
+		    <groupId>commons-io</groupId>
+		    <artifactId>commons-io</artifactId>
+		</dependency>
+		<dependency>
+		    <groupId>commons-fileupload</groupId>
+		    <artifactId>commons-fileupload</artifactId>
+		</dependency>		
+		<dependency>
+		    <groupId>commons-codec</groupId>
+		    <artifactId>commons-codec</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-beanutils</groupId>
+			<artifactId>commons-beanutils</artifactId>
+		</dependency>
+		
+		<!-- org.apache -->	
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-pool2</artifactId>
+		</dependency>
+		<dependency>
+		  <groupId>org.apache.commons</groupId>
+		  <artifactId>commons-compress</artifactId>
+		</dependency>
+		
+		<!-- org.apache.httpcomponents -->	
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpclient</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpmime</artifactId>
+		</dependency>
+		
+		<!-- sftp -->
+		<dependency>
+		    <groupId>com.hierynomus</groupId>
+		    <artifactId>sshj</artifactId>
+		</dependency>
+		
+		<!-- com.google -->
+		<dependency>
+			<groupId>com.google.cloud</groupId>
+			<artifactId>spring-cloud-gcp-starter</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.cloud</groupId>
+			<artifactId>spring-cloud-gcp-starter-storage</artifactId>
+		</dependency>
+  				  		
+		<dependency>
+		    <groupId>com.google.cloud</groupId>
+		    <artifactId>spring-cloud-gcp-starter-bigquery</artifactId>
+		</dependency>
+  		  				  				
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>com.googlecode.json-simple</groupId>
+			<artifactId>json-simple</artifactId>
+		</dependency>
+		
+		<dependency>
+		    <groupId>com.google.guava</groupId>
+		    <artifactId>guava</artifactId>
+		</dependency>		
+		
+		<dependency>
+		    <groupId>io.opentelemetry</groupId>
+		    <artifactId>opentelemetry-opencensus-shim</artifactId>
+		</dependency>
+		
+		<!-- fasterxml.jackson -->
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>com.mikesamuel</groupId>
+		    <artifactId>json-sanitizer</artifactId>
+		</dependency>
+		
+		<!-- springdoc(swagger) -->
+		<dependency>
+			<groupId>org.springdoc</groupId>
+			<artifactId>springdoc-openapi-ui</artifactId>
+		</dependency>
+					
+	</dependencies>
+
+</project>

+ 186 - 0
src/main/java/kr/co/iya/base/advice/ExceptionAdvice.java

@@ -0,0 +1,186 @@
+package kr.co.iya.base.advice;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import kr.co.iya.base.exception.BusinessException;
+import kr.co.iya.base.exception.SystemException;
+import kr.co.iya.base.support.AbstractMeta;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RestControllerAdvice
+public class ExceptionAdvice extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private final static String DEFAULT_MESSAGE = "시스템 이용에 불편을 드려서 죄송합니다.";
+	
+	@Value("${spring.application.name}")
+	private String applicationName;
+
+	@Value("${spring.profiles.active:local}")
+	private String springProfilesActive;
+	
+	@Value("${project.name:}")
+	private String projectName;	
+	
+	@FunctionalInterface
+	public interface ExceptionMessageLambda {public abstract String getMessage();}
+
+	public static String message(String message) {return makeMessage(message,null);}
+	public static String message(String message, String arg1) {return makeMessage(message,new Object[] {arg1});}
+	public static String message(String message, Object[] args) {return makeMessage(message,args);}
+
+	public static String getTrace(Throwable e) {
+		try {
+			ByteArrayOutputStream out = new ByteArrayOutputStream();
+			PrintStream stream = new PrintStream(out);
+			e.printStackTrace(stream);
+			return out.toString();
+		}catch(Exception ex) {
+			return ex.getMessage();
+		}
+	}
+
+	public static Duration getExceptionTraceDuration() {
+		return Duration.ofMinutes(10);
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		
+	private static String makeMessage(String message, Object[] args) {		
+		String realMessage=MetaUtil.getMessageUtil().getMessage(message==null?"":message,args);
+		
+		boolean isCode=true;
+		if(isCode && message==null) {isCode=false;}
+		if(isCode && message.equals(realMessage)) {isCode=false;}
+		
+		Map<String,Object> map = new HashMap<>();
+		map.put("messageCode",message);
+		map.put("message",realMessage);
+		
+		return MetaUtil.getJsonUtil().toJson(map);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@ExceptionHandler({BusinessException.class,})
+	protected ResponseEntity<Object> businessExceptionHandler(BusinessException ex, WebRequest request){
+		Map<String,Object> body = new HashMap<>();
+		body.put("httpStatus",ex.getHttpStatus().value());
+		body.putAll(this.parseMessage(ex,request));
+
+		return new ResponseEntity<>(body,ex.getHttpStatus());
+	}
+	
+	@ExceptionHandler({SystemException.class})
+	protected ResponseEntity<Object> systemExceptionHandler(SystemException ex, WebRequest request){
+		Map<String,Object> body = new HashMap<>();
+		body.put("httpStatus",ex.getHttpStatus().value());
+		body.putAll(this.parseMessage(ex,request));
+
+		return new ResponseEntity<>(body,ex.getHttpStatus());
+	}	
+
+	@ExceptionHandler(IllegalArgumentException.class)
+	protected ResponseEntity<Object> illegalArgumentExceptionHandler(IllegalArgumentException ex, WebRequest request){
+		Map<String,Object> body = new HashMap<>();
+		body.put("httpStatus",HttpStatus.CONFLICT); //409
+		body.putAll(this.parseMessage(ex,request));		
+
+		return new ResponseEntity<>(body,HttpStatus.INTERNAL_SERVER_ERROR);
+	}	
+	
+	@ExceptionHandler(Exception.class)
+	protected ResponseEntity<Object> exceptionHandler(Exception ex, WebRequest request){
+		Map<String,Object> body = new HashMap<>();
+		body.put("httpStatus",HttpStatus.INTERNAL_SERVER_ERROR); //500
+		body.putAll(this.parseMessage(ex,request));
+		//body.put("trace", traceStr);
+		
+		return new ResponseEntity<>(body,HttpStatus.INTERNAL_SERVER_ERROR);
+	}	
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private Map<String,Object> parseMessage(Throwable ex, WebRequest request){
+		String message = ex.getMessage();
+		String exceptionName = ex.getClass().getName();
+		
+		Map<String,Object> map = new HashMap<>();
+		
+		if(!this.springProfilesActive.equals(SystemUtil.PROFILE_PROD)) {		
+			map.put("exception",exceptionName);
+		}
+		map.put("propagation",this.projectName);
+		map.put("date",new Date());
+		map.put("messageCode","");
+		map.put("message",message);
+
+		StringBuilder traceId = new StringBuilder();
+		traceId.append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(System.currentTimeMillis())).append(":"); //when
+		traceId.append(MetaUtil.getSystemUtil().getServerInstanceCode()).append(":"); //where[base64(ip)+port]
+		traceId.append(this.applicationName).append(":"); //application
+		traceId.append(exceptionName.substring(exceptionName.lastIndexOf(".")+1)); //exption
+				
+		log.error("\ntraceId:{}\n{}",traceId.toString(),getTrace(ex));
+
+		//empty인경우 디폴트메세지 처리
+		//kr.co.iya 패키지 하부의 Exception이 아닌 runtimeException이 throw된 경우는 개발자가 의도적으로 메세지 처리를 하지 않은 경우로 디폴트메세지를 사용한다.
+		//kr.co.iya 패키지 하부의 Exception을 사용한 경우는 메세지가 구성된 Exception으로 메세지를 추출하여 사용한다.
+		boolean isDefault=false;
+		if(!isDefault && StringUtils.isBlank(message)) {isDefault=true;}
+		if(!isDefault && ex.getClass().getCanonicalName().indexOf("kr.co.iya")==-1) {isDefault=true;}
+		if(isDefault) {
+				log.debug(">> Change Default Message:[{}]->[{}]",message,DEFAULT_MESSAGE);
+				map.put("message",DEFAULT_MESSAGE);
+		}
+
+		//오류가 타 시스템으로 부터 전파경우 메세지 처리
+		if(MetaUtil.getJsonUtil().isJsonObject(map.get("message"))) {
+			try {
+				Map<String,Object> refMap = MetaUtil.getObjectMapperUtil().readValue(String.valueOf(map.get("message")), new TypeReference<Map<String,Object>>(){});
+				
+				if(refMap.containsKey("propagation")) {map.put("propagation",refMap.get("propagation")+">"+map.get("propagation"));}
+				if(refMap.containsKey("messageCode")) {map.put("messageCode",refMap.get("messageCode"));}
+				if(refMap.containsKey("message")) {map.put("message",refMap.get("message"));}
+				
+			}catch(Exception e){
+				log.debug(">> Change Default Message:[{}]->[{}]",map.get("message"),DEFAULT_MESSAGE);
+				map.put("message",DEFAULT_MESSAGE);
+				log.debug(">> 에러메세지 처리시 오류 발생, 처리대상 메세지:{}",map.get("message"));
+				log.debug(">> 디폴드 메세지를 사용함 :{}",DEFAULT_MESSAGE);
+			}			
+		}
+
+		//운영환경이 아닌 경우, 상세 에러메세지를 조회할 수 있는 traceId를 붙인다
+		if(!this.springProfilesActive.equals(SystemUtil.PROFILE_PROD)) {
+			StringBuilder builder = new StringBuilder();
+			builder.append(map.get("message"));
+			builder.append("(traceId:").append(traceId).append(")");
+			map.put("message",builder.toString());
+		}
+
+		return map;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 55 - 0
src/main/java/kr/co/iya/base/advice/MaskAdvice.java

@@ -0,0 +1,55 @@
+package kr.co.iya.base.advice;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import kr.co.iya.base.annotation.MaskHandler;
+import kr.co.iya.base.context.MaskContext;
+
+//@Slf4j
+@RestControllerAdvice
+@ConditionalOnWebApplication
+public class MaskAdvice implements ResponseBodyAdvice<Object> {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Autowired
+	private MaskContext maskContext;
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Override
+	public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> messageConverter) {
+		if(!this.maskContext.isMaskSupport()) {return false;}
+		List<Annotation> list = Arrays.asList(methodParameter.getMethodAnnotations());
+		return list.stream().anyMatch(a -> a.annotationType().equals(MaskHandler.class));
+	}
+
+	@Override
+	public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> messageConverter, ServerHttpRequest request, ServerHttpResponse response) {
+		if(body==null || !mediaType.equals(MediaType.APPLICATION_JSON)) {return body;}		
+		if(!this.maskContext.isMaskApply(request)) {return body;}
+        	
+		List<Annotation> list = Arrays.asList(methodParameter.getMethodAnnotations());
+		Optional<Annotation> optional = list.stream().filter(a -> a.annotationType().equals(MaskHandler.class)).findFirst();		
+		MaskHandler maskHandler = (MaskHandler)optional.get();
+
+		return this.maskContext.doMask(body,maskHandler.value());
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}
+
+

+ 51 - 0
src/main/java/kr/co/iya/base/advice/PageAdvice.java

@@ -0,0 +1,51 @@
+package kr.co.iya.base.advice;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import kr.co.iya.base.annotation.PageHandler;
+import kr.co.iya.base.context.PageContext;
+
+@RestControllerAdvice
+@ConditionalOnWebApplication
+public class PageAdvice implements ResponseBodyAdvice<Object> {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	private final static String pagingKey = "SERVER-PAGING";
+	
+	@Autowired
+	private PageContext pagingContext;
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static String getPagingKey() {
+		return pagingKey;
+	}
+	
+	@Override
+	public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> messageConverter) {
+		List<Annotation> list = Arrays.asList(methodParameter.getMethodAnnotations());		
+		return list.stream().anyMatch(a -> a.annotationType().equals(PageHandler.class));
+	}
+
+	@Override
+	public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> messageConverter, ServerHttpRequest request, ServerHttpResponse response) {
+		if(this.pagingContext!=null){response.getHeaders().set(pagingKey,this.pagingContext.toJsonBase64());}
+		return body;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 12 - 0
src/main/java/kr/co/iya/base/annotation/MaskHandler.java

@@ -0,0 +1,12 @@
+package kr.co.iya.base.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MaskHandler {
+    public String[] value() default "";
+}

+ 12 - 0
src/main/java/kr/co/iya/base/annotation/PageHandler.java

@@ -0,0 +1,12 @@
+package kr.co.iya.base.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PageHandler {
+    //public String value() default "";
+}

+ 36 - 0
src/main/java/kr/co/iya/base/aspect/KafkaListenerAspect.java

@@ -0,0 +1,36 @@
+package kr.co.iya.base.aspect;
+
+import java.util.Locale;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+import kr.co.iya.base.context.PageContext;
+import kr.co.iya.base.support.AbstractMeta;
+
+@Aspect
+@Component
+public class KafkaListenerAspect extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Around(value="@annotation(org.springframework.kafka.annotation.KafkaListener)")
+	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+
+		MetaUtil.getThreadLocalMap().put("locale",Locale.KOREAN);
+		MetaUtil.getThreadLocalMap().put("pageContext",new PageContext());
+		
+		try {
+			return joinPoint.proceed();			
+		}catch(Exception e) {		
+			throw e;
+		}finally {		
+			MetaUtil.getThreadLocalMap().remove("locale");		
+			MetaUtil.getThreadLocalMap().remove("pageContext");
+		}
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 50 - 0
src/main/java/kr/co/iya/base/aspect/PageAspect.java

@@ -0,0 +1,50 @@
+package kr.co.iya.base.aspect;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import kr.co.iya.base.context.PageContext;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Aspect
+@Component
+@ConditionalOnWebApplication
+public class PageAspect {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Autowired
+	private PageContext pagingContext;
+	
+	@Before(value="@annotation(kr.co.iya.base.annotation.PageHandler)")
+	public void before(JoinPoint joinPoint) throws Throwable {
+		if(this.pagingContext==null) {return;}
+				
+		ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();	
+		HttpServletRequest request = attributes.getRequest();
+		List<String> parameterList = Collections.list(request.getParameterNames());
+				
+		this.pagingContext.set(key->parameterList.contains(key),key->request.getParameter(key));
+		
+		if(log.isDebugEnabled()) {
+			log.debug(">> pageContext:{}",this.pagingContext.toString());
+		}
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}
+
+

+ 25 - 0
src/main/java/kr/co/iya/base/config/AgentConfig.java

@@ -0,0 +1,25 @@
+package kr.co.iya.base.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+@Configuration
+public class AgentConfig {
+
+	@Component
+	@ConfigurationProperties(prefix="utcb.agent")
+	public class AgentProperties{
+		private String metaLocation;
+
+		public String getMetaLocation() {
+			return metaLocation;
+		}
+
+		public void setMetaLocation(String metaLocation) {
+			this.metaLocation = metaLocation;
+		}
+
+	}
+
+}

+ 251 - 0
src/main/java/kr/co/iya/base/config/AidBaseConfig.java

@@ -0,0 +1,251 @@
+package kr.co.iya.base.config;
+
+import javax.servlet.MultipartConfigElement;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.servlet.MultipartConfigFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.core.env.Environment;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.util.unit.DataSize;
+
+import kr.co.iya.base.config.KafkaConfig.KafkaProperties;
+import kr.co.iya.base.context.MaskContext;
+import kr.co.iya.base.support.aid.AwareAidPack;
+import kr.co.iya.base.support.aid.BeanAidPack;
+import kr.co.iya.base.support.aid.RequestAttributesAidPack;
+import kr.co.iya.base.support.aid.AwareAidPack.ApplicationContextAid;
+import kr.co.iya.base.support.aid.BeanAidPack.BeanAid;
+import kr.co.iya.base.support.aid.RequestAttributesAidPack.RequestAttributesAid;
+import kr.co.iya.base.support.util.Base64UtilPack;
+import kr.co.iya.base.support.util.BashProcessUtilPack;
+import kr.co.iya.base.support.util.CompressUtilPack;
+import kr.co.iya.base.support.util.CryptoUtilPack;
+import kr.co.iya.base.support.util.FileUtilPack;
+import kr.co.iya.base.support.util.GcpUtilPack;
+import kr.co.iya.base.support.util.HttpRequestUtilPack;
+import kr.co.iya.base.support.util.JsonUtilPack;
+import kr.co.iya.base.support.util.KafkaUtilPack;
+import kr.co.iya.base.support.util.LobConverterUtilPack;
+import kr.co.iya.base.support.util.LocaleUtilPack;
+import kr.co.iya.base.support.util.MaskUtilPack;
+import kr.co.iya.base.support.util.MessageUtilPack;
+import kr.co.iya.base.support.util.ObjectMapperUtilPack;
+import kr.co.iya.base.support.util.PropertiesUtilPack;
+import kr.co.iya.base.support.util.RestTemplateUtilPack;
+import kr.co.iya.base.support.util.SessionUtilPack;
+import kr.co.iya.base.support.util.SftpUtilPack;
+import kr.co.iya.base.support.util.SystemUtilPack;
+import kr.co.iya.base.support.util.ThreadUtilPack;
+import kr.co.iya.base.support.util.TimeUtilPack;
+import kr.co.iya.base.support.util.TransactionUtilPack;
+import kr.co.iya.base.support.util.Base64UtilPack.Base64Util;
+import kr.co.iya.base.support.util.BashProcessUtilPack.BashProcessUtil;
+import kr.co.iya.base.support.util.CompressUtilPack.CompressUtil;
+import kr.co.iya.base.support.util.CryptoUtilPack.CryptoUtil;
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+import kr.co.iya.base.support.util.GcpUtilPack.GcpUtil;
+import kr.co.iya.base.support.util.HttpRequestUtilPack.HttpRequestUtil;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import kr.co.iya.base.support.util.KafkaUtilPack.KafkaMeta;
+import kr.co.iya.base.support.util.KafkaUtilPack.KafkaUtil;
+import kr.co.iya.base.support.util.LobConverterUtilPack.LobConverterUtil;
+import kr.co.iya.base.support.util.LocaleUtilPack.LocaleUtil;
+import kr.co.iya.base.support.util.MaskUtilPack.MaskUtil;
+import kr.co.iya.base.support.util.MessageUtilPack.MessageUtil;
+import kr.co.iya.base.support.util.ObjectMapperUtilPack.ObjectMapperUtil;
+import kr.co.iya.base.support.util.PropertiesUtilPack.PropertiesUtil;
+import kr.co.iya.base.support.util.RestTemplateUtilPack.RestTemplateUtil;
+import kr.co.iya.base.support.util.SessionUtilPack.SessionUtil;
+import kr.co.iya.base.support.util.SftpUtilPack.SftpUtil;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import kr.co.iya.base.support.util.ThreadUtilPack.ThreadUtil;
+import kr.co.iya.base.support.util.TimeUtilPack.TimeUtil;
+import kr.co.iya.base.support.util.TransactionUtilPack.TransactionUtil;
+
+@Configuration
+public class AidBaseConfig {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Value("${spring.application.name}")
+	private String applicationName;
+
+	@Value("${server.name:}")
+	private String serverName;	
+	
+	@Value("${server.port:}")
+	private String serverPort;
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private final Environment environment;
+
+	private final ReloadableResourceBundleMessageSource messageSource;
+
+	public AidBaseConfig(Environment environment, ReloadableResourceBundleMessageSource messageSource) {
+		this.environment = environment;
+		this.messageSource = messageSource;
+	}
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Bean
+    @ConditionalOnExpression("'${spring.main.web-application-type:true}'.equals('none')")
+    MultipartConfigElement getMultipartConfigElement() {    	
+    	MultipartConfigFactory factory = new MultipartConfigFactory();
+    	factory.setMaxFileSize(DataSize.ofMegabytes(10));
+    	return factory.createMultipartConfig();
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Bean
+    ApplicationContextAid getApplicationContextAid() {
+		return AwareAidPack.getApplicationContextAid();
+	}
+
+    @Bean
+    BeanAid getBeanAid() {
+		return BeanAidPack.getBeanAid();
+	}
+
+    @Bean
+    RequestAttributesAid getRequestAttributesAid() {
+		return RequestAttributesAidPack.getRequestAttributesAid();
+	}
+
+    @Bean
+    Base64Util getBase64Util() {
+		return Base64UtilPack.getBase64Util();
+	}
+
+    @Bean
+    BashProcessUtil getBashProcessUtil() {
+		return BashProcessUtilPack.getBashProcessUtil();
+	}
+    
+    @Bean
+    @ConditionalOnExpression("T(kr.co.iya.base.GcpContext).isEnabled()")
+    GcpUtil getGcpUtil() {   	
+		return GcpUtilPack.getGcpUtil(getBeanAid());
+	}
+
+    @Bean
+    CompressUtil getCompressUtil() {
+    	return CompressUtilPack.getCompressUtil();
+    }
+    
+    @Bean
+    CryptoUtil getCryptoUtil() {
+		return CryptoUtilPack.getCryptoUtil();
+	}
+    
+    @Bean
+    FileUtil getFileUtil() {
+		return FileUtilPack.getFileUtil(getSystemUtil());	
+	}
+
+    @Bean
+    @ConditionalOnWebApplication
+    HttpRequestUtil getHttpRequestUtil() {
+		return HttpRequestUtilPack.getHttpRequestUtil(getRequestAttributesAid());
+	}
+
+    @Bean
+    JsonUtil getJsonUtil() {
+		return JsonUtilPack.getJsonUtil(true);
+	}
+
+    @Bean
+    @ConditionalOnExpression("T(kr.co.iya.base.KafkaConfig).isEnabled()")
+    @ConditionalOnProperty(name="spring.kafka.enabled", havingValue="true", matchIfMissing=false)
+    KafkaUtil getKafkaUtil() {
+    	return KafkaUtilPack.getKafkaUtil(KafkaMeta.builder()
+			.applicationName(applicationName)
+	    	.properties(this.getBeanAid().getBean(KafkaProperties.class))
+	    	.objectMapperUtil(getObjectMapperUtil())
+	    	.jsonUtil(getJsonUtil())
+	    	.systemUtil(getSystemUtil())
+	    	.timeUtil(getTimeUtil())
+	    	.build());
+	}
+
+    @Bean
+    LobConverterUtil getLobConverterUtil() {
+		return LobConverterUtilPack.getLobConverterUtil(getFileUtil());
+	}
+
+    @Bean
+    LocaleUtil getLocaleUtil() {
+		return LocaleUtilPack.getLocaleUtil(getRequestAttributesAid());
+	}
+
+    @Bean
+    MaskUtil getMaskUtil() {
+    	MaskContext context = null;
+    	try{context = ApplicationContextAid.getApplicationContext().getBean(MaskContext.class);}catch(Exception e) {}
+		return MaskUtilPack.getMaskUtil(context);
+	}
+
+    @Bean
+    MessageUtil getMessageUtil() {
+		return MessageUtilPack.getMessageUtil(getLocaleUtil(),this.messageSource);
+	}
+
+    @Bean
+    ObjectMapperUtil getObjectMapperUtil() {
+		return ObjectMapperUtilPack.getObjectMapperUtil();
+	}
+
+    @Bean
+    @ConditionalOnWebApplication
+    SessionUtil getSessionUtil() {
+		return SessionUtilPack.getSessionUtil(getRequestAttributesAid());
+	}
+
+    @Bean
+    SftpUtil getSftpUtil() {
+		return SftpUtilPack.getSftpUtil(getFileUtil());
+    }
+
+    @Bean
+    SystemUtil getSystemUtil() {
+		return SystemUtilPack.getSystemUtil(this.environment,this.serverName,this.serverPort,this.applicationName);
+	}
+    
+    @Bean
+    ThreadUtil getThreadUtil() {
+		return ThreadUtilPack.getThreadUtil();
+	}
+    
+    @Bean
+    TimeUtil getTimeUtil() {
+		return TimeUtilPack.getTimeUtil();
+	}
+
+    @Bean
+    RestTemplateUtil getRestTemplateUtil() {
+		return RestTemplateUtilPack.getRestTemplateUtil();
+	}
+
+    @Bean
+    PropertiesUtil getPropertiesUtil() {
+		return PropertiesUtilPack.getPropertiesUtil(getBeanAid(),getJsonUtil(),PropertiesConfig.DEFAULT_APPLICATION_PROPS);
+	}
+
+    @Bean
+    @ConditionalOnWebApplication
+    TransactionUtil getTransactionUtil() {
+    	PlatformTransactionManager txManager = null;
+    	try{txManager = ApplicationContextAid.getApplicationContext().getBean(PlatformTransactionManager.class);}catch(Exception e) {}
+		return TransactionUtilPack.getTransactionUtil(txManager);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 59 - 0
src/main/java/kr/co/iya/base/config/DataSourceConfig.java

@@ -0,0 +1,59 @@
+package kr.co.iya.base.config;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+@ConditionalOnProperty(prefix="spring.datasource.hikari",name="jdbc-url")
+@ConditionalOnExpression("T(kr.co.iya.base.DatasourceConfig).isEnabled()")
+public class DataSourceConfig {
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Bean(name="default.hikariConfig")
+    @ConfigurationProperties(prefix="spring.datasource.hikari")
+    HikariConfig hikariConfig() {
+    	log.debug(">> default.hikariConfig");
+    	
+		return new HikariConfig();
+	}
+
+    @Bean(name="default.dataSource")
+    @ConditionalOnBean(name="default.hikariConfig")
+    LazyConnectionDataSourceProxy dataSource(@Qualifier("default.hikariConfig") HikariConfig hikariConfig) {
+    	log.debug(">> default.dataSource");
+    	//스프링의 경우 트랜잭션 시작 시, datasource 의 connection 를 사용하건 안하건 커넥션을 확보하며, 그로 인해 불필요한 리소스가 발생됨.
+    	//이를 줄이기 위해 LazyConnectionDataSourceProxy로 wrapping 할 경우 실제 커넥션이 필요한 경우에만 datasource 에서 connection 을 반환    	
+		DataSource dataSource = new HikariDataSource(hikariConfig);
+		return new LazyConnectionDataSourceProxy(dataSource);
+	}
+
+    @Bean(name="default.txManager")
+    @ConditionalOnBean(name="default.dataSource")
+    PlatformTransactionManager txManager(@Qualifier("default.dataSource") DataSource dataSource) {
+    	log.debug(">> default.txManager");
+		DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource);
+		txManager.setNestedTransactionAllowed(true);
+		txManager.setGlobalRollbackOnParticipationFailure(false);
+		return txManager;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+}

+ 225 - 0
src/main/java/kr/co/iya/base/config/KafkaConfig.java

@@ -0,0 +1,225 @@
+package kr.co.iya.base.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.apache.kafka.common.header.internals.RecordHeader;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+import org.springframework.kafka.listener.RecordInterceptor;
+import org.springframework.kafka.listener.adapter.RecordFilterStrategy;
+import org.springframework.kafka.support.SendResult;
+import org.springframework.kafka.support.serializer.JsonSerializer;
+import org.springframework.stereotype.Component;
+import org.springframework.util.concurrent.ListenableFutureCallback;
+
+import kr.co.iya.base.support.AbstractMeta;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@EnableKafka
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.KafkaConfig).isEnabled()")
+@ConditionalOnProperty(name="spring.kafka.enabled", havingValue="true", matchIfMissing=false)
+public class KafkaConfig extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Component
+	@ConfigurationProperties(prefix="spring.kafka")
+	public static class KafkaProperties{
+		private boolean enabled;
+		private String applicationName;
+		private String bootstrapServers;
+		
+		public boolean isEnabled() {return enabled;}
+		public void setEnabled(boolean enabled) {this.enabled = enabled;}
+		
+		public String getApplicationName() {return applicationName;}
+		public void setApplicationName(String applicationName) {this.applicationName = applicationName;}
+
+		public String getBootstrapServers() {return bootstrapServers;}
+		public void setBootstrapServers(String bootstrapServers) {this.bootstrapServers = bootstrapServers;}		
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Autowired
+	private KafkaProperties properties;
+
+    @Bean
+    KafkaPublisher<String> kafkaPublisherString() {
+		return new KafkaPublisher<String>() {
+			@Override
+			public void doSend(String topic, String key, String payload, Map<String, String> header, ListenableFutureCallback<? super SendResult<String, String>> callback) {
+				this.kafkaTemplateStringValue().send(this.makeProducerRecord(topic, key, payload, header)).addCallback(callback!=null?callback:this.callback());
+			}
+		};
+	}
+
+    @Bean
+    ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
+        Map<String, Object> configs = new HashMap<>();
+        configs.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.properties.getBootstrapServers());
+        configs.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+        configs.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+        configs.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, "10485760");
+
+        ConcurrentKafkaListenerContainerFactory<String,String> factory = new ConcurrentKafkaListenerContainerFactory<>();
+	    factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(configs));	    
+
+	    factory.setRecordFilterStrategy(new RecordFilterStrategy<String,String>() {
+	    	@Override public boolean filter(ConsumerRecord<String,String> record) {
+	    		log.debug(">> kafkaListenerContainerFactory:filter");
+	    		return false;
+	    	}
+	    });      
+	    factory.setRecordInterceptor(new RecordInterceptor<String,String>(){
+			@Override public ConsumerRecord<String,String> intercept(ConsumerRecord<String,String> record) {
+				log.debug(">> kafkaListenerContainerFactory:intercept");
+				return record;
+			}
+	    });
+	    
+        return factory;
+    }	
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    public static class KafkaTemplateContext {
+    	private static Map<Object,KafkaTemplate<String,?>> context = new HashMap<>();
+
+    	public static boolean isExist(Object obj){
+    		return context.containsKey(obj);
+    	}
+    	
+    	public static KafkaTemplate<String,?> getKafkaTemplate(Object obj){
+    		if(!isExist(obj)) {return null;}
+    		KafkaTemplate<String,?> kafkaTemplate = context.get(obj);
+    		log.debug(">> obj.hashCode():{},kafkaTemplate.hashCode():{}",obj.hashCode(),kafkaTemplate.hashCode());
+    		return kafkaTemplate;    		
+    	}
+    	
+    	public static synchronized void setKafkaTemplate(Object obj,KafkaTemplate<String,?> kafkaTemplate){
+			context.put(obj, kafkaTemplate);
+    	}
+    	
+    }
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface KafkaPublisher<T> {
+	    public void doSend(String topic, String key, T payload, Map<String, String> header, ListenableFutureCallback<? super SendResult<String, T>> callback);
+
+	    default public void send(String topic, T payload) {this.doSend(topic, null, payload, null, null);}		    
+	    default public void send(String topic, T payload, Map<String,String> header) {this.doSend(topic, null, payload, header, null);} 		    
+	    default public void send(String topic, T payload, ListenableFutureCallback<? super SendResult<String, T>> callback) {this.doSend(topic, null, payload, null, callback);}
+	    default public void send(String topic, T payload, Map<String, String> header, ListenableFutureCallback<? super SendResult<String, T>> callback) {this.doSend(topic, null, payload, header, callback);}
+
+	    default public void send(String topic, String key, T payload) {this.doSend(topic, key, payload, null, null);}			
+	    default public void send(String topic, String key, T payload, Map<String,String> header) {this.doSend(topic, key, payload, header, null);} 			
+	    default public void send(String topic, String key, T payload, ListenableFutureCallback<? super SendResult<String, T>> callback) {this.doSend(topic, key, payload, null, callback);}	    
+	    default public void send(String topic, String key, T payload, Map<String,String> header, ListenableFutureCallback<? super SendResult<String, T>> callback) {this.doSend(topic, key, payload, header, callback);}
+
+	    default public Map<String,String> addDefaultHeader(Map<String,String> header) {
+	    	return MetaUtil.getKafkaUtil().addDefault(header);
+	    }
+	    
+//	    default public KafkaProducer<String, T> kafkaProducerJsonValue(){
+//	    	KafkaProducer<String, T> producer = new KafkaProducer<>(this.getPublisherConfigMap(JsonSerializer.class));
+//	    	return producer;
+//	    }
+//
+//	    default public KafkaProducer<String, String> kafkaProducerStringValue(){
+//	    	KafkaProducer<String, String> producer = new KafkaProducer<>(this.getPublisherConfigMap(StringSerializer.class));
+//	    	return producer;
+//	    }
+
+	    @SuppressWarnings("unchecked")
+		default public KafkaTemplate<String,T> kafkaTemplateJsonValue(){
+	    	if(!KafkaTemplateContext.isExist(this)) {
+		    	ProducerFactory<String,T> producerFactory = new DefaultKafkaProducerFactory<>(this.getPublisherConfigMap(JsonSerializer.class));
+		    	KafkaTemplate<String,T> kafkaTemplate = new KafkaTemplate<>(producerFactory);
+		    	KafkaTemplateContext.setKafkaTemplate(this,kafkaTemplate);
+		    	return kafkaTemplate;
+	    	}
+	    	return (KafkaTemplate<String, T>) KafkaTemplateContext.getKafkaTemplate(this);
+	    }
+	    
+	    @SuppressWarnings("unchecked")
+	    default public KafkaTemplate<String,String> kafkaTemplateStringValue(){
+	    	if(!KafkaTemplateContext.isExist(this)) {
+	    		ProducerFactory<String,String> producerFactory = new DefaultKafkaProducerFactory<>(this.getPublisherConfigMap(StringSerializer.class));
+		    	KafkaTemplate<String,String> kafkaTemplate = new KafkaTemplate<>(producerFactory);
+		    	KafkaTemplateContext.setKafkaTemplate(this,kafkaTemplate);
+		    	return kafkaTemplate;
+	    	}
+	    	return (KafkaTemplate<String, String>) KafkaTemplateContext.getKafkaTemplate(this);
+	    }	
+	    
+	    default public ProducerRecord<String,T> makeProducerRecord(String topic, String key, T payload, Map<String, String> header){
+			ProducerRecord<String,T> producerRecord = StringUtils.isBlank(key)?new ProducerRecord<>(topic,payload):new ProducerRecord<>(topic,key,payload);		 				
+			this.addDefaultHeader(header).forEach((k,v)->producerRecord.headers().add(new RecordHeader(k,v.getBytes())));
+			return producerRecord;
+	    }
+
+	    default public Map<String, Object> getPublisherConfigMap(Object valueClass){
+			Map<String, Object> configMap = new HashMap<>();
+			
+			configMap.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, MetaUtil.getKafkaUtil().getBootstrapServers());					
+			configMap.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);			
+			configMap.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, valueClass);			
+			configMap.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 1000);
+			configMap.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, "10485760");
+			configMap.put(ProducerConfig.ACKS_CONFIG,"1"); // ack=0, ack=1, ack=all
+			//configMap.put(ProducerConfig.RETRIES_CONFIG,  1);
+			//configMap.put(ProducerConfig.BATCH_SIZE_CONFIG, 20000);
+			//configMap.put(ProducerConfig.LINGER_MS_CONFIG, 1);
+			//configMap.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 24568545);	
+			return configMap;
+	    }
+
+	    default public ListenableFutureCallback<SendResult<String,T>> callback(){
+			return new ListenableFutureCallback<SendResult<String,T>>() {
+	            @Override
+	            public void onSuccess(SendResult<String, T> result) {
+	                log.debug(">> topic:{}",result.getProducerRecord().topic());
+	                log.debug(">> key:{}",result.getProducerRecord().key());
+	                //log.debug("headers:{}",result.getProducerRecord().headers());
+	                log.debug(">> headers:{}",MetaUtil.getKafkaUtil().convertMap(result.getProducerRecord().headers()));
+	                log.debug(">> payload:{}",result.getProducerRecord().value());	                
+	                
+	                log.debug(">> partition:{}",result.getRecordMetadata().partition());                
+	                log.debug(">> offset:{}",result.getRecordMetadata().offset());
+	                log.debug(">> timestamp:{}",result.getRecordMetadata().timestamp());
+	            }
+
+	            @Override
+	            public void onFailure(Throwable ex) {
+	                log.error("error:{}",ex.getMessage());
+	            }
+			};   
+	   }
+	}
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}
+
+

+ 65 - 0
src/main/java/kr/co/iya/base/config/MessageSourceConfig.java

@@ -0,0 +1,65 @@
+package kr.co.iya.base.config;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.stereotype.Component;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.MessageSourceConfig).isEnabled()")
+public class MessageSourceConfig {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Component
+	@ConfigurationProperties(prefix="utcb.messagesource")
+	public static class MessageSourceProperties{
+		private final String defaultMessageSource="classpath:message/message-default";
+		
+		private Set<String> baseNames;
+
+		public Set<String> getBaseNames() {return baseNames;}
+		public void setBaseNames(Set<String> baseNames) {this.baseNames = baseNames;}
+		
+		public String getDefaultmessagesource() {return defaultMessageSource;}
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Autowired
+	private MessageSourceProperties props;
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Bean
+    ReloadableResourceBundleMessageSource messageSource() {
+		ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource();
+		source.setBasenames(this.getBaseNames());
+        source.setDefaultEncoding("UTF-8");
+        source.setCacheSeconds(60);
+        source.setUseCodeAsDefaultMessage(true);
+        return source;
+    }
+
+	private String[] getBaseNames() {
+		Set<String> baseNames = this.props.getBaseNames();		
+		if(baseNames==null) {baseNames = new HashSet<>();}
+		baseNames.add(props.getDefaultmessagesource());		
+		baseNames.forEach(i->log.info(">> messageSource: {}",i));
+		return baseNames.toArray(new String[baseNames.size()]);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+}
+
+

+ 88 - 0
src/main/java/kr/co/iya/base/config/MybatisConfig.java

@@ -0,0 +1,88 @@
+package kr.co.iya.base.config;
+
+import java.io.FileNotFoundException;
+
+import javax.sql.DataSource;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.MybatisConfig).isEnabled()")
+@MapperScan(basePackages="kr.co.iya.**.mapper")
+public class MybatisConfig {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private ApplicationContext applicationContext;
+	
+	public MybatisConfig(ApplicationContext applicationContext) {
+		this.applicationContext = applicationContext;
+	}
+
+    @Bean
+    SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
+		Resource[] resources=null;
+		try {
+			resources=new PathMatchingResourcePatternResolver().getResources("classpath:sqlmap/**/*Mapper.xml");
+		}catch(FileNotFoundException e){
+			log.debug(">> resources(*Mapper.xml) does not exist.");
+			return null;
+		}
+		//log.debug(">> default.sqlSessionFactory");
+		SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();		
+		sessionFactory.setDataSource(dataSource);
+		sessionFactory.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
+		sessionFactory.setMapperLocations(resources);
+		return sessionFactory.getObject();
+	}
+
+    @Bean
+    SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+		if(sqlSessionFactory==null) {
+			log.debug(">> sqlSessionFactory is null.");
+			return null;
+		}
+		//log.debug(">> default.sqlSessionTemplate");
+		return new SqlSessionTemplate(sqlSessionFactory);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    /**
+	Mapper interface class : 
+		매핑 파일이나 어노테이션에 정의한 SQL에 대응하는 자바 인터페이스.
+		Mapper 인터페이스의 구현 클래스를 프락시로 인스턴스화 하기 때문에(자동으로 생성) 개발자는 Mapper 인터페이스의 구현 클래스를 작성할 필요 없음.
+
+	SQL Mapping File(XML) :
+	 	SQL과 객체의 매핑 정의를 기술하는 XML 파일. SQL을 어노테이션에 지정하는 경우에는 사용하지 않는다.
+	 	해당 파일을 SqlSession 객체가 참조한다.
+	
+	SqlSession : 
+		SQL 발행이나 트랜잭션 제어용 API를 제공하는 컴포넌트. 스프링 프레임워크에서 사용하는 경우에는 마이바티스 측의 트랜잭션 제어 API는 사용하지 않음.
+		SqlSession이 Mapper 파일에서 SQL을 수행하고 결과 데이터를 반환하는 역할을 한다.
+
+	SqlSessionFactory : 
+		SqlSession을 생성하기 위한 컴포넌트. [SqlSessionFactoryBuilder:빌더패턴, SqlSessionFactoryBean:스프링의 DI 컨테이너에서 Bean]
+		
+	SqlSessionTemplate : 
+		스프링 트랜잭션 관리하에 마이바티스 표준의 SqlSession을 취급하기 위한 컴포넌트로 스레드 안전하게 구현됨.
+		(SqlSession은 트랜잭션 처리를 위해 commit/rollback 메소드를 명시적으로 호출해야 함)
+		SqlSession 인터페이스를 구현하고 있으며 SqlSession으로 동작(실제 처리는 마이바티스의 표준의 SqlSession에 위임)하는 것도 지원.
+
+	MapperFactoryBean : 
+		스프링 트랜잭션 관리하에 SQL을 실행하는 Mapper객체를 빈으로 생성하기 위한 컴포넌트.
+		스프링의 DI 컨테이너에서 Bean으로 취급할 수 있으므로 임의의 빈에 주입해서 SQL을 실행하게 함.
+	**/
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 82 - 0
src/main/java/kr/co/iya/base/config/PropertiesConfig.java

@@ -0,0 +1,82 @@
+package kr.co.iya.base.config;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Set;
+
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
+import org.springframework.lang.Nullable;
+
+import kr.co.iya.base.config.PropertiesConfig.YamlPropertySourceFactory;
+
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.PropertiesConfig).isEnabled()")
+@PropertySource(value="classpath:application.yaml", factory=YamlPropertySourceFactory.class)
+public class PropertiesConfig implements EnvironmentAware {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public static final String DEFAULT_APPLICATION_PROPS = "applicationProps";
+	
+	private Properties applicationProps;
+	
+	@Override
+	public void setEnvironment(Environment environment) {
+		YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+		factory.setResources(new ClassPathResource("application.yaml")); 
+		factory.afterPropertiesSet();
+		Properties props = factory.getObject();
+		
+		Set<String> keys = props.stringPropertyNames();
+		for (String key : keys) {
+			props.put(key, environment.getProperty(key));
+		}
+		
+		this.applicationProps = props;	    
+	 }
+
+    @Bean(name=DEFAULT_APPLICATION_PROPS)
+    Properties getApplicationProps() {
+		 return this.applicationProps;
+	 }
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	 
+	public static class YamlPropertySourceFactory implements PropertySourceFactory {
+
+	    @Override
+	    public org.springframework.core.env.PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
+	        String sourceName = name!= null?name:resource.getResource().getFilename();
+	        Properties properties = this.loadYamlIntoProperties(resource);
+	        return new PropertiesPropertySource(sourceName,properties);
+	    }
+
+	    private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
+	        try {
+	            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+	            factory.setResources(resource.getResource());
+	            factory.afterPropertiesSet();
+	            return factory.getObject();
+	        } catch (IllegalStateException e) {
+	            Throwable cause = e.getCause();
+	            if (cause instanceof FileNotFoundException){throw (FileNotFoundException) e.getCause();}
+	            throw e;
+	        }
+	    }
+	} 
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}
+
+

+ 80 - 0
src/main/java/kr/co/iya/base/config/SchedulerConfig.java

@@ -0,0 +1,80 @@
+package kr.co.iya.base.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+import lombok.Data;
+
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.SchedulerConfig).isEnabled()")
+@ConditionalOnProperty(name="utcb.scheduler.enabled", havingValue="true", matchIfMissing=false)
+public class SchedulerConfig {
+
+	@Component
+	@ConfigurationProperties(prefix="utcb.scheduler")
+	@Data
+	public static class SchedulerProperties{
+		
+		private boolean enabled=false;
+		private int poolSize=10;
+		private long stopCheckTerm=100; //ms
+		
+		private String threadNamePrefix="utcb-task-";
+		private String worknoteDirectory="/utcbfs/data/scheduler/worknote";
+		
+		@NestedConfigurationProperty
+		private SchedulerOptimizeProperties optimize;
+	}
+
+	@Data
+	public static class SchedulerOptimizeProperties{
+		private boolean enabled=false;
+		private String cron="0 0 0 * * ?"; //매일 00시 00분 00초			
+	}		
+
+	/* 
+	------------------------------------------------------------------
+	7개의 각 필드로 구성
+	------------------------------------------------------------------
+	필드명			값의 허용 범위 						허용된 특수문자
+	------------------------------------------------------------------
+	초 (Seconds)	0 ~ 59 							, - * /
+	분 (Minutes)	0 ~ 59 							, - * /
+	시 (Hours)	0 ~ 23 							, - * /
+	일 (Day)		1 ~ 31 							, - * ? / L W
+	월 (Month)	1 ~ 12 or JAN ~ DEC				, - * /
+	요일 (Week)	0 ~ 6 or SUN ~ SAT(7도 일요일) 		, - * ? / L #
+	연도 (Year)	empty or 1970 ~ 2099 			, - * /		
+	------------------------------------------------------------------
+	* : 모든 값을 뜻합니다.
+	? : 특정한 값이 없음을 뜻합니다. 
+	- : 범위를 뜻합니다. (예) 월요일에서 수요일까지는 MON-WED로 표현
+	, : 특별한 값일 때만 동작 (예) 월,수,금 MON,WED,FRI 
+	/ : 시작시간 / 단위  (예) 0분부터 매 5분 0/5
+	L : 일에서 사용하면 마지막 일, 요일에서는 마지막 요일(토요일)
+	W : 가장 가까운 평일 (예) 15W는 15일에서 가장 가까운 평일 (월 ~ 금)을 찾음
+	# : 몇째주의 무슨 요일을 표현 (예) 3#2 : 2번째주 수요일
+	------------------------------------------------------------------
+	"0 0 12 * * ?" : 아무 요일, 매월, 매일 12:00:00
+	"0 15 10 ? * *" : 모든 요일, 매월, 아무 날이나 10:15:00 
+	"0 15 10 * * ?" : 아무 요일, 매월, 매일 10:15:00 
+	"0 15 10 * * ? *" : 모든 연도, 아무 요일, 매월, 매일 10:15 
+	"0 15 10 * * ? : 2005" 2005년 아무 요일이나 매월, 매일 10:15 
+	"0 * 14 * * ?" : 아무 요일, 매월, 매일, 14시 매분 0초 
+	"0 0/5 14 * * ?" : 아무 요일, 매월, 매일, 14시 매 5분마다 0초 
+	"0 0/5 14,18 * * ?" : 아무 요일, 매월, 매일, 14시, 18시 매 5분마다 0초 
+	"0 0-5 14 * * ?" : 아무 요일, 매월, 매일, 14:00 부터 매 14:05까지 매 분 0초 
+	"0 10,44 14 ? 3 WED" : 3월의 매 주 수요일, 아무 날짜나 14:10:00, 14:44:00 
+	"0 15 10 ? * MON-FRI" : 월~금, 매월, 아무 날이나 10:15:00 
+	"0 15 10 15 * ?" : 아무 요일, 매월 15일 10:15:00 
+	"0 15 10 L * ?" : 아무 요일, 매월 마지막 날 10:15:00 
+	"0 15 10 ? * 6L" : 매월 마지막 금요일 아무 날이나 10:15:00 
+	"0 15 10 ? * 6L 2002-2005" : 2002년부터 2005년까지 매월 마지막 금요일 아무 날이나 10:15:00 
+	"0 15 10 ? * 6#3" : 매월 3번째 금요일 아무 날이나 10:15:00
+	------------------------------------------------------------------
+	*/	
+}

+ 44 - 0
src/main/java/kr/co/iya/base/config/SwaggerConfig.java

@@ -0,0 +1,44 @@
+package kr.co.iya.base.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+
+
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.SwaggerConfig).isEnabled()")
+@ConditionalOnWebApplication
+public class SwaggerConfig {
+
+	@Value("${spring.profiles.active:local}")
+	private String springProfilesActive;
+	
+	@Value("${project.name:project.name}")
+    private String name;
+
+    @Value("${project.description:project.description}")
+    private String description;
+
+    @Value("${project.version:project.version}")
+    private String version;
+
+    @Bean
+    OpenAPI openAPI() {
+
+    	Info info = new Info();
+    	info.setTitle(this.name+"-"+this.springProfilesActive);
+    	info.setDescription(this.description);
+    	info.version(this.version);
+      	
+    	OpenAPI openApi = new OpenAPI();
+        openApi.setInfo(info);
+    	
+    	return openApi;
+    }
+
+}

+ 349 - 0
src/main/java/kr/co/iya/base/config/TomcatClusterConfig.java

@@ -0,0 +1,349 @@
+package kr.co.iya.base.config;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.ha.session.ClusterSessionListener;
+import org.apache.catalina.ha.session.DeltaManager;
+import org.apache.catalina.ha.session.JvmRouteBinderValve;
+import org.apache.catalina.ha.tcp.ReplicationValve;
+import org.apache.catalina.ha.tcp.SimpleTcpCluster;
+import org.apache.catalina.tribes.group.GroupChannel;
+import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
+import org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor;
+import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+import org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor;
+import org.apache.catalina.tribes.membership.McastService;
+import org.apache.catalina.tribes.membership.StaticMember;
+import org.apache.catalina.tribes.transport.ReplicationTransmitter;
+import org.apache.catalina.tribes.transport.nio.NioReceiver;
+import org.apache.catalina.tribes.transport.nio.PooledParallelSender;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import kr.co.iya.base.config.TomcatClusterConfig.MembershipProperties.MembershipNode;
+import kr.co.iya.base.config.TomcatClusterConfig.MembershipProperties.NodeType;
+import kr.co.iya.base.exception.SystemException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.TomcatClusterConfig).isEnabled() && '${utcb.tomcat-cluster.enabled:false}'.equals('true')")
+@ConditionalOnWebApplication
+public class TomcatClusterConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Component
+	@ConfigurationProperties(prefix="utcb.tomcat-cluster")
+	public static class MembershipProperties{
+
+		public enum NodeType {receiver,staticMember}
+
+		private boolean enabled = false;
+		private String mcastIp = "228.0.0.4";
+		private String mcastPort = "45564";
+		private String frequency = "500";
+		private String dropTime = "3000";
+		private String receiverAddress = "auto";
+		private String receiverPort = "5000";
+
+		private List<MembershipNode> nodes;
+
+		public boolean isEnabled() {return enabled;}
+		public void setEnabled(boolean enabled) {this.enabled = enabled;}
+
+		public List<MembershipNode> getNodes() {return nodes;}
+		public void setNodes(List<MembershipNode> nodes) {this.nodes = nodes;}
+
+		public String getMcastIp() {return mcastIp;}
+		public void setMcastIp(String mcastIp) {this.mcastIp = mcastIp;}
+
+		public String getMcastPort() {return mcastPort;}
+		public void setMcastPort(String mcastPort) {this.mcastPort = mcastPort;}
+
+		public String getFrequency() {return frequency;}
+		public void setFrequency(String frequency) {this.frequency = frequency;}
+
+		public String getDropTime() {return dropTime;}
+		public void setDropTime(String dropTime) {this.dropTime = dropTime;}
+
+		public String getReceiverAddress() {return receiverAddress;}
+		public void setReceiverAddress(String receiverAddress) {this.receiverAddress = receiverAddress;}
+
+		public String getReceiverPort() {return receiverPort;}
+		public void setReceiverPort(String receiverPort) {this.receiverPort = receiverPort;}
+
+		public boolean isStaticMember() {
+			if(this.nodes != null && !this.nodes.isEmpty()) {return true;}
+			return false;
+		}		
+
+		public static class MembershipNode{
+			private String name;
+			private String host;
+			private String port;
+			private String uniqueId;
+			private NodeType type;
+			
+			public String getName() {return name;}
+			public void setName(String name) {this.name = name;}
+			
+			public String getHost() {return host;}
+			public void setHost(String host) {this.host = host;}
+			
+			public String getPort() {return port;}
+			public void setPort(String port) {this.port = port;}
+			
+			public String getUniqueId() {return uniqueId;}
+			public void setUniqueId(String uniqueId) {this.uniqueId = uniqueId;}
+			
+			public NodeType getType() {return type;}
+			public void setType(NodeType type) {this.type = type;}
+
+			@Override
+			public String toString() {
+				return "membershipNode [name=" + name + ", host=" + host + ", port=" + port + ", uniqueId=" + uniqueId + ", type=" + type + "]";
+			}		
+		}
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Autowired
+	private Environment environment;
+	
+	@Autowired
+	private MembershipProperties props;
+	
+	@Value("${server.port:}")
+	private String serverPort;
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Override
+	public void customize(TomcatServletWebServerFactory factory) {
+		
+		factory.addContextCustomizers(new TomcatContextCustomizer() {
+			
+			@Override
+			public void customize(Context context) {
+
+				if(props.isStaticMember() && props.getNodes()!=null) {
+					props.getNodes().stream().forEach(i -> log.debug(">> {}",i.toString()));
+				}
+
+				//[STEP1] 클러스터링시 메시지 송수신에 관련된 하위 component를 그룹핑 
+		        GroupChannel channel = null;
+		        if(props.isStaticMember()) {
+		        	channel = this.getStaticChannel(props.getNodes());
+		        } else {
+		        	channel = this.getMcatChannel();
+		        }
+		        
+		        //[STEP2] 내장톰켓서버(server.xml)에 cluster설정
+		        //org.apache.catalina.ha.tcp.SimpleTcpCluster: 클러스터링 동작
+		        
+		        //[STEP2.1] SimpleTcpCluster
+		        SimpleTcpCluster cluster = new SimpleTcpCluster();
+		        cluster.setChannel(channel);
+		        cluster.setChannelSendOptions(6);		        
+		        cluster.addValve(new ReplicationValve()); //HTTP Request가 끝나는 시점에 다른 복제를 해야할지 말아야 할지 cluster에 알리는 역활
+		        cluster.addValve(new JvmRouteBinderValve()); //mod_jk를 사용중 failover시 session에 저장한 jvmWorker속성을 변경하여 다음 request부터는 해당 노드에 고정시킴.     
+		        cluster.addClusterListener(new ClusterSessionListener());
+		        
+		        //[STEP2.2] Engine(server.xml)
+		        Engine engine = (Engine)context.getParent().getParent();
+		        engine.setCluster(cluster);	
+		        
+		        ////////////////////////////////////////////////////////////////////////////////////////////////
+
+		        //[STEP3] 웹어플리케이션(web.xml)에 세션복제 활성화 및 세션복제방식 지정     
+		        //org.apache.catalina.ha.session.DeltaManager: 모든 노드에 동일한 세션을 복제, 노드 개수가 많을 수록 네트워크 트래픽이 높아지고 메모리 소모 증가
+		        //org.apache.catalina.ha.session.BackupManager: Primary Node와 Backup Node로 분리되어 모든 노드에 복제하지 않고 단 Backup Node에만 복제합니다. 하나의 노드에만 복제하기 때문에 DeltaManager의 단점을 커버할 수 있고 failover도 지원.
+		        //org.apache.catalina.ha.session.PersistentManager: DB나 파일시스템을 이용하여 세션을 저장합니다. IO문제가 생기기 떄문에 실시간성이 떨어짐.
+		        
+		        //[STEP3.1] DeltaManager   
+		        DeltaManager manager = new DeltaManager();
+		        manager.setExpireSessionsOnShutdown(false); //shutdown시 모든 노드의 모든 세션을 expire할지 여부
+		        manager.setNotifyListenersOnReplication(true); //다른 tomcat에서 세션이 생성/소멸시 알림을 받을지 여부		        
+
+		        //[STEP3.2] Context(web.xml)
+				context.setDistributable(true); 
+		        context.setManager(manager);
+		        	
+			}
+		
+			private String getServerIp() {
+				String hostAddress=null;
+
+				if(!StringUtils.isEmpty(serverPort)) {
+					try(DatagramSocket socket = new DatagramSocket()) {
+						socket.connect(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), Integer.parseInt(serverPort));
+						hostAddress = socket.getLocalAddress().getHostAddress();		
+						//log.debug("socket.getLocalAddress().getHostAddress():{}",hostAddress);
+					} catch (Exception e) {
+						log.debug(">> exception skip: {}",e.getMessage());
+						//e.printStackTrace();
+					}
+				}
+				
+				if(hostAddress==null || hostAddress.equals("")) {hostAddress="127.0.0.1";}
+				return hostAddress;
+			}
+			
+			private GroupChannel getStaticChannel(List<MembershipNode> list) {
+				
+				String membershipReceiver = environment.getProperty("membership.receiver");
+				log.info(">> membershipReceiver(property by jvm option):{}",membershipReceiver);
+				
+				//auto detect Receiver : nodes 리스트에서 자신의 IP와 같은 것을 검출
+				if(StringUtils.isEmpty(membershipReceiver)) {
+					String serverIp=this.getServerIp();
+					Long count = list.stream().filter(i->i.getHost().equals(serverIp)).count();
+					if(count==1) {
+						list.forEach(i->{
+							if(i.getHost().equals(serverIp)) {
+								i.setType(NodeType.receiver);
+								log.info(">> membershipReceiver(auto detect):{}",i.getName());
+							}
+						});
+					}
+				}
+
+		        //NioReceiver: Cluster로부터 메시지를 수신하는 역활
+				NioReceiver receiver = new NioReceiver();				
+				
+				//StaticMembershipInterceptor
+				StaticMembershipInterceptor staticMembershipInterceptor = new StaticMembershipInterceptor();
+	
+				//UniqueId 중복체크, host:port 중복체크, receiver 중복체크
+				Set<String> checkSet = new HashSet<>();
+				list.forEach(i->{
+					if(StringUtils.isBlank(i.getName())) {throw new SystemException("node:name is null or empty.");}
+					if(StringUtils.isBlank(i.getHost())) {throw new SystemException("node:host is null or empty.");}
+					if(StringUtils.isBlank(i.getPort())) {throw new SystemException("node:port is null or empty.");}
+					if(StringUtils.isBlank(i.getUniqueId())) {throw new SystemException("node:UniqueId is null or empty.");}
+					if(i.getType()==null) {i.setType(NodeType.staticMember);} //default
+
+					if(checkSet.contains(i.getName())) {throw new SystemException("node:name is duplication.");}	
+					checkSet.add(i.getName());
+
+					String member=i.getHost()+":"+i.getPort();
+					if(checkSet.contains(member)) {throw new SystemException("node:port is duplication.");}					
+					checkSet.add(member);
+					
+					if(checkSet.contains(i.getUniqueId())) {throw new SystemException("node:uniqueId is duplication.");}	
+					checkSet.add(i.getUniqueId());
+
+					if(i.getType().equals(NodeType.receiver)) {
+						if(checkSet.contains(NodeType.receiver.toString())) {throw new SystemException("node:type(receiver) is only one.");}	
+						checkSet.add(NodeType.receiver.toString());					
+					}
+					
+					if(!StringUtils.isBlank(membershipReceiver) && i.getName().equals(membershipReceiver)) {
+						i.setType(NodeType.receiver);
+						checkSet.add(NodeType.receiver.toString());
+					}
+					 log.info(">> node:{}",i.toString());
+				});
+				
+				
+				//receiver 존재여부체크
+				if(!checkSet.contains(NodeType.receiver.toString())) {
+					throw new SystemException("membershipNode:type(receiver) is not exist. JVM option -Dmembership.receiver=[node.name] or One of the settings in application properties 'tomcatcluster.nodes' should be 'receiver'.");
+				}
+				
+				//구성: staticMembershipInterceptor, receiver
+				list.forEach(i->{					
+					if(i.getType().equals(NodeType.staticMember)) {
+						StaticMember staticMember = new StaticMember();
+				        staticMember.setUniqueId(i.getUniqueId());
+				        staticMember.setHost(i.getHost());
+				        staticMember.setPort(Integer.parseInt(i.getPort()));
+				        staticMember.setSecurePort(-1); //default
+				        staticMembershipInterceptor.addStaticMember(staticMember);
+				        
+				        log.info(">> staticMember:{}",i.toString());
+					}
+					if(i.getType().equals(NodeType.receiver)) {
+				        receiver.setAddress(i.getHost());
+				        receiver.setPort(Integer.parseInt(i.getPort()));
+				        receiver.setMaxThreads(6); //non-blocking, 기본적으로 노드당 1개의 thread를 할당 default(min:6, max:15)
+				        
+				        log.info(">> receiver:{}",i.toString());
+					}
+				});
+				
+		        //ReplicationTransmitter: 노드에서 Cluster로 메시지를 보내는 역활 (사실상 빈 껍데기로 상세 역확을 Transport에서 정의)
+		        ReplicationTransmitter channelSender = new ReplicationTransmitter();
+		        channelSender.setTransport(new PooledParallelSender());	//non-blocking, 여러 노드로 메시지를 전송, 하나의 노드에 여러 메시지를 동시전송
+		        
+		        //GroupChannel: 클러스터링시 메시지 송수신에 관련된 하위 component를 그룹핑(membership, sender/transport, receiver, interceptor)		        
+				GroupChannel channel = new GroupChannel();
+				channel.setChannelReceiver(receiver);
+				channel.setChannelSender(channelSender);
+				channel.addInterceptor(staticMembershipInterceptor);
+		        channel.addInterceptor(new TcpPingInterceptor());
+		        channel.addInterceptor(new TcpFailureDetector());
+		        channel.addInterceptor(new MessageDispatchInterceptor());
+
+				return channel;
+			}
+			
+			private GroupChannel getMcatChannel() {
+				
+				log.debug(">> membership.mcastIp:{}",props.getMcastIp());
+				log.debug(">> membership.mcastPort:{}",props.getMcastPort());
+				log.debug(">> membership.frequency:{}",props.getFrequency());
+				log.debug(">> membership.castDropTime:{}",props.getDropTime());
+				
+		        //McastService: 클러스터에 참여한 노드에서 활성노드 파악(분별),그룹내 1대N UDP통신.
+		        McastService membershipService = new McastService();
+		        membershipService.setAddress(props.getMcastIp());
+		        membershipService.setPort(Integer.parseInt(props.getMcastPort())); //TCP&UDP port 오픈 필요
+		        membershipService.setFrequency(Integer.parseInt(props.getFrequency())); //frequency에 설정된 간격으로 각 노드들이 UDP packet을 날려 heartbeat 확인.
+		        membershipService.setDropTime(Integer.parseInt(props.getDropTime())); //dropTime에 설정된 시간동안 HeartBeat이 없을 경우 장애로 판단하고 각 노드에 알림.
+		        
+		        //ReplicationTransmitter: 노드에서 Cluster로 메시지를 보내는 역활 (사실상 빈 껍데기로 상세 역확을 Transport에서 정의)
+		        ReplicationTransmitter channelSender = new ReplicationTransmitter();
+		        channelSender.setTransport(new PooledParallelSender()); //non-blocking, 여러 노드로 메시지를 전송, 하나의 노드에 여러 메시지를 동시전송
+		        
+		        //NioReceiver: Cluster로부터 메시지를 수신하는 역활, non-blocking
+		        NioReceiver channelReceiver = new NioReceiver();
+		        channelReceiver.setAddress(props.getReceiverAddress()); //tomcat_IP (auto: java.net.InetAddress.getLocalHost().getHostAddress())
+		        channelReceiver.setPort(Integer.parseInt(props.getReceiverPort())); //TCP port 오픈 필요
+		        channelReceiver.setMaxThreads(6); //기본적으로 노드당 1개의 thread를 할당 default(min:6, max:15)
+		        
+		        //GroupChannel: 클러스터링시 메시지 송수신에 관련된 하위 component를 그룹핑(membership, sender/transport, receiver, interceptor)
+		        GroupChannel channel = new GroupChannel();
+		        channel.setMembershipService(membershipService);
+		        channel.setChannelReceiver(channelReceiver);
+		        channel.setChannelSender(channelSender);
+		        channel.addInterceptor(new TcpPingInterceptor()); //클러스터 그룹내 타 멤버가 정말 중지한 것이 맞는지 TCP Unicast를 통해 확인.
+		        channel.addInterceptor(new TcpFailureDetector()); //클러스터 그룹 내 특정 멤버가 장애나 재기동에 의해 중지되면 그룹내 다른 멤버가 중지한 멤버 내 세션들을 이어받아 처리하게 된다. 이때 세션 ID가 jvmRoute를 포함하고 있다면 JvmRouteBinderValve는 이전 멤버의 jvmRoute값을 새로운 멤버의 jvmRoute로 변경		        
+		        channel.addInterceptor(new MessageDispatchInterceptor());
+		        
+		        return channel;
+			}
+		});
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 75 - 0
src/main/java/kr/co/iya/base/config/TransactionConfig.java

@@ -0,0 +1,75 @@
+package kr.co.iya.base.config;
+
+import java.util.Collections;
+
+import org.springframework.aop.Advisor;
+import org.springframework.aop.aspectj.AspectJExpressionPointcut;
+import org.springframework.aop.support.DefaultPointcutAdvisor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource;
+import org.springframework.transaction.interceptor.RollbackRuleAttribute;
+import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+@Configuration
+@EnableTransactionManagement
+@ConditionalOnBean(PlatformTransactionManager.class)
+@ConditionalOnExpression("T(kr.co.iya.base.TransactionConfig).isEnabled()")
+public class TransactionConfig {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private static final String AOP_TX_METHOD_NAME = "*";
+	private static final String AOP_TX_EXPRESSION = "execution(* kr.co.iya..service..*Impl.*(..))";
+	
+	private final TransactionManager txManager;
+
+	public TransactionConfig(TransactionManager transactionManager) {
+		this.txManager = transactionManager;
+	}
+
+    @Bean
+    TransactionInterceptor txInterceptor() {
+		RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
+		attribute.setName(AOP_TX_METHOD_NAME);
+		attribute.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
+		attribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+		MatchAlwaysTransactionAttributeSource attributeSource = new MatchAlwaysTransactionAttributeSource  ();
+		attributeSource.setTransactionAttribute(attribute);
+		
+		return new TransactionInterceptor(this.txManager,attributeSource);
+	}
+
+//	Spring-Boot 2.3 이전사용시
+//	@Bean
+//	public TransactionInterceptor transactionAdvice() {
+//		RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
+//		attribute.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
+//		attribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+//
+//		Map<String,TransactionAttribute> txMethods = new HashMap<>();
+//		txMethods.put(AOP_TX_METHOD_NAME,attribute);
+//
+//		NameMatchTransactionAttributeSource attributeSource = new NameMatchTransactionAttributeSource();
+//		attributeSource.setNameMap(txMethods);
+//		
+//		return new TransactionInterceptor(this.transactionManager,attributeSource);
+//	}
+    
+    @Bean
+    Advisor txAdvisor() {
+		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
+		pointcut.setExpression(AOP_TX_EXPRESSION);
+		return new DefaultPointcutAdvisor(pointcut, txInterceptor());
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 173 - 0
src/main/java/kr/co/iya/base/config/WebMvcConfig.java

@@ -0,0 +1,173 @@
+package kr.co.iya.base.config;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
+
+import kr.co.iya.base.advice.PageAdvice;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+@ConditionalOnExpression("T(kr.co.iya.base.WebMvcConfig).isEnabled()")
+@ConditionalOnWebApplication
+public class WebMvcConfig implements WebMvcConfigurer {
+
+	@Value("${project.name:}")
+	private String projectName;	
+
+	@Value("${interceptor.enabled:true}")
+	private boolean isEnable_interceptor;
+
+	@Value("${interceptor.locale.enabled:true}")
+	private boolean isEnable_localeChangeInterceptor;
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Override	
+	public void addCorsMappings(CorsRegistry registry) {
+		registry.addMapping("/**")
+			.maxAge(1800L) // Access-Control-Max-Age : 클라이언트에서 preFlight의 요청 결과를 저장할 시간(초) 지정
+			.allowedOriginPatterns("*") // Access-Control-Allow-Origin : 요청을 보내는 페이지의 출처 (*, 도메인)
+			.allowedHeaders("*") // Access-Control-Allow-Headers : 요청을 허용하는 헤더
+			.allowedMethods("*") // Access-Control-Allow-Methods : 요청을 허용하는 메소드 (Default : GET, POST, HEAD)
+			.allowCredentials(true) // Access-Control-Allow-Credentials : 쿠키를 요청에 포함 (요청시 withCredentials:true)
+			.exposedHeaders(PageAdvice.getPagingKey()); // JavaScript에서 참조하기 위한 추가, CORS의 경우 기본적으로 화면(JavaScript)에서 response header 값을 읽지 못함.
+	}
+	
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {    	
+    	if(!isEnable_interceptor) {return;}
+
+    	int order=0;
+        if(isEnable_localeChangeInterceptor) {registry.addInterceptor(localeChangeInterceptor()).order(++order);}
+    }
+
+    //@Override
+    //public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
+    //	 아규먼트리졸버(Argument Resolver)는 사용자가 컨트롤러의 메서드 인자값으로 임의의 값을 전달하려할 때 사용
+    //   argumentResolvers.add(deviceHandlerMethodArgumentResolver());
+    //}
+    
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Bean
+    DefaultErrorAttributes errorAttributes() {		
+		return new DefaultErrorAttributes() {
+			@Override
+			public Map<String,Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
+				Integer status = (Integer) webRequest.getAttribute("javax.servlet.error.status_code", RequestAttributes.SCOPE_REQUEST);				
+				if(status!=HttpStatus.NOT_FOUND.value()) {return super.getErrorAttributes(webRequest, options);}
+				
+				Map<String, Object> errorAttributes = new HashMap<>();				
+				errorAttributes.put("httpStatus", HttpStatus.NOT_FOUND.value());
+				errorAttributes.put("messageCode", "404");				
+				errorAttributes.put("message", "Not Found");
+				errorAttributes.put("exception", "");
+				errorAttributes.put("propagation", projectName);
+	
+				return errorAttributes;
+			}
+		};
+	}
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Bean
+    @ConditionalOnExpression("'${interceptor.enabled:true}'.equals('true') && '${interceptor.locale.enabled:true}'.equals('true')")
+    HandlerInterceptor localeChangeInterceptor() {
+    	LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
+        interceptor.setParamName("lang");
+    	log.info(">> interceptor-localeChangeInterceptor [paramName:{}]",interceptor.getParamName());
+        return interceptor;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    //@Bean
+    //public DeviceHandlerMethodArgumentResolver deviceHandlerMethodArgumentResolver() {
+    //   return new DeviceHandlerMethodArgumentResolver();
+    //}
+
+    @Bean
+    SessionLocaleResolver localeResolver() {
+    	SessionLocaleResolver resolver = new SessionLocaleResolver();
+    	resolver.setDefaultLocale(Locale.KOREA);
+    	log.info(">> localeResolver.locale:{}",Locale.KOREA);
+    	return resolver;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Bean
+    HttpMessageConverter<String> messageConverter() {
+        return new StringHttpMessageConverter(Charset.forName("UTF-8"));
+    }
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	//@Bean
+	//public HttpSessionListener getHttpSessionListener() {
+	//    return new HttpSessionListener() {
+	//    	
+	//        @Override
+	//        public void sessionCreated(HttpSessionEvent se) {
+	//        	HttpSession s = se.getSession();
+	//    		log.debug(">> 세션.생성[id:{},creationTime:{},maxInactiveInterval:{}]",s.getId(),s.getCreationTime());
+	//        }
+	//
+	//        @Override
+	//        public void sessionDestroyed(HttpSessionEvent se) {
+	//        	HttpSession s = se.getSession();
+	//    		log.debug(">> 세션.종료[id:{},creationTime:{},maxInactiveInterval:{}]",s.getId(),s.getCreationTime());
+	//        }
+	//    };
+	//}
+
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	//addCorsMappings 메소드 적용으로 주석처리
+	//	@Bean
+	//	public FilterRegistrationBean<Filter> filterRegistrationBean() { 
+	//		CorsConfiguration config = new CorsConfiguration();
+	//		config.setMaxAge(1800L); // Access-Control-Max-Age : 클라이언트에서 preFlight의 요청 결과를 저장할 시간(초) 지정 (default:1800L)
+	//		config.addAllowedOrigin("*"); // Access-Control-Allow-Orgin : 요청을 보내는 페이지의 출처 (*, 도메인)
+	//		config.addAllowedHeader("*"); // Access-Control-Allow-Headers : 요청을 허용하는 헤더
+	//		config.addAllowedMethod("*"); // Access-Control-Allow-Methods : 요청을 허용하는 메소드 (Default : GET, POST, HEAD)
+	//		config.setAllowCredentials(true); // Access-Control-Allow-Credentials : 쿠키를 요청에 포함 (요청시 withCredentials:true)
+	//		config.addExposedHeader(PageAdvice.getPagingKey()); // JavaScript에서 참조하기 위한 추가, CORS의 경우 기본적으로 화면(JavaScript)에서 response header 값을 읽지 못함.
+	//		
+	//		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+	//		source.registerCorsConfiguration("/**", config);
+	//		
+	//		Filter filter = new BaseCorsFilter(source);
+	//		
+	//		FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(filter);
+	//		bean.setOrder(0);
+	//		
+	//		return bean;
+	//	} 
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 6 - 0
src/main/java/kr/co/iya/base/context/BaseToken.java

@@ -0,0 +1,6 @@
+package kr.co.iya.base.context;
+
+public interface BaseToken {	
+	public String getName();
+	public String getValue();
+}

+ 370 - 0
src/main/java/kr/co/iya/base/context/GcpContext.java

@@ -0,0 +1,370 @@
+package kr.co.iya.base.context;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.google.api.client.json.GenericJson;
+import com.google.api.client.json.JsonObjectParser;
+import com.google.api.client.json.gson.GsonFactory;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.cloud.bigquery.BigQuery;
+import com.google.cloud.bigquery.BigQueryOptions;
+import com.google.cloud.bigquery.CsvOptions;
+import com.google.cloud.bigquery.Dataset;
+import com.google.cloud.bigquery.Schema;
+import com.google.cloud.bigquery.Table;
+import com.google.cloud.bigquery.TableResult;
+import com.google.cloud.spring.autoconfigure.core.GcpProperties;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+
+import kr.co.iya.base.exception.SystemException;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@ConditionalOnExpression("T(kr.co.iya.base.GcpContext).isEnabled()")
+public class GcpContext {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Component
+	@ConfigurationProperties(prefix="utcb.cloud.gcp.context")
+	@Data
+	public static class UtcbGcpProperties{
+		private boolean enabled;
+		private String defaultProjectId;
+		private List<Resource> credentials;
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static interface BigQueryHandler {
+		
+		public BigQuery getBigQuery();
+		
+		public Map<String,Object> convert(Dataset dataset);
+		public Map<String,Object> convert(Table dataset);
+		public Map<String,Object> convert(Blob blob);		
+		public List<Map<String,Object>> convert(List<?> list);
+
+	    public List<Dataset> listDatasets(String projectId);
+	    public Dataset getDataset(String projectId, String datasetName);	    
+	    public boolean isExistDataset(String projectId, String datasetName);
+	    public Dataset createDataset(String projectId, String datasetName);
+		
+	    public List<Table> listTables(String projectId, String datasetName);	        
+	    public Table getTable(String projectId, String datasetName, String tableName);	        
+	    public boolean isExistTable(String projectId, String datasetName, String tableName);	    
+	    
+	    public TableResult executeQuery(String projectId, String datasetName, String query, boolean isLegacySql);
+	    
+	    public Table createTable(String projectId, String datasetName, String tableName, Schema schema);
+
+	    public Table createTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri); 
+	    public Table createTableFromCsv(String projectId, String datasetName, String tableName, Schema schema, String bucketUri); 
+	    public Table createTableFromCsv(String projectId, String datasetName, String tableName, Schema schema, String bucketUri, CsvOptions csvOptions);	    
+	    public Table createTableFromQuery(String projectId, String datasetName, String tableName, String query, boolean isLegacySql);
+	    
+	    public Table truncateTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri);
+	    public Table truncateTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri, CsvOptions csvOptions);
+	    public Table truncateTableFromQuery(String projectId, String datasvsetName, String tableName, String query, boolean isLegacySql);
+	    
+	    public Table appendTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri);
+	    public Table appendTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri, CsvOptions csvOptions);
+	    public Table appendTableFromQuery(String projectId, String datasetName, String tableName, String query, boolean isLegacySql);
+	   
+	    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri);
+	    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri, String fieldDelimiter);
+	    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri, String fieldDelimiter, boolean printHeader);
+	    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri, String fieldDelimiter, boolean printHeader, boolean isCompressed);
+
+	    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri);
+	    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri, String fieldDelimiter);
+	    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri, String fieldDelimiter, boolean printHeader);
+	    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri, String fieldDelimiter, boolean printHeader, boolean isCompressed);
+	}
+	
+	public static interface StorageHandler {
+		
+		public enum UploadType {buffer,stream};
+		public enum CompressType {gzip,nomal};
+		
+		public Storage getStorage();
+		
+		public Map<String,Object> convert(Blob blob);
+		public List<Map<String,Object>> convert(List<Blob> blob);
+		
+	    public boolean isExistBucket(String bucket);
+	    public boolean isExistObject(String bucket, String remotePath);
+		public boolean isDirectory(String bucket, String remotePath);
+	    
+	    public Bucket getBucket(String bucket);	    
+		public Blob getObject(String bucket, String remotePath);
+
+		public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetRemotePath);
+		public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetRemotePath, boolean isOverWrite);
+		public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetBucket, String targetRemotePath);
+		public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetBucket, String targetRemotePath, boolean isOverWrite);
+		
+		public List<String> listBuckets();
+		public List<Blob> listObjects(String bucket);
+		public List<Blob> listObjects(String bucket, String remotePath);
+
+		public List<Blob> upload(String bucket, String storePath, String remotePath) throws IOException;		
+		public List<Blob> upload(String bucket, String storePath, String remotePath, UploadType uploadType) throws IOException;
+		public List<Blob> upload(String bucket, String storePath, String remotePath, boolean isOverWrite) throws IOException;
+		public List<Blob> upload(String bucket, String storePath, String remotePath, UploadType uploadType, boolean isOverWrite) throws IOException;
+		
+		public BucketObjectInfo download(String bucket, String remotePath, OutputStream outputStream);
+		public BucketObjectInfo download(String bucket, String remotePath, File file);
+		public List<BucketObjectInfo> download(String bucket, String remotePath, String storePath);
+		public List<BucketObjectInfo> download(String bucket, String remotePath, String storePath, boolean isOverWrite);
+		
+		public void delete(String bucket, String remotePath);
+		
+		public Blob compose(String bucket, List<String> sources, String targetPath);
+	}
+	
+	@Data
+	public static class BucketObjectInfo implements Serializable {
+		private static final long serialVersionUID = 1L;
+		private Map<String,String> systemInfo;
+		private String bucket;
+		private String path;		
+		private String name;
+		private String contentType;		
+		private Long size;
+		private boolean isDirectory;
+		private ZonedDateTime createTime;
+		private ZonedDateTime updateTime;
+		private String downloadStorePath;
+		private Long downloadTime;
+		private Map<String, String> metadata;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Autowired
+	private UtcbGcpProperties utcbGcpProperties;
+
+	@PostConstruct
+	private void postConstruct() throws Exception {	
+		if(this.utcbGcpProperties == null || !this.utcbGcpProperties.isEnabled()) {return;}
+
+		JsonObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance());		    
+		List<Resource> list = this.utcbGcpProperties.getCredentials();
+		if(list != null) {
+			String firstProjectId = null;
+			for(Resource resource : list) {
+				GenericJson jsonData = parser.parseAndClose(resource.getInputStream(), StandardCharsets.UTF_8, GenericJson.class);
+				String projectId = jsonData.get("project_id").toString();
+				if(firstProjectId == null) {firstProjectId=projectId;}
+				log.debug(">> gcp.projectId: {}",projectId);
+				this.authContext.put(projectId,new GcpAuthInfo(projectId,resource,jsonData));
+			}
+			if(StringUtils.isEmpty(this.defaultProjectId)) {
+				this.defaultProjectId = firstProjectId;
+			}
+		}
+		this.isEnabled = true;
+	}
+
+	public void initContext(GcpProperties properties, BigQuery defaultBigQuery, Storage defaultStorage){
+		if(properties == null) {return;}
+		
+		try {
+			JsonObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance());	
+
+			Resource resource = properties.getCredentials().getLocation();
+			GenericJson jsonData = parser.parseAndClose(resource.getInputStream(),StandardCharsets.UTF_8, GenericJson.class);
+			String credentialProjectId = jsonData.get("project_id").toString();
+							
+			this.isEnabled = false;
+			this.authContext = new HashMap<>();
+			this.bigQueryContext = new HashMap<>();
+			this.bigQueryHandler = new HashMap<>();			
+			this.storageContext = new HashMap<>();
+			this.storageHandler = new HashMap<>();
+
+			if(StringUtils.isEmpty(properties.getProjectId())) {
+				properties.setProjectId(credentialProjectId);
+			}
+
+			this.defaultProjectId = properties.getProjectId();
+			
+			this.authContext.put(this.defaultProjectId ,new GcpAuthInfo(this.defaultProjectId,resource,jsonData));
+			if(defaultBigQuery != null) {
+				this.bigQueryContext.put(this.defaultProjectId, defaultBigQuery);
+				if(!this.defaultProjectId.equals(credentialProjectId)){this.bigQueryContext.put(credentialProjectId, defaultBigQuery);}
+			}
+			if(defaultStorage != null) {
+				this.storageContext.put(this.defaultProjectId, defaultStorage);
+				if(!this.defaultProjectId.equals(credentialProjectId)){this.storageContext.put(credentialProjectId, defaultStorage);}
+			}
+		}catch(Exception e){
+			throw new SystemException(e.getMessage());
+		}
+		
+		this.isEnabled = true;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Data
+	private class GcpAuthInfo{
+		private String projectId;
+		private Resource resource;
+		private GenericJson jsonData;
+
+		public GcpAuthInfo(String projectId,Resource resource,GenericJson jsonData) {
+			this.projectId = projectId;
+			this.resource = resource;
+			this.jsonData = jsonData;
+		}
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private boolean isEnabled = false;
+
+	private String defaultProjectId;
+
+	private Map<String,GcpAuthInfo> authContext = new HashMap<>();
+	
+	private Map<String,BigQuery> bigQueryContext = new HashMap<>();
+	private Map<String,BigQueryHandler> bigQueryHandler = new HashMap<>();
+	
+	private Map<String,Storage> storageContext = new HashMap<>();
+	private Map<String,StorageHandler> storageHandler = new HashMap<>();
+
+	private ObjectMapper mapper;
+	
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public boolean isEnabled() {
+		return this.isEnabled;
+	}
+	
+	public String getDefaultProjectId() {
+		return this.defaultProjectId;
+	}
+	
+	public Resource getResource(String projectId) {
+		GcpAuthInfo info = this.authContext.get(projectId);
+		return info == null?null:info.getResource();
+	}
+
+	public GenericJson getGenericJson(String projectId) {
+		GcpAuthInfo info = this.authContext.get(projectId);
+		return info == null?null:info.getJsonData();
+	}
+
+	public List<String> getProjects(){			
+	    return this.authContext.keySet().stream().collect(Collectors.toCollection(ArrayList::new));
+	}
+
+	public BigQuery getDefaultBigQuery() {
+		if(StringUtils.isEmpty(this.defaultProjectId)) {throw new SystemException("defaultProjectId is empty or null.");}
+		return this.getBigQuery(this.defaultProjectId);
+	}
+	
+	public BigQuery getBigQuery(String projectId) {
+		if(this.bigQueryContext != null && this.bigQueryContext.containsKey(projectId)) {
+			return this.bigQueryContext.get(projectId);
+		}
+		try {			
+			Resource resource = this.getResource(projectId);
+			if(resource == null) {throw new SystemException("serviceAccountCredentials's resource("+projectId+") is null");}
+			BigQuery bigQuery = BigQueryOptions.newBuilder()
+					.setCredentials(ServiceAccountCredentials.fromStream(resource.getInputStream()))
+					.build()
+					.getService();
+			this.bigQueryContext.put(projectId, bigQuery);
+		}catch(Exception e) {
+			throw new SystemException(e.getMessage());
+		}
+		return this.getBigQuery(projectId);
+	}
+	
+	public Storage getDefaultStorage() {
+		if(StringUtils.isEmpty(this.defaultProjectId)) {throw new SystemException("defaultProjectId is empty or null.");}
+		return this.getStorage(this.defaultProjectId);
+	}
+	
+	public Storage getStorage(String projectId) {
+		if(this.storageContext != null && this.storageContext.containsKey(projectId)) {
+			return this.storageContext.get(projectId);
+		}
+		try {			
+			Resource resource = this.getResource(projectId);
+			if(resource == null) {throw new SystemException("serviceAccountCredentials's resource("+projectId+") is null");}
+			Storage storage = StorageOptions.newBuilder()
+					.setCredentials(ServiceAccountCredentials.fromStream(resource.getInputStream()))
+					.build()
+					.getService();
+			this.storageContext.put(projectId, storage);			
+		}catch(Exception e) {
+			throw new SystemException(e.getMessage());
+		}
+		return this.getStorage(projectId);
+	}
+
+	public BigQueryHandler getBigQueryHandler(String projectId) {
+		if(this.bigQueryHandler == null) {return null;}
+		return this.bigQueryHandler.get(projectId);
+	}
+
+	public void setBigQueryHandler(String projectId,BigQueryHandler handler) {
+		this.bigQueryHandler.put(projectId, handler);
+	}
+	
+	public StorageHandler getStorageHandler(String projectId) {
+		if(this.storageHandler == null) {return null;}
+		return this.storageHandler.get(projectId);
+	}
+
+	public void setStorageHandler(String projectId,StorageHandler handler) {
+		this.storageHandler.put(projectId, handler);
+	}
+
+	public ObjectMapper getMapper() {
+		if(this.mapper == null) {
+			this.mapper = new ObjectMapper();
+			this.mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+			this.mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+			this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);	
+			this.mapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
+		}
+		return this.mapper;
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 327 - 0
src/main/java/kr/co/iya/base/context/MaskContext.java

@@ -0,0 +1,327 @@
+package kr.co.iya.base.context;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import kr.co.iya.base.support.AbstractMeta;
+import kr.co.iya.base.support.aid.BeanAidPack;
+import kr.co.iya.base.support.aid.RequestAttributesAidPack.RequestAttributesAid;
+import kr.co.iya.base.support.util.JsonUtilPack;
+import kr.co.iya.base.support.util.PropertiesUtilPack;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import kr.co.iya.base.support.util.PropertiesUtilPack.PropertiesUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@ConditionalOnExpression("T(kr.co.iya.base.MaskContext).isEnabled()")
+@ConditionalOnWebApplication
+public class MaskContext extends AbstractMeta {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Component
+	@ConfigurationProperties(prefix="utcb.mask")
+	public static class MaskProperties{
+		
+		private final String defaultRecource="classpath:mask/mask-default";
+		private boolean enabled = false;
+		private String ruleBase = "maskRule";
+		private long maskTokenExpireTime = 30L; //단위:초
+		private String spelBase = "T(kr.co.iya.base.BaseUtil).getMaskUtil()";		
+		private List<String> ruleProperties;
+		
+		@PostConstruct
+		private void postConstruct() {
+			if(this.ruleProperties==null) {this.ruleProperties = new ArrayList<>();}
+			if(!this.ruleProperties.contains(this.defaultRecource)) {this.ruleProperties.add(this.defaultRecource);}
+		}
+		
+		public String getDefaultRecource() {return this.defaultRecource;}
+		
+		public boolean isEnabled() {return enabled;}
+		public void setEnabled(boolean enabled) {this.enabled = enabled;}
+		
+		public String getRuleBase() {return ruleBase;}
+		public void setRuleBase(String ruleBase) {this.ruleBase = ruleBase;}
+		
+		public long getMaskTokenExpireTime() {return maskTokenExpireTime;}
+		public void setMaskTokenExpireTime(long maskTokenExpireTime) {this.maskTokenExpireTime = maskTokenExpireTime;}
+		
+		public String getSpelBase() {return spelBase;}
+		public void setSpelBase(String spelBase) {this.spelBase = spelBase;}
+		
+		public List<String> getRuleProperties() {return ruleProperties;}
+		public void setRuleProperties(List<String> ruleProperties) {this.ruleProperties = ruleProperties;}
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Autowired
+	private RequestAttributesAid requestAttributesAid;
+
+	@Autowired
+	private MaskProperties props;
+
+	private SpelExpressionParser spelParser;
+	private Map<String,Map<String,String>> maskRule;
+
+	private String[] defaultMaskRuleId;
+	
+	private JsonUtil jsonUtil;
+	private PropertiesUtil propertiesUtil;
+
+	private final String maskTokenMeta="*utcb123456789*";
+	private final String maskTokenName="_maskToken";
+	private final String maskApplyAttribute="_maskApplyAttribute";
+	private final TypeReference<List<Object>> listTypeReference = new TypeReference<List<Object>>(){};
+	private final TypeReference<Map<String,Object>> mapTypeReference = new TypeReference<Map<String,Object>>(){};
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@PostConstruct
+	private void postConstruct() throws Exception {
+		if(!this.isMaskSupport()) {return;}
+			
+		this.jsonUtil = JsonUtilPack.getJsonUtil(false);
+		this.propertiesUtil= PropertiesUtilPack.getPropertiesUtil(BeanAidPack.getBeanAid(), this.jsonUtil, "");
+
+		this.maskRule = new ConcurrentHashMap<>();
+		this.spelParser = new SpelExpressionParser();
+		
+		List<String> list = this.props.getRuleProperties();
+		if(list==null) {return;}
+		
+		for(String resource:list) {
+			log.debug(">> maskRuleProperties:{}",resource);
+			
+			Properties properties = this.getProperties(resource);
+			if(properties==null || properties.isEmpty()) {continue;}
+			
+			String key,value;
+			
+			Iterator<String> keys = properties.stringPropertyNames().iterator();
+			while(keys.hasNext()) {
+		      key = keys.next(); 
+		      value = properties.getProperty(key);
+		      log.debug(">> {}:{}",key,value);
+		      this.maskRule.put(key,this.buildMaskRule(this.props.getRuleBase(), value));
+		    }
+
+			if(resource.equals(this.props.getDefaultRecource())) {
+				this.defaultMaskRuleId = properties.stringPropertyNames().stream().sorted().toArray(String[]::new);
+				//log.debug(">> defaultMaskRuleId:{}",Arrays.toString(this.defaultMaskRuleId));
+			}
+		}
+		
+	    log.debug(">> maskRule: {}",maskRule.keySet());
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public boolean isMaskSupport() {
+		return this.props.isEnabled();
+	}
+	
+	public void setMaskApply(boolean isApply) {
+		if(this.isMaskSupport()) {
+			
+			this.requestAttributesAid.getHttpServletRequest().setAttribute(this.maskApplyAttribute,isApply);
+		}
+	}
+	
+	//용도: @MaskHandler없이 maskUtil을 사용한 경우 HttpServletRequest header에서 maskToken확인처리
+	public boolean isMaskApply() {
+		if(!this.isMaskSupport()) {return false;}
+		
+		HttpServletRequest request = this.requestAttributesAid.getHttpServletRequest();
+		Object attribute = request.getAttribute(this.maskApplyAttribute);
+		if(attribute!=null) {return (boolean)attribute;}
+
+		return this.isMaskApply(request.getHeader(this.maskTokenName));
+	}
+
+	//용도: @MaskHandler에 대한 처리시 ServerHttpRequest header에서 maskToken확인처리 (for MaskAdvice)
+	public boolean isMaskApply(ServerHttpRequest request) {
+		if(!this.isMaskSupport()) {return false;}
+		
+		return this.isMaskApply(request.getHeaders().getFirst(this.maskTokenName));
+	}
+	
+	public BaseToken createMaskToken() {
+
+		String uuid = UUID.randomUUID().toString().replace("-", "");
+		String maskTokenMeta = uuid+":"+MetaUtil.getCryptoUtil().getAES256().encryption(this.maskTokenMeta,uuid);
+		String maskTokenKey = maskTokenMeta+"@"+MetaUtil.getSystemUtil().getServerInstanceCode();
+		//log.debug(">> maskTokenMeta:{}",maskTokenMeta);
+
+		BaseToken token = new BaseToken() {
+			@Override public String getName() {return maskTokenName;}
+			@Override public String getValue() {return maskTokenKey;}			
+		};
+		
+		return token;
+	}
+
+	public Object doMask(Object body, String maskRuleId[]) {
+        //log.debug(">> bodyType:{}",body.getClass().getTypeName());
+        //log.debug(">> maskRuleId:{}",Arrays.toString(maskRuleId));
+
+		Object data = null;
+		if(data==null) {data = this.jsonUtil.getList(body);}
+		if(data==null) {data = this.jsonUtil.getMap(body);}
+		if(data==null) {data = body;}
+		
+		Map<String,String> maskRule = this.getMaskRule(maskRuleId);
+
+		if(maskRule==null || maskRule.isEmpty()) {return body;}
+		
+		return this.doMask(data, maskRule, this.props.getRuleBase());
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private boolean isMaskApply(String maskTokenKey) {
+
+		if(maskTokenKey==null || maskTokenKey.equals("")) {return true;}
+
+		//[check] maskTokenMeta
+		if(maskTokenKey.indexOf("@")>-1 && maskTokenKey.indexOf(":") < maskTokenKey.indexOf("@")) {
+			String maskTokenMeta = maskTokenKey.substring(0,maskTokenKey.indexOf("@"));
+			log.debug(">> maskTokenMeta:{}",maskTokenMeta);
+			
+			String uuid = maskTokenMeta.substring(0,maskTokenMeta.indexOf(":"));
+			String data= maskTokenMeta.substring(maskTokenMeta.indexOf(":"));
+			if(this.maskTokenMeta.equals(MetaUtil.getCryptoUtil().getAES256().decryption(data, uuid))) {
+				this.setMaskApply(false);
+				return false;
+			}
+		}
+
+		this.setMaskApply(true);		
+		return true;
+	}
+
+	private Object doMask(Object data, Map<String,String> maskRuleMap, String maskRuleKey) {
+		
+		if(data instanceof List) {
+			//log.debug(">> List");
+			List<Object> list = MetaUtil.getObjectMapperUtil().convert(data,this.listTypeReference);
+			for(int i=0;i<list.size();i++) {list.set(i, this.doMask(list.get(i), maskRuleMap, maskRuleKey+"[*]"));}
+			return list;
+		}
+		
+		if(data instanceof Map) {
+			//log.debug(">> Map");
+			Map<String,Object> map = MetaUtil.getObjectMapperUtil().convert(data,this.mapTypeReference);	
+			for(String key:map.keySet()) {map.put(key, this.doMask(map.get(key), maskRuleMap, maskRuleKey+"."+key));}
+			return map;
+		}
+
+		if(maskRuleMap.containsKey(maskRuleKey)) {
+			String maskSpel = maskRuleMap.get(maskRuleKey);
+			Boolean isEnable = true;
+			if(isEnable && maskSpel==null) {isEnable=false;}
+			if(isEnable && maskSpel.equals("")) {isEnable=false;}
+			if(isEnable && maskSpel.indexOf("@data")==-1) {isEnable=false;}			
+			if(isEnable) {
+				if(isEnable && maskSpel.startsWith("T")) {isEnable=false;}
+				if(isEnable && maskSpel.indexOf(this.props.getSpelBase())!=-1) {isEnable=false;}
+				if(isEnable) {maskSpel=this.props.getSpelBase()+"."+maskSpel;}	
+				
+				maskSpel = maskSpel.replace("@data",String.valueOf(data));
+				data = this.spelParser.parseExpression(maskSpel).getValue(String.class);
+				//log.debug(">> {}:{}",maskRuleKey,data);
+			}
+		} 
+
+		return data;
+	}	
+
+	private Properties getProperties(String resource) throws Exception {
+		
+		if(!resource.endsWith(".properties")) {resource=resource+".properties";}
+		
+		Properties properties = this.propertiesUtil.getProperties(new URL(resource));
+		if(properties==null || properties.isEmpty()) {return null;}
+		
+		Set<String> keySet = properties.stringPropertyNames();
+		
+		String key1,key2;
+		String value;
+
+		Iterator<String> iterator1 = keySet.iterator();
+		while(iterator1.hasNext()) {
+			key1 = iterator1.next();   
+			value = properties.getProperty(key1);
+			if(!value.matches(".*[$]\\{.*\\}.*")) {continue;}
+			
+			Iterator<String> iterator2 = keySet.iterator();
+			while(iterator2.hasNext()) {
+				key2 = iterator2.next();
+				if(key2.equals(key1)) {continue;}
+				value = value.replace("${"+key2+"}",properties.getProperty(key2));				
+				properties.replace(key1, value);				
+				if(!value.matches(".*[$]\\{.*\\}.*")) {break;}
+			}
+	    }
+	    
+	    return properties;
+	}
+	
+	private Map<String,String> buildMaskRule(String ruleBase, Object value){
+		Map<String,String> map = this.jsonUtil.getSerialMap(ruleBase, value);
+
+		String keys[] = map.keySet().stream().sorted().toArray(String[]::new);
+		for(String key:keys) {
+			if(key.indexOf("[0]")==-1){continue;}
+			map.put(key.replace("[0]","[*]"), map.get(key));
+			map.remove(key);
+		}
+	    return map;
+	}
+	
+	private Map<String,String> getMaskRule(String maskRuleId[]){
+
+		if(maskRuleId==null || Arrays.toString(maskRuleId).equals("[]")) {
+			maskRuleId=this.defaultMaskRuleId;
+		}
+		
+		String key = maskRuleId.length>1?Arrays.toString(maskRuleId):maskRuleId[0];		
+		log.debug(">> maskRuleId:{}",key);
+		
+		if(!this.maskRule.containsKey(key)) {
+			Map<String,String> map = new HashMap<>();			
+			Arrays.stream(maskRuleId)
+			.filter(s->this.maskRule.get(s)==null?false:true)
+			.forEach(s -> map.putAll(this.maskRule.get(s)));
+			if(!map.isEmpty()) {this.maskRule.put(key, map);}
+		}
+
+		return this.maskRule.get(key);
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 181 - 0
src/main/java/kr/co/iya/base/context/PageContext.java

@@ -0,0 +1,181 @@
+package kr.co.iya.base.context;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.json.simple.JSONObject;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.annotation.RequestScope;
+
+import kr.co.iya.base.advice.PageAdvice;
+import kr.co.iya.base.support.AbstractMeta;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@RequestScope
+@ConditionalOnExpression("T(kr.co.iya.base.PageContext).isEnabled()")
+@ConditionalOnWebApplication
+public class PageContext extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	private static class PageInfo {
+		private int rowSize = 10; 
+		private int pageNo = 0; //current
+		private int totalPage = 0;
+		private int start = 1;
+		private int last = 1;
+		private int totalCount = 0;
+		
+		public int getRowSize() {return rowSize;}
+		public void setRowSize(int rowSize) {this.rowSize = rowSize;}
+		public int getPageNo() {return pageNo;}
+		public void setPageNo(int pageNo) {this.pageNo = pageNo;}
+		public int getTotalPage() {return totalPage;}
+		public void setTotalPage(int totalPage) {this.totalPage = totalPage;}
+		public int getStart() {return start;}
+		public void setStart(int start) {this.start = start;}
+		public int getLast() {return last;}
+		public void setLast(int last) {this.last = last;}
+		public int getTotalCount() {return totalCount;}
+		public void setTotalCount(int totalCount) {this.totalCount = totalCount;}
+		@Override
+		public String toString() {
+			return "PageInfo [rowSize=" + rowSize + ", pageNo=" + pageNo + ", totalPage=" + totalPage + ", start="+ start + ", last=" + last + ", totalCount=" + totalCount + "]";
+		}				
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	private final static String[] pageParams = {"pageNo","rowSize","totalCount"};	
+
+	private PageInfo pageInfo;
+	
+	public PageContext() {
+		this.pageInfo = new PageInfo();
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public int getRowSize() {return this.pageInfo.getRowSize();}
+	public void setRowSize(int rowSize) {this.pageInfo.setRowSize(rowSize);}
+
+	public int getPageNo() {return this.pageInfo.getPageNo();}
+	public void setPageNo(int pageNo) {this.pageInfo.setPageNo(pageNo);}
+
+	public int getTotalPage() {return this.pageInfo.getTotalPage();}
+	public void setTotalPage(int totalPage) {this.pageInfo.setTotalPage(totalPage);}
+
+	public int getStart() {return this.pageInfo.getStart();}
+	public int getLast() {return this.pageInfo.getLast();}
+	public int getTotalCount() {return this.pageInfo.getTotalCount();}
+	public void setTotalCount(int totalCount) {
+		this.pageInfo.setTotalCount(totalCount);
+
+		int start=0;
+		if(this.getPageNo()>1) {
+			start=(this.pageInfo.getPageNo()-1)*this.getRowSize()+1;
+			start=start>=2?start-1:start;
+		}
+		this.pageInfo.setStart(start);
+		this.pageInfo.setLast((start-1)+this.getRowSize());		
+		this.pageInfo.setTotalPage((totalCount/this.getRowSize())+(totalCount%this.getRowSize()>0?1:0));
+	}
+		
+	public boolean isLast() {
+		if(this.getTotalPage()==0) {return false;}
+		return this.getTotalPage()-this.getPageNo()>0?false:true;
+	}
+	
+	public boolean isPaging() {
+		return this.getPageNo() > 0;
+	}
+	
+	public Map<String,Object> toMap() {
+		Map<String,Object> map = new HashMap<>();
+		map.put("rowSize",this.getRowSize());
+		map.put("pageNo",this.getPageNo());
+		map.put("totalPage",this.getTotalPage());
+		map.put("totalCount",this.getTotalCount());
+		//map.put("start",this.getStart());
+		//map.put("last",this.getLast());
+				
+		return map;		
+	}
+
+	public String toJsonBase64() {		
+		return MetaUtil.getBase64Util().getEncodeBase64String(this.toJson());	
+	}
+	
+	public String toJson() {
+		return new JSONObject(this.toMap()).toJSONString();		
+	}
+	
+	@Override
+	public String toString() {
+		return "PageContext"+this.pageInfo.toString();
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public void set(CheckerLambda checker, DataLambda data) {
+		this.set(this, checker, data);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static PageContext get(Map<String, Collection<String>> header){	
+		String pagingKey = PageAdvice.getPagingKey();
+	    if(!header.containsKey(pagingKey)) {return null;}
+	    
+		Collection<String> values = header.get(pagingKey);
+		if(values==null || values.isEmpty()) {return null;}
+		
+		//JSONObject jsonObj=getJsonObject(new ArrayList<>(values).get(0));
+		JSONObject jsonObj = MetaUtil.getJsonUtil().getJsonObject(new ArrayList<>(values).get(0));
+		if(jsonObj==null || jsonObj.isEmpty()) {return null;}
+		
+		PageContext pageContext = new PageContext();
+		pageContext.set(pageContext, key->jsonObj.containsKey(key),key->String.valueOf(jsonObj.get(key)));
+	
+		if(log.isDebugEnabled()) {
+			log.debug(">> pageContext[ref:header]:{}",pageContext.toString());
+		}
+		
+		return pageContext;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@FunctionalInterface
+	public interface CheckerLambda {public abstract boolean contains(String key);}
+
+	@FunctionalInterface
+	public interface DataLambda {public abstract String get(String key);}
+	
+	public void set(PageContext pageContext, CheckerLambda checker, DataLambda data) {
+		List<String> keyList = Arrays.asList(pageParams);
+		for(String key : keyList) {
+			if(!checker.contains(key)) {continue;}
+			
+			String value = data.get(key);
+
+			//log.debug(">> key:value=[{}:{}]",key,value);
+
+			if(value==null || value.equals("")) {continue;}
+			else if(key.equalsIgnoreCase(pageParams[0])) {pageContext.setPageNo(Integer.valueOf(value));}
+			else if(key.equalsIgnoreCase(pageParams[1])) {pageContext.setRowSize(Integer.valueOf(value));}
+			else if(key.equalsIgnoreCase(pageParams[2])) {pageContext.setTotalCount(Integer.valueOf(value));}				
+		}		
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 70 - 0
src/main/java/kr/co/iya/base/context/ProjectContext.java

@@ -0,0 +1,70 @@
+package kr.co.iya.base.context;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix="project")
+public class ProjectContext {
+
+	  private String groupId;
+	  private String artifact;
+	  private String version;
+	  private String name;
+	  private String description;
+	  
+	  private Meta meta;
+	  private Build build;
+	  
+	  public String getGroupId() {return groupId;}
+	  public void setGroupId(String groupId) {this.groupId = groupId;}
+	  
+	  public String getArtifact() {return artifact;}
+	  public void setArtifact(String artifact) {this.artifact = artifact;}
+	  
+	  public String getVersion() {return version;}
+	  public void setVersion(String version) {this.version = version;}
+	  
+	  public String getName() {return name;}
+	  public void setName(String name) {this.name = name;}
+	  
+	  public String getDescription() {return description;}
+	  public void setDescription(String description) {this.description = description;}
+
+	  public Meta getMeta() {return meta;}
+	  public void setMeta(Meta meta) {this.meta = meta;}
+	  
+	  public Build getBuild() {return build;}
+	  public void setBuild(Build build) {this.build = build;}
+	  
+	  public static class Meta{
+		  private String group;
+		  private String service;
+		  private String jobId;
+		  
+		  public String getGroup() {return group;}
+		  public void setGroup(String group) {this.group = group;}
+		  public String getService() {return service;}
+		  public void setService(String service) {this.service = service;}
+		  public String getJobId() {return jobId;}
+		  public void setJobId(String jobId) {this.jobId = jobId;}
+		  
+	  }
+	  
+	  public static class Build{
+		  private String finalName;
+		  private String revision;
+		  private String branch;
+		  private String timestamp;
+		  
+		  public String getFinalName() {return finalName;}
+		  public void setFinalName(String finalName) {this.finalName = finalName;}
+		  public String getRevision() {return revision;}
+		  public void setRevision(String revision) {this.revision = revision;}
+		  public String getBranch() {return branch;}
+		  public void setBranch(String branch) {this.branch = branch;}
+		  public String getTimestamp() {return timestamp;}
+		  public void setTimestamp(String timestamp) {this.timestamp = timestamp;}
+	  }
+}
+

+ 195 - 0
src/main/java/kr/co/iya/base/context/SchedulerContext.java

@@ -0,0 +1,195 @@
+package kr.co.iya.base.context;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Component;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@ConditionalOnExpression("T(kr.co.iya.base.SchedulerConfig).isEnabled()")
+@ConditionalOnProperty(name="utcb.scheduler.enabled", havingValue="true", matchIfMissing=false)
+public class SchedulerContext {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public enum SchedulerStatus {load,start,stop};
+	
+	public enum TaskStatus {load,ready,running,completed,block,cancel};
+	
+	public enum TriggerType {cron,fixedRate,fixedDelay,fixedTime};
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Data
+	public static class Task implements Serializable {
+		private static final long serialVersionUID = 1L;
+
+		@Schema(description="스케쥴 id : 스케쥴 등록시 자동구성되는 id")
+		private String taskId;
+		
+		@Schema(description="이름")
+		private String name;
+		
+		@Schema(description="설명")
+		private String description;
+		
+		@Schema(description="타스크 상태")
+		private TaskStatus status;
+		
+		@Schema(description="타스크 스케쥴 객체")
+		private ScheduledFuture<?> scheduledFuture;
+
+		@Schema(description="타스크 수행한 runnable객체")
+		private Runnable runnableWrapper;	
+		
+		@Schema(description="트리거 타입")
+		private TriggerType triggerType;
+
+		@Schema(description="트리거 시작일(yyyyMMddHHmmss)")
+		private String triggerStartDate;
+
+		@Schema(description="트리거 종료일(yyyyMMddHHmmss)")
+		private String triggerEndDate;
+		
+		@Schema(description="트리거 메모")
+		private String triggerMemo;
+
+		@Schema(description="cron표현식 : triggerType이 cron인 경우 cron표현식")
+		private String cronExpression;
+		
+		@Schema(description="수행후 지정한 시간마다 실행 : triggerType이 fixedRate인 단위:ms")
+		private String fixedRate;
+		
+		@Schema(description="수행후 지정한 시간이후 실행 : triggerType이 fixedDelay인 단위:ms")
+		private String fixedDelay;
+		
+		@Schema(description="지정한 시간에 수행 : triggerType이 fixedTime인 단위:ms")
+		private String fixedTime;	
+		
+		@Schema(description="프로세스 실행 횟수")
+		private int executeCount=0;
+		
+		@Schema(description="프로세스 실행요청정보 객체")
+		private Object executeRequest;
+		
+		@Schema(description="프로세스 실행응답정보 객체목록")
+		private List<Object> executeResponseList;		
+	}
+		
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private ThreadPoolTaskScheduler scheduler;
+	
+	private SchedulerStatus schedulerStatus;
+
+	private String schedulerStartTime;
+	
+	private String schedulerStopTime;
+	
+	private List<Task> taskList;
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public ThreadPoolTaskScheduler buildScheduler(int poolSize,String threadNamePrefix) {
+		this.scheduler = new ThreadPoolTaskScheduler();
+		this.scheduler.setPoolSize(poolSize);
+		this.scheduler.setThreadNamePrefix(threadNamePrefix);
+		this.scheduler.setWaitForTasksToCompleteOnShutdown(true);
+		this.scheduler.initialize();
+		return this.getScheduler();
+	}
+	
+	public ThreadPoolTaskScheduler getScheduler() {
+		return this.scheduler;
+	}
+
+	public void setScheduler(ThreadPoolTaskScheduler scheduler) {
+		this.scheduler = scheduler;
+	}
+
+	public SchedulerStatus getSchedulerStatus() {
+		return this.schedulerStatus;
+	}
+
+	public void setSchedulerStatus(SchedulerStatus schedulerStatus) {
+		this.schedulerStatus = schedulerStatus;
+	}
+
+	public String getSchedulerStartTime() {
+		return this.schedulerStartTime;
+	}
+
+	public void setSchedulerStartTime(String schedulerStartTime) {
+		this.schedulerStartTime = schedulerStartTime;
+	}
+
+	public String getSchedulerStopTime() {
+		return this.schedulerStopTime;
+	}
+
+	public void setSchedulerStopTime(String schedulerStopTime) {
+		this.schedulerStopTime = schedulerStopTime;
+	}
+
+	public List<Task> getTaskList() {
+		return this.taskList;
+	}
+
+	public void setTaskList(List<Task> taskList) {
+		this.taskList = taskList;
+	}
+
+	public Task getTask(String taskId) {
+		if(taskId==null || taskId.equals("")) {return null;}
+		if(this.taskList==null || this.taskList.size()==0) {return null;}
+		
+		Task targetTask=null;
+		for(Task task : this.taskList) {
+			if(task.getTaskId().equals(taskId)) {targetTask=task;break;}
+		}
+		return targetTask;
+	}
+	
+    public int getTaskCount(TaskStatus status) {
+    	if(this.taskList==null) {return 0;}
+        int count=0;
+        for(Task task : this.taskList) {
+        	if(task.getStatus().equals(status)) {count++;}
+        }  
+    	return count;
+    }
+
+    public void optimizeTaskList() {
+    	if(this.taskList==null || this.taskList.isEmpty()) {return;}
+    	
+    	List<Task> newlist = new ArrayList<>();
+    	
+    	log.debug(">> before-optimize:{}",this.getTaskList().size());    	
+    	for(Task task : this.taskList) {
+    		 boolean isRemove=false;
+    		 if(!isRemove && task.getScheduledFuture()==null) {isRemove=true;}
+    		 if(!isRemove && task.getScheduledFuture().isCancelled()) {isRemove=true;}
+    		 if(!isRemove && task.getScheduledFuture().isDone()) {isRemove=true;}
+    		 if(!isRemove && task.getStatus().equals(TaskStatus.cancel)) {isRemove=true;}
+    		 if(isRemove) {
+    			 log.debug(">> gabage:{}",task.toString());
+    			 continue;
+    		 }
+    		 newlist.add(task);
+    	 }
+    	 this.setTaskList(newlist);
+    	 log.debug(">> afteroptimize:{}",this.getTaskList().size());    	 
+    }
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 41 - 0
src/main/java/kr/co/iya/base/exception/BusinessException.java

@@ -0,0 +1,41 @@
+package kr.co.iya.base.exception;
+
+import org.springframework.http.HttpStatus;
+
+import kr.co.iya.base.advice.ExceptionAdvice.ExceptionMessageLambda;
+
+public class BusinessException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+	
+	private final String message;
+
+	private final HttpStatus httpStatus;
+
+	public String getMessage() {
+		return message;
+	}
+
+	public HttpStatus getHttpStatus() {
+		return httpStatus;
+	}
+
+	public BusinessException(ExceptionMessageLambda messageLambda) {
+		this(messageLambda.getMessage());
+	}
+
+	public BusinessException(String message) {
+		this(message,HttpStatus.CONFLICT);
+	}
+
+	public BusinessException(ExceptionMessageLambda messageLambda, HttpStatus httpStatus) {
+		this(messageLambda.getMessage(),httpStatus);
+	}
+
+	public BusinessException(String message, HttpStatus httpStatus) {
+		super(message);
+		this.message = message;
+		this.httpStatus = httpStatus;
+	}
+
+}

+ 41 - 0
src/main/java/kr/co/iya/base/exception/SystemException.java

@@ -0,0 +1,41 @@
+package kr.co.iya.base.exception;
+
+import org.springframework.http.HttpStatus;
+
+import kr.co.iya.base.advice.ExceptionAdvice.ExceptionMessageLambda;
+
+public class SystemException extends RuntimeException{
+
+	private static final long serialVersionUID = 1L;
+	
+	private final String message;
+
+	private final HttpStatus httpStatus;
+
+	public String getMessage() {
+		return message;
+	}
+
+	public HttpStatus getHttpStatus() {
+		return httpStatus;
+	}
+
+	public SystemException(ExceptionMessageLambda messageLambda) {
+		this(messageLambda.getMessage());
+	}
+	
+	public SystemException(String message) {
+		this(message,HttpStatus.INTERNAL_SERVER_ERROR);
+	}
+
+	public SystemException(ExceptionMessageLambda messageLambda, HttpStatus httpStatus) {
+		this(messageLambda.getMessage(),httpStatus);
+	}
+	
+	public SystemException(String message, HttpStatus httpStatus) {
+		super(message);
+		this.message = message;
+		this.httpStatus = httpStatus;
+	}
+
+}

+ 25 - 0
src/main/java/kr/co/iya/base/support/AbstractMeta.java

@@ -0,0 +1,25 @@
+package kr.co.iya.base.support;
+
+import java.util.Map;
+
+import org.springframework.context.ApplicationContext;
+
+public abstract class AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	protected final class MetaUtil extends AbstractUtil{};
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static final ApplicationContext getApplicationContext() {return MetaUtil.getApplicationContext();}
+	
+	public static final Map<String,Object> getThreadLocalMap() {return MetaUtil.getThreadLocalMap();}
+	
+	public static final <T> T getBean(Class<T> type) {return MetaUtil.getBean(type);}
+	
+	public static final <T> T getBean(String beanId, Class<T> type) {return MetaUtil.getBean(beanId, type);}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 65 - 0
src/main/java/kr/co/iya/base/support/AbstractMetaBatch.java

@@ -0,0 +1,65 @@
+package kr.co.iya.base.support;
+
+import org.springframework.http.HttpHeaders;
+
+import kr.co.iya.base.context.MaskContext;
+import kr.co.iya.base.context.PageContext;
+import kr.co.iya.base.support.aid.ContextAid;
+import kr.co.iya.base.support.util.Base64UtilPack.Base64Util;
+import kr.co.iya.base.support.util.BashProcessUtilPack.BashProcessUtil;
+import kr.co.iya.base.support.util.CompressUtilPack.CompressUtil;
+import kr.co.iya.base.support.util.CryptoUtilPack.CryptoUtil;
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+import kr.co.iya.base.support.util.GcpUtilPack.GcpUtil;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import kr.co.iya.base.support.util.KafkaUtilPack.KafkaUtil;
+import kr.co.iya.base.support.util.LobConverterUtilPack.LobConverterUtil;
+import kr.co.iya.base.support.util.LocaleUtilPack.LocaleUtil;
+import kr.co.iya.base.support.util.MaskUtilPack.MaskUtil;
+import kr.co.iya.base.support.util.MessageUtilPack.MessageUtil;
+import kr.co.iya.base.support.util.ObjectMapperUtilPack.ObjectMapperUtil;
+import kr.co.iya.base.support.util.PropertiesUtilPack.PropertiesUtil;
+import kr.co.iya.base.support.util.RestTemplateUtilPack.RestTemplateUtil;
+import kr.co.iya.base.support.util.SftpUtilPack.SftpUtil;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import kr.co.iya.base.support.util.ThreadUtilPack.ThreadUtil;
+import kr.co.iya.base.support.util.TimeUtilPack.TimeUtil;
+import kr.co.iya.base.support.util.TransactionUtilPack.TransactionUtil;
+
+public abstract class AbstractMetaBatch extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static final PageContext getPageContext() {return ContextAid.getPageContext();}
+    public static final PageContext getPageContext(int totalCount) {return ContextAid.getPageContext(totalCount);}
+    public static final PageContext getPageContext(HttpHeaders header) {return ContextAid.getPageContext(header);}
+    public static final MaskContext getMaskContext() {return ContextAid.getMaskContext();}
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static final Base64Util getBase64Util() {return MetaUtil.getBase64Util();}
+	public static final BashProcessUtil getBashProcessUtil() {return MetaUtil.getBashProcessUtil();}	
+	public static final CompressUtil getCompressUtil() {return MetaUtil.getCompressUtil();} 
+	public static final CryptoUtil getCryptoUtil() {return MetaUtil.getCryptoUtil();} 
+	public static final FileUtil getFileUtil() { return MetaUtil.getFileUtil();} 
+	public static final GcpUtil getGcpUtil() {return getBean(GcpUtil.class);}	
+
+	public static final JsonUtil getJsonUtil() {return MetaUtil.getJsonUtil();}
+    public static final KafkaUtil getKafkaUtil() {return MetaUtil.getKafkaUtil();}	
+	public static final LobConverterUtil getLobConvertUtil() {return MetaUtil.getLobConvertUtil();}
+	public static final LocaleUtil getLocaleUtil() {return MetaUtil.getLocaleUtil();} 
+	public static final MaskUtil getMaskUtil() {return MetaUtil.getMaskUtil();} 	
+	public static final MessageUtil getMessageUtil() {return MetaUtil.getMessageUtil();} 
+	public static final ObjectMapperUtil getObjectMapperUtil() {return MetaUtil.getObjectMapperUtil();}
+	public static final PropertiesUtil getPropertiesUtil() {return MetaUtil.getPropertiesUtil();}
+	public static final RestTemplateUtil getRestTemplateUtil() {return MetaUtil.getRestTemplateUtil();} 	
+
+	public static final SftpUtil getSftpUtil() {return MetaUtil.getSftpUtil();}
+	public static final SystemUtil getSystemUtil() {return MetaUtil.getSystemUtil();}
+	public static final ThreadUtil getThreadUtil() {return MetaUtil.getThreadUtil();}
+	public static final TimeUtil getTimeUtil() {return MetaUtil.getTimeUtil();} 
+    public static final TransactionUtil getTransactionUtil() {return MetaUtil.getTransactionUtil();}	
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 70 - 0
src/main/java/kr/co/iya/base/support/AbstractMetaOnline.java

@@ -0,0 +1,70 @@
+package kr.co.iya.base.support;
+
+import org.springframework.http.HttpHeaders;
+
+import kr.co.iya.base.context.MaskContext;
+import kr.co.iya.base.context.PageContext;
+import kr.co.iya.base.support.aid.ContextAid;
+import kr.co.iya.base.support.util.Base64UtilPack.Base64Util;
+import kr.co.iya.base.support.util.BashProcessUtilPack.BashProcessUtil;
+import kr.co.iya.base.support.util.CompressUtilPack.CompressUtil;
+import kr.co.iya.base.support.util.CryptoUtilPack.CryptoUtil;
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+import kr.co.iya.base.support.util.GcpUtilPack.GcpUtil;
+import kr.co.iya.base.support.util.HttpRequestUtilPack.HttpRequestUtil;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import kr.co.iya.base.support.util.KafkaUtilPack.KafkaUtil;
+import kr.co.iya.base.support.util.LobConverterUtilPack.LobConverterUtil;
+import kr.co.iya.base.support.util.LocaleUtilPack.LocaleUtil;
+import kr.co.iya.base.support.util.MaskUtilPack.MaskUtil;
+import kr.co.iya.base.support.util.MessageUtilPack.MessageUtil;
+import kr.co.iya.base.support.util.ObjectMapperUtilPack.ObjectMapperUtil;
+import kr.co.iya.base.support.util.PropertiesUtilPack.PropertiesUtil;
+import kr.co.iya.base.support.util.RestTemplateUtilPack.RestTemplateUtil;
+import kr.co.iya.base.support.util.SessionUtilPack.SessionUtil;
+import kr.co.iya.base.support.util.SftpUtilPack.SftpUtil;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import kr.co.iya.base.support.util.ThreadUtilPack.ThreadUtil;
+import kr.co.iya.base.support.util.TimeUtilPack.TimeUtil;
+import kr.co.iya.base.support.util.TransactionUtilPack.TransactionUtil;
+
+public abstract class AbstractMetaOnline extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static final PageContext getPageContext() {return ContextAid.getPageContext();}
+    public static final PageContext getPageContext(int totalCount) {return ContextAid.getPageContext(totalCount);}
+    public static final PageContext getPageContext(HttpHeaders header) {return ContextAid.getPageContext(header);}
+    public static final MaskContext getMaskContext() {return ContextAid.getMaskContext();}
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static final Base64Util getBase64Util() {return MetaUtil.getBase64Util();}
+	public static final BashProcessUtil getBashProcessUtil() {return MetaUtil.getBashProcessUtil();}
+	public static final CompressUtil getCompressUtil() {return MetaUtil.getCompressUtil();} 
+	public static final CryptoUtil getCryptoUtil() {return MetaUtil.getCryptoUtil();} 
+	public static final FileUtil getFileUtil() { return MetaUtil.getFileUtil();} 
+	public static final GcpUtil getGcpUtil() {return getBean(GcpUtil.class);}	
+	
+	public static final HttpRequestUtil getHttpRequestUtil() {return MetaUtil.getHttpRequestUtil();}
+	
+	public static final JsonUtil getJsonUtil() {return MetaUtil.getJsonUtil();}
+    public static final KafkaUtil getKafkaUtil() {return MetaUtil.getKafkaUtil();}	
+	public static final LobConverterUtil getLobConvertUtil() {return MetaUtil.getLobConvertUtil();}
+	public static final LocaleUtil getLocaleUtil() {return MetaUtil.getLocaleUtil();} 
+	public static final MaskUtil getMaskUtil() {return MetaUtil.getMaskUtil();} 	
+	public static final MessageUtil getMessageUtil() {return MetaUtil.getMessageUtil();} 
+	public static final ObjectMapperUtil getObjectMapperUtil() {return MetaUtil.getObjectMapperUtil();}
+	public static final PropertiesUtil getPropertiesUtil() {return MetaUtil.getPropertiesUtil();}
+	public static final RestTemplateUtil getRestTemplateUtil() {return MetaUtil.getRestTemplateUtil();}
+	
+	public static final SessionUtil getSessionUtil() {return MetaUtil.getSessionUtil();} 	
+	public static final SftpUtil getSftpUtil() {return MetaUtil.getSftpUtil();}
+	public static final SystemUtil getSystemUtil() {return MetaUtil.getSystemUtil();}
+	public static final ThreadUtil getThreadUtil() {return MetaUtil.getThreadUtil();}
+	public static final TimeUtil getTimeUtil() {return MetaUtil.getTimeUtil();} 
+    public static final TransactionUtil getTransactionUtil() {return MetaUtil.getTransactionUtil();}	
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 88 - 0
src/main/java/kr/co/iya/base/support/AbstractUtil.java

@@ -0,0 +1,88 @@
+package kr.co.iya.base.support;
+
+import java.util.Map;
+
+import org.springframework.context.ApplicationContext;
+
+import kr.co.iya.base.advice.ExceptionAdvice;
+import kr.co.iya.base.support.aid.AwareAidPack.ApplicationContextAid;
+import kr.co.iya.base.support.aid.AwareAidPack.ThreadLocalAid;
+import kr.co.iya.base.support.util.Base64UtilPack.Base64Util;
+import kr.co.iya.base.support.util.BashProcessUtilPack.BashProcessUtil;
+import kr.co.iya.base.support.util.CompressUtilPack.CompressUtil;
+import kr.co.iya.base.support.util.CryptoUtilPack.CryptoUtil;
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+import kr.co.iya.base.support.util.GcpUtilPack.GcpUtil;
+import kr.co.iya.base.support.util.HttpRequestUtilPack.HttpRequestUtil;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import kr.co.iya.base.support.util.KafkaUtilPack.KafkaUtil;
+import kr.co.iya.base.support.util.LobConverterUtilPack.LobConverterUtil;
+import kr.co.iya.base.support.util.LocaleUtilPack.LocaleUtil;
+import kr.co.iya.base.support.util.MaskUtilPack.MaskUtil;
+import kr.co.iya.base.support.util.MessageUtilPack.MessageUtil;
+import kr.co.iya.base.support.util.ObjectMapperUtilPack.ObjectMapperUtil;
+import kr.co.iya.base.support.util.PropertiesUtilPack.PropertiesUtil;
+import kr.co.iya.base.support.util.RestTemplateUtilPack.RestTemplateUtil;
+import kr.co.iya.base.support.util.SessionUtilPack.SessionUtil;
+import kr.co.iya.base.support.util.SftpUtilPack.SftpUtil;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import kr.co.iya.base.support.util.ThreadUtilPack.ThreadUtil;
+import kr.co.iya.base.support.util.TimeUtilPack.TimeUtil;
+import kr.co.iya.base.support.util.TransactionUtilPack.TransactionUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class AbstractUtil {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static ApplicationContext getApplicationContext() {return ApplicationContextAid.getApplicationContext();}
+	public static Map<String,Object> getThreadLocalMap() {return ThreadLocalAid.getThreadLocalMap();}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static <T> T getBean(Class<T> type) {
+		try {
+			return getApplicationContext().getBean(type);
+		}catch(Exception e) {
+			log.error("\n{}",ExceptionAdvice.getTrace(e));
+			return null;
+		}
+	}
+	
+	public static <T> T getBean(String beanId, Class<T> type) {
+		try {
+			return getApplicationContext().getBean(beanId,type);
+		}catch(Exception e) {
+			log.error("\n{}",ExceptionAdvice.getTrace(e));
+			return null;
+		}
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	// kr.co.iya.base.config.AidBaseConfig에 정의된 bean에 대한 method 등록
+	
+	public static final Base64Util getBase64Util() {return getBean(Base64Util.class);}
+	public static final BashProcessUtil getBashProcessUtil() {return getBean(BashProcessUtil.class);}
+	public static final CompressUtil getCompressUtil() {return getBean(CompressUtil.class);} 
+	public static final CryptoUtil getCryptoUtil() {return getBean(CryptoUtil.class);} 
+	public static final FileUtil getFileUtil() { return getBean(FileUtil.class);} 	
+	public static final GcpUtil getGcpUtil() {return getBean(GcpUtil.class);}	
+	
+	public static final HttpRequestUtil getHttpRequestUtil() {return getBean(HttpRequestUtil.class);}
+	public static final JsonUtil getJsonUtil() {return getBean(JsonUtil.class);}
+    public static final KafkaUtil getKafkaUtil() {return getBean(KafkaUtil.class);}	
+	public static final LobConverterUtil getLobConvertUtil() {return getBean(LobConverterUtil.class);}
+	public static final LocaleUtil getLocaleUtil() {return getBean(LocaleUtil.class);} 
+	public static final MaskUtil getMaskUtil() {return getBean(MaskUtil.class);} 	
+	public static final MessageUtil getMessageUtil() {return getBean(MessageUtil.class);} 
+	public static final ObjectMapperUtil getObjectMapperUtil() {return getBean(ObjectMapperUtil.class);}
+	public static final PropertiesUtil getPropertiesUtil() {return getBean(PropertiesUtil.class);}
+	public static final RestTemplateUtil getRestTemplateUtil() {return getBean(RestTemplateUtil.class);} 	
+	public static final SessionUtil getSessionUtil() {return getBean(SessionUtil.class);} 	
+	public static final SftpUtil getSftpUtil() {return getBean(SftpUtil.class);}
+	public static final SystemUtil getSystemUtil() {return getBean(SystemUtil.class);}
+	public static final ThreadUtil getThreadUtil() {return getBean(ThreadUtil.class);}
+	public static final TimeUtil getTimeUtil() {return getBean(TimeUtil.class);} 
+    public static final TransactionUtil getTransactionUtil() {return getBean(TransactionUtil.class);}
+} 

+ 48 - 0
src/main/java/kr/co/iya/base/support/aid/AwareAidPack.java

@@ -0,0 +1,48 @@
+package kr.co.iya.base.support.aid;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+public final class AwareAidPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static class ApplicationContextAid implements ApplicationContextAware {
+	
+		private static ApplicationContext applicationContext;
+		
+	    public static ApplicationContext getApplicationContext() {
+	        return applicationContext;
+	    }
+	
+		@Override
+	    public void setApplicationContext(ApplicationContext context) throws BeansException {
+	    	applicationContext=context;
+	    }		
+	}
+
+	public static class ThreadLocalAid  {
+
+		private static ThreadLocal<Map<String,Object>> threadLocal = null;
+	
+		public static synchronized Map<String,Object> getThreadLocalMap() {
+			if(threadLocal==null) {threadLocal = new ThreadLocal<>();}
+			if(threadLocal.get()==null) {threadLocal.set(new HashMap<>());}
+			return threadLocal.get();
+		}
+	
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static ApplicationContextAid getApplicationContextAid() {		
+		return new ApplicationContextAid();
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 97 - 0
src/main/java/kr/co/iya/base/support/aid/BeanAidPack.java

@@ -0,0 +1,97 @@
+package kr.co.iya.base.support.aid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class BeanAidPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface BeanAid extends BeanNameAware, ApplicationContextAware{
+
+		public ApplicationContext getApplicationContext();
+		
+		public void printApplicationContextInfo();
+		
+		public Logger getLogger(Class<?> clazz);
+				
+		public <T> T getBean(String beanId,Class<T> type);
+
+		public <T> T getBean(Class<T> type);
+
+		public Object getBean(String beanId);
+		
+		public String getBeanName();
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static BeanAid getBeanAid() {
+		
+		return new BeanAid() {
+
+			private ApplicationContext applicationContext=null;
+			
+			private String beanName=null;
+			
+			@Override
+			public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+				this.applicationContext=applicationContext;
+			}
+
+			@Override
+			public ApplicationContext getApplicationContext() {
+				return applicationContext;
+			}
+
+			@Override
+			public void setBeanName(String beanName) {
+				this.beanName=beanName;
+			}
+			
+			@Override
+			public String getBeanName() {
+				return this.beanName;
+			}
+			 
+			@Override
+			public <T> T getBean(String beanId, Class<T> type) {
+				return getApplicationContext().getBean(beanId, type);
+			}
+
+			@Override
+			public <T> T getBean(Class<T> type) {
+				return getApplicationContext().getBean(type);		
+			}
+			
+			@Override
+			public Object getBean(String beanId) {
+				return getApplicationContext().getBean(beanId);
+			}
+
+			@Override
+			public void printApplicationContextInfo() {
+				log.debug(">> applicationContext.getId: {}",applicationContext.getId());
+				log.debug(">> applicationContext.getApplicationName: {}",applicationContext.getApplicationName());
+				log.debug(">> applicationContext.getDisplayName: {}",applicationContext.getDisplayName());
+				log.debug(">> applicationContext.BeanDefinitionCount: {}",applicationContext.getBeanDefinitionCount());
+				log.debug(">> applicationContext.toString: {}",applicationContext.toString());
+			}
+
+			@Override			
+			public Logger getLogger(Class<?> clazz) {
+				return LoggerFactory.getLogger(clazz);
+			}
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 54 - 0
src/main/java/kr/co/iya/base/support/aid/ContextAid.java

@@ -0,0 +1,54 @@
+package kr.co.iya.base.support.aid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.json.simple.JSONObject;
+import org.springframework.http.HttpHeaders;
+
+import kr.co.iya.base.advice.PageAdvice;
+import kr.co.iya.base.context.MaskContext;
+import kr.co.iya.base.context.PageContext;
+import kr.co.iya.base.support.AbstractMeta;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class ContextAid extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static final PageContext getPageContext() {
+    	if(getThreadLocalMap().containsKey("pageContext")) {return (PageContext)getThreadLocalMap().get("pageContext");}    	
+    	return getBean(PageContext.class);
+    }
+
+    public static final PageContext getPageContext(int totalCount) {
+		PageContext context = getPageContext();
+		if(context!=null) {context.setTotalCount(totalCount);}
+	    return context;
+    }
+
+    public static final PageContext getPageContext(HttpHeaders header) {
+
+		Collection<String> values = header.get(PageAdvice.getPagingKey());
+
+		String value = new ArrayList<>(values).get(0);
+		
+		JSONObject jsonObj = MetaUtil.getJsonUtil().getJsonObject(MetaUtil.getBase64Util().getDecodeBase64String(value));
+		if(jsonObj==null || jsonObj.isEmpty()) {throw new RuntimeException("json conversion error");}
+		
+		PageContext pageContext = new PageContext();
+		pageContext.set(pageContext, key->jsonObj.containsKey(key),key->String.valueOf(jsonObj.get(key)));
+	
+		if(log.isDebugEnabled()) {
+			log.debug(">> pageContext[response:header]:{}",pageContext.toString());
+		}
+		
+		return pageContext;
+	}
+	
+    public static final MaskContext getMaskContext() {
+    	return MetaUtil.getBean(MaskContext.class);
+    }	
+ 
+}

+ 108 - 0
src/main/java/kr/co/iya/base/support/aid/RequestAttributesAidPack.java

@@ -0,0 +1,108 @@
+package kr.co.iya.base.support.aid;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class RequestAttributesAidPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public interface RequestAttributesAid {
+
+		public ApplicationContext getApplicationContext();
+		
+		public void printApplicationContextInfo();
+		
+		public Logger getLogger(Class<?> clazz);
+		
+		public ServletRequestAttributes getServletRequestAttributes();
+		
+		public HttpServletRequest getHttpServletRequest();
+
+		public HttpServletResponse getHttpServletResponse();
+
+		public HttpSession getHttpSession();
+		
+		public ServletContext getServletContext();
+		
+		public WebApplicationContext getWebApplicationContext();
+		
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static RequestAttributesAid getRequestAttributesAid() {
+		
+		return new RequestAttributesAid() {
+			
+			@Override
+			public ServletRequestAttributes getServletRequestAttributes() {
+				return (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
+			}
+			
+			@Override
+			public HttpServletRequest getHttpServletRequest() {				
+				return this.getServletRequestAttributes().getRequest();
+			}
+			
+			@Override
+			public HttpServletResponse getHttpServletResponse() {				
+				return this.getServletRequestAttributes().getResponse();
+			}
+			
+			@Override
+			public HttpSession getHttpSession() {
+				return this.getHttpServletRequest().getSession();
+			}
+			
+			@Override
+			public ServletContext getServletContext() {
+				return this.getHttpSession().getServletContext();
+			}
+			
+			@Override
+			public WebApplicationContext getWebApplicationContext() {
+				return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
+			}
+			
+			@Override
+			public ApplicationContext getApplicationContext() {
+				return (ApplicationContext)this.getWebApplicationContext();
+			}
+			
+			@Override
+			public void printApplicationContextInfo() {
+				ApplicationContext applicationContext=getApplicationContext();
+				log.debug(">>applicationContext.getId: {}",applicationContext.getId());
+				log.debug(">>applicationContext.getApplicationName: {}",applicationContext.getApplicationName());
+				log.debug(">>applicationContext.getDisplayName: {}",applicationContext.getDisplayName());
+				log.debug(">>applicationContext.BeanDefinitionCount: {}",applicationContext.getBeanDefinitionCount());
+				log.debug(">>applicationContext.toString: {}",applicationContext.toString());
+				log.debug(">>ServletContext: {}",getServletContext().toString());
+				log.debug(">>HttpServletRequest: {}",getHttpServletRequest().toString());
+				log.debug(">>HttpSession: {}",getHttpSession().toString());
+			}	
+			
+			@Override
+			public Logger getLogger(Class<?> clazz) {
+				return LoggerFactory.getLogger(clazz);
+			}	
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 74 - 0
src/main/java/kr/co/iya/base/support/etc/PathCheck.java

@@ -0,0 +1,74 @@
+package kr.co.iya.base.support.etc;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.AntPathMatcher;
+
+//@Slf4j
+public final class PathCheck {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static boolean isRequestPathCheck(HttpServletRequest request, Set<String> set) {
+		return isPathCheck(request,set==null?null:new ArrayList<>(set),null);
+	}
+
+	public static boolean isRequestPathCheck(HttpServletRequest request, Set<String> set, Map<String,String> map) {
+		return isPathCheck(request,set==null?null:new ArrayList<>(set),map);
+	}
+
+	public static boolean isRequestPathCheck(HttpServletRequest request, List<String> list) {
+		return isPathCheck(request,list,null);
+	}
+
+	public static boolean isRequestPathCheck(HttpServletRequest request, List<String> list, Map<String,String> map) {
+		return isPathCheck(request,list,map);
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	private static boolean isPathCheck(HttpServletRequest request, List<String> list, Map<String,String> map) {
+		if(list==null){return false;}
+
+		AntPathMatcher antPathMatcher = new AntPathMatcher();
+
+		String context=(request.getContextPath().equals("/")?request.getContextPath():request.getContextPath()+"/");
+		
+		String requestMethod = request.getMethod();
+		String requestURI = request.getRequestURI();		
+		if(requestURI.lastIndexOf(";")!=-1){ 
+			requestURI=requestURI.substring(0,requestURI.indexOf(";"));
+		}
+
+		String matchPattern,matchMethod;
+		for(String pattern:list){			
+			if(pattern==null || pattern.equals("")){continue;}
+			
+			matchPattern = (pattern.equals("/")?"":pattern);
+			matchPattern = context+(matchPattern.startsWith("/")?matchPattern.substring(1):matchPattern);	
+			
+			if(antPathMatcher.match(matchPattern,requestURI)){
+				if(map==null || map.isEmpty()) {return true;}
+			
+				matchMethod = map.get(matchPattern);				
+				boolean isEnable=false;
+				if(!isEnable && matchMethod==null) {isEnable=true;}
+				if(!isEnable && matchMethod.equals("")) {isEnable=true;}
+				if(!isEnable && matchMethod.indexOf(requestMethod)>-1) {isEnable=true;}
+				if(isEnable) {
+					//log.debug(">> match[pattern:{},requestURI:{},method:{}]",matchPattern,requestURI,requestMethod);			
+					return true;
+				}
+			}
+		}
+		
+		return false;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 135 - 0
src/main/java/kr/co/iya/base/support/etc/Sweeper.java

@@ -0,0 +1,135 @@
+package kr.co.iya.base.support.etc;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class Sweeper  {
+
+	private static final Logger logger=LoggerFactory.getLogger(Sweeper.class);
+		
+	private static String[] replaceEashCRLF1 = new String[] {"\n","%0a","%0A","\r","%0d","%0D"};
+	private static String[] replaceEashCRLF2 = new String[] {"","","","","",""};
+	
+	private static Pattern[] xssPatterns = new Pattern[] {
+		Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE)
+		,Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
+		,Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
+		,Pattern.compile("</script>", Pattern.CASE_INSENSITIVE)
+		,Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
+		,Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
+		,Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
+		,Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE)
+		,Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE)
+		,Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
+		,Pattern.compile("src/", Pattern.CASE_INSENSITIVE)
+	};
+
+	private static Pattern[] sqlInjectionPatterns = new Pattern[] {
+			Pattern.compile("#", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(">", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("<", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("=", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("[$]", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("\"", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("[|]", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("'", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("%", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(";", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("--", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile("[.][.]/", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" select ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" update ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" delete ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" insert ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" where ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" from ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" create ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" drop ", Pattern.CASE_INSENSITIVE)
+			,Pattern.compile(" or ", Pattern.CASE_INSENSITIVE)
+		};
+	
+    public static String[] cleanXSS(String[] values) {
+    	if(values==null || values.length==0) {return values;}
+    	String[] cleans = Arrays.copyOf(values, values.length);
+    	for(int i=0;i<values.length;i++) {
+    		cleans[i]=cleanXSS(values[i]);
+    	}
+    	return cleans;
+    }
+	
+    public static String cleanXSS(String value) {
+    	if(value==null) {return null;}
+		String cleanValue=value.replaceAll("\0", "");
+		for(Pattern pattern : xssPatterns) {
+			if(pattern.matcher(cleanValue).find()) {
+				cleanValue=cleanValue.replaceAll("<","&lt;").replaceAll(">","&gt;");
+				logger.debug("pattern:{},:inValue:{}",pattern.pattern(),value);
+				break;
+			}
+		}		
+    	logger.debug("cleanXSS:{}",cleanValue);
+        return cleanValue;
+    };
+
+    public static String cleanSqlInjection(String inValue) {
+    	if(inValue==null) {return null;}
+		String cleanValue=inValue.replaceAll("\0", "");
+		
+		if(cleanValue.toUpperCase().startsWith("<P>") && cleanValue.toUpperCase().endsWith("</P>")) {
+			return cleanValue;
+		}
+		
+		for(Pattern pattern : sqlInjectionPatterns) {
+			if(pattern.matcher(cleanValue.replaceAll("[&]lt[;]","").replaceAll("[&]gt[;]","")).find()) {
+				cleanValue="<P>"+cleanValue+"</P>";
+				logger.debug("pattern: {},:cleanValue: {}",pattern.pattern(),cleanValue);
+				break;
+			}
+		}
+		
+    	logger.debug("cleanSqlInjection in: {},out: {}",inValue,cleanValue);
+    	return cleanValue;
+    }
+    
+    public static String getEacapeSqlInjectionBlockTag(String inValue) {
+        if(inValue==null) {return null;}
+        if(inValue.toUpperCase().startsWith("<P>") && inValue.toUpperCase().endsWith("</P>")) {
+            return inValue.substring(3,inValue.length()-4);            
+        }
+        return inValue;
+    }
+    public static Map<String,String> removeCRLF(Map<String,String> map) {
+    	if(map==null || map.isEmpty()) {return map;}
+    	Map<String,String> result = new HashMap<>();
+    	map.entrySet().forEach(e-> result.put(removeCRLF(e.getKey()), removeCRLF(e.getValue())));
+    	return result;
+   } 
+
+    public static String removeCRLF(String str) {
+    	return StringUtils.replaceEach(str,replaceEashCRLF1,replaceEashCRLF2);
+    } 
+
+    public static String normalizePath(String path) {
+        if(path == null){return null;}
+    	return URI.create(path).normalize().toString();
+    }
+
+    public static String normalizeURI(String uri) {
+        if(uri == null){return null;}
+    	return URI.create(uri).normalize().toString();
+    }
+
+    public static String normalizeFilename(String filename) {
+        if(filename == null){return null;}
+    	return FilenameUtils.normalize(filename);
+    } 
+
+}

+ 193 - 0
src/main/java/kr/co/iya/base/support/linker/HealthCheckLinker.java

@@ -0,0 +1,193 @@
+package kr.co.iya.base.support.linker;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.env.Environment;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import kr.co.iya.base.context.ProjectContext;
+import kr.co.iya.base.support.AbstractMeta;
+import kr.co.iya.base.support.util.SystemUtilPack;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+
+//@Slf4j
+public class HealthCheckLinker extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Hidden
+	@RestController
+	@ConditionalOnWebApplication
+	public static class HealthCheckController {
+
+		@Autowired
+		private Environment environment;
+		
+		@Autowired
+		private ProjectContext projectContext;
+		
+		@Autowired
+		private JdbcTemplate jdbcTemplate;
+
+		@Value("${utcb.healthcheck.systemPrefix:utcb}")
+		private String systemPrefix;
+
+		@Value("${spring.application.name}")
+		private String applicationName;
+
+		@Value("${spring.profiles.active:local}")
+		private String springProfilesActive;		
+				
+		@Value("${server.name:}")
+		private String serverName;	
+		
+		@Value("${server.port:}")
+		private String serverPort;
+
+		@Value("${server.servlet.context-path:}")
+		private String serverServletContextPath;
+
+		@Value("${project.meta.service:}")
+		private String projectMetaService;
+
+		@Value("${logging.file.name:}")
+		private String loggingFileName;
+			    
+		@PostConstruct
+		private void postConstruct() throws Exception {
+			SystemUtil systemUtil = SystemUtilPack.getSystemUtil(this.environment,this.serverName,this.serverPort,this.applicationName);
+			
+			StringBuilder builder = new StringBuilder();
+			builder.append("http://").append(systemUtil.getServerIp()).append(":").append(systemUtil.getServerPort()).append("/");
+			
+			String webContextPath="";
+			if(!StringUtils.isEmpty(this.projectMetaService)) {webContextPath=this.projectMetaService;}
+			if(!StringUtils.isEmpty(this.serverServletContextPath)) {webContextPath=this.serverServletContextPath;}
+			if(!StringUtils.isEmpty(webContextPath)) {
+				if(webContextPath.startsWith("/")) {webContextPath=webContextPath.substring(1);}
+				builder.append(webContextPath).append("/");
+			}
+			builder.append("healthcheck");
+
+			String metaFile=FilenameUtils.normalize(this.loggingFileName+".meta");
+			try(FileWriter fw = new FileWriter(new File(metaFile))){
+				fw.write(builder.toString());
+				fw.flush();
+			}
+		}
+
+		@GetMapping(path = "/healthcheck")
+		public Map<String, Object> healthcheck1(HttpServletRequest request) throws Exception {
+			this.removeSession(request);
+			return this.getHealthcheck();
+		}
+
+		@GetMapping(path = "/{applicationCode}/healthcheck")
+		public Map<String, Object> healthcheck2(@PathVariable final String applicationCode, HttpServletRequest request) throws Exception {
+			if (!this.applicationName.equals(systemPrefix + "-" + applicationCode)) {
+				throw new RuntimeException("applicationCode is wrong.");
+			}
+			//this.removeSession(request);
+			return this.getHealthcheck();
+		}
+
+		@GetMapping(path = "/{applicationCode}/availability")
+		public Map<String, Object> availability(@PathVariable final String applicationCode, HttpServletRequest request) throws Exception {
+			if (!this.applicationName.equals(systemPrefix + "-" + applicationCode)) {
+				throw new RuntimeException("applicationCode is wrong.");
+			}
+			
+			HttpSession session = MetaUtil.getSessionUtil().getHttpSession();
+			Map<String,Object> sessionInfo = new HashMap<>();
+			sessionInfo.put("sessionId", session.getId());
+			sessionInfo.put("time", session.getAttribute("time"));		
+			sessionInfo.put("interval", session.getMaxInactiveInterval());	
+			
+			Map<String,Object> healthCheck = this.getHealthcheck();
+			healthCheck.put("sessionInfo", sessionInfo);
+			healthCheck.put("systemUsage", MetaUtil.getSystemUtil().getSystemUsage());
+			
+			return healthCheck;
+		}
+
+		private void removeSession(HttpServletRequest request) {
+			// 주기적인 healthcheck요청시 session이 생성되지만, session에 저장하는 AttributeNames는 없다.
+			// 주기적인 healthcheck요청시 생성되는 session을 삭제한다.
+			if (request.getHeader("referer") == null && request.getSession().getAttributeNames().hasMoreElements() == false) {
+				request.getSession().invalidate();
+			}
+		}
+
+		private Map<String,Object> getHealthcheck(){
+			SystemUtil sysUtil = MetaUtil.getSystemUtil();
+
+	        Map<String,String> deployInfo = new HashMap<>();
+	        deployInfo.put("serverName",sysUtil.getServerName());
+	        deployInfo.put("serverIp",sysUtil.getServerIp());
+	        deployInfo.put("serverPort",sysUtil.getServerPort());
+	        deployInfo.put("serverInstanceCode",sysUtil.getServerInstanceCode());
+
+	        deployInfo.put("projectName",projectContext.getName());
+	        deployInfo.put("projectDescription",projectContext.getDescription());
+	        deployInfo.put("projectProfile",this.springProfilesActive);	
+
+	        deployInfo.put("projectGroupId",projectContext.getGroupId());
+	        deployInfo.put("projectArtifact",projectContext.getArtifact());
+	        deployInfo.put("projectVersion",projectContext.getVersion());
+	        
+	        deployInfo.put("buildFinalName",projectContext.getBuild().getFinalName());
+	        deployInfo.put("buildBranch",projectContext.getBuild().getBranch());
+	        deployInfo.put("buildRevision",projectContext.getBuild().getRevision());
+	        deployInfo.put("buildTimestamp",projectContext.getBuild().getTimestamp());
+
+	        for(String key:deployInfo.keySet()){
+	        	if(deployInfo.get(key)!=null && deployInfo.get(key).startsWith("@") && deployInfo.get(key).endsWith("@")) {
+	        		deployInfo.put(key,"");
+	        	}
+	        }
+	        
+	        String healthcheck="ok";
+	        if(this.jdbcTemplate!=null) {
+	        	healthcheck = this.jdbcTemplate.query(
+        			"select now()"
+        			,new ResultSetExtractor<String>() {
+        				@Override
+        				public String extractData(ResultSet rs) throws SQLException, DataAccessException {
+        					rs.next();
+        					return rs.getString(1);
+        				}
+        			}
+        		);	        	
+	        }
+
+	        Map<String,Object> result = new HashMap<>();        
+	        result.put("healthcheck", healthcheck);
+	        result.put("deployInfo", deployInfo);
+
+	        return result;
+		}
+	};
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 51 - 0
src/main/java/kr/co/iya/base/support/linker/SessionCheckLinker.java

@@ -0,0 +1,51 @@
+package kr.co.iya.base.support.linker;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import kr.co.iya.base.support.AbstractMeta;
+
+public class SessionCheckLinker extends AbstractMeta {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	@Hidden
+	@RestController
+	@ConditionalOnWebApplication
+	public static class SessionCheckController {
+
+		@Value("${utcb.healthcheck.systemPrefix:utcb}")
+		private String systemPrefix;
+		
+		@Value("${spring.application.name}")
+		private String applicationName;
+		
+		@GetMapping(path="/{applicationCode}/sessioncheck")
+		public Map<String,Object> SessionCheck(@PathVariable final String applicationCode) throws Exception {	
+			if (!this.applicationName.equals(systemPrefix + "-" + applicationCode)) {
+				throw new RuntimeException("applicationCode is wrong.");
+			}
+			
+			HttpSession session = MetaUtil.getSessionUtil().getHttpSession();
+			
+	        Map<String,Object> map = new HashMap<>();
+	        map.put("sessionId", session.getId());
+	        map.put("time", session.getAttribute("time"));
+	        map.put("interval", session.getMaxInactiveInterval());
+	        
+	        return map;
+		}
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+}

+ 84 - 0
src/main/java/kr/co/iya/base/support/util/Base64UtilPack.java

@@ -0,0 +1,84 @@
+package kr.co.iya.base.support.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+public final class Base64UtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface Base64Util {
+		public byte[] getEncodeBase64(String str);
+		public byte[] getEncodeBase64(byte[] bytes);
+		
+		public byte[] getDecodeBase64(String str);
+		public byte[] getDecodeBase64(byte[] bytes);
+		
+		public String getEncodeBase64String(String str);
+		public String getEncodeBase64String(byte[] bytes);
+
+		public String getDecodeBase64String(String str);
+		public String getDecodeBase64String(byte[] bytes);
+		
+		public boolean isBase64(String str);
+		public boolean isBase64(byte[] bytes);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static Base64Util getBase64Util() {
+		
+		return new Base64Util() {
+
+			@Override
+			public byte[] getEncodeBase64(String str) {
+				return (str==null?null:this.getEncodeBase64(str.getBytes()));
+			}
+			
+			@Override
+			public byte[] getEncodeBase64(byte[] bytes) {
+				return (bytes==null?null:Base64.encodeBase64(bytes));
+			}
+
+			@Override
+			public byte[] getDecodeBase64(String str) {
+				return (str==null?null:this.getDecodeBase64(str.getBytes()));
+			}
+			
+			@Override
+			public byte[] getDecodeBase64(byte[] bytes) {
+				return (bytes==null?null:Base64.decodeBase64(bytes));
+			}
+			
+			@Override
+			public String getEncodeBase64String(String str) {
+				return (str==null?null:new String(this.getEncodeBase64(str)));
+			}
+			
+			@Override
+			public String getEncodeBase64String(byte[] bytes) {
+				return (bytes==null?null:new String(this.getEncodeBase64(bytes)));
+			}
+
+			@Override
+			public String getDecodeBase64String(String str) {
+				return (str==null?null:new String(this.getDecodeBase64(str)));
+			}
+			
+			@Override
+			public String getDecodeBase64String(byte[] bytes) {
+				return (bytes==null?null:new String(this.getDecodeBase64(bytes)));
+			}
+			
+			public boolean isBase64(String str) {
+				return Base64.isBase64(str);
+			}
+			
+			public boolean isBase64(byte[] bytes) {
+				return Base64.isBase64(bytes);				
+			}
+			
+		};
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 413 - 0
src/main/java/kr/co/iya/base/support/util/BashProcessUtilPack.java

@@ -0,0 +1,413 @@
+package kr.co.iya.base.support.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import kr.co.iya.base.exception.SystemException;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class BashProcessUtilPack{
+		
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private static final String TRACE_KEY_NAME = "processExecuteId";
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface BashProcessUtil{
+
+		@FunctionalInterface
+		public interface BashProcessMetaFunction {public abstract BashProcessMeta getMeta();}
+
+		public String command(String command);
+
+		public BashProcessResponse execute(String script, BashProcessMetaFunction metaFunc);
+		public BashProcessResponse execute(String script, List<String> parameters, BashProcessMetaFunction metaFunc);
+		public BashProcessResponse execute(BashProcessRequest bashRequest, BashProcessMetaFunction metaFunc);
+
+		public int stop(long processExecuteId);
+		public int stop(String pid);
+		
+		public BashProcessStatus status(long processExecuteId);
+		public List<BashProcessStatus> status(String... greps);
+		public List<BashProcessStatus> status(Set<String> grepSet);
+		public List<BashProcessStatus> status(String grepInfo);
+	}
+
+	@Data
+	public static class BashProcessMeta implements Serializable{
+		private static final long serialVersionUID = 1L;
+		private String directory;	
+		private String message;
+	}
+	
+	@Data
+	public static class BashProcessRequest implements Serializable{
+		private static final long serialVersionUID = 1L;
+		private String script;
+		private Map<String,String> environments;
+		private List<String> parameters;
+	}
+
+	@Data
+	public static class BashProcessResponse implements Serializable{
+		private static final long serialVersionUID = 1L;
+		private long processExecuteId;
+		private Date processExecuteDate;
+		private String console;
+		private String error;
+		private String exitValue;
+	}
+
+	@Data
+	public static class BashProcessStatus implements Serializable{
+		private static final long serialVersionUID = 1L;
+    	private String uid;
+    	private String pid;
+    	private String ppid;
+    	private String cpu;
+    	private String stime;
+    	private String tty;
+    	private String time;
+    	private String cmd;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private static String getEnvShell(String osName) {
+		log.debug(">> osName: {}",osName);
+		if (!osName.toLowerCase().contains("linux")) {return null;}
+		
+		StringBuilder console=new StringBuilder();		
+		try {
+			ProcessBuilder processBuilder=new ProcessBuilder("sh","-c","env | grep SHELL");
+			Process process=processBuilder.start();
+            
+			String line;
+			BufferedReader consoleReader=new BufferedReader(new InputStreamReader(process.getInputStream()));            
+            while((line=consoleReader.readLine()) != null) {
+            	console.append(line);
+            }
+            
+		} catch (IOException e) {
+			throw new SystemException(e.getMessage());
+		}
+		
+		log.debug(">> envShell :{}",console);
+		return console.toString();
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public static BashProcessUtil getBashProcessUtil() {
+		
+		String osName=System.getProperty("os.name"); 
+		String envShell=getEnvShell(osName); 
+		
+		return new BashProcessUtil() {
+						
+			private void checkSystem() {
+		    	if (!osName.toLowerCase().contains("linux")) {throw new SystemException("해당 기능은 linux 시스템에서만 지원 합니다.");}
+		    	if (!envShell.toLowerCase().contains("bash")) {throw new SystemException("해당 기능은 bash 시스템에서만 지원 합니다.");}
+			}
+			
+			@Override
+			public String command(String command) {
+				if (StringUtils.isEmpty(command)) {return null;}
+				BashProcessRequest bashRequest=new BashProcessRequest();
+				bashRequest.setScript(command);
+				
+				BashProcessResponse response = this._execute(bashRequest, "command", null, false);
+		    	if (response == null ) {return null;}
+		    	
+		    	if (!StringUtils.isEmpty(response.getError())) {
+		    		throw new SystemException(response.getError());
+		    	}
+		    	
+		    	return response.getConsole();				
+			}
+
+			@Override
+			public BashProcessResponse execute(String script, BashProcessMetaFunction metaFunc) {
+				BashProcessRequest bashRequest=new BashProcessRequest();
+				bashRequest.setScript(script);
+				return this.execute(bashRequest,metaFunc);
+			}
+
+			@Override
+			public BashProcessResponse execute(String script, List<String> parameters, BashProcessMetaFunction metaFunc) {
+				BashProcessRequest bashRequest=new BashProcessRequest();
+				bashRequest.setScript(script);
+				bashRequest.setParameters(parameters);
+				return this.execute(bashRequest,metaFunc);
+			}
+
+			@Override
+			public BashProcessResponse execute(BashProcessRequest bashRequest, BashProcessMetaFunction metaFunc) {
+				return this._execute(bashRequest,"script",metaFunc,true);
+			}
+			
+			@Override
+			public int stop(long processExecuteId) {
+				BashProcessStatus status=this.status(processExecuteId);
+				if (status == null) {return -1;}
+				return this.stop(status.getPid());
+			}
+			
+			@Override
+			public int stop(String pid) {
+				if (StringUtils.isEmpty(pid)) {return -1;}
+				
+				BashProcessRequest bashRequest=new BashProcessRequest();
+				bashRequest.setScript("kill -9 "+pid);
+				
+				BashProcessResponse response = this._execute(bashRequest,"command",null,true);
+				return Integer.parseInt(response.getExitValue());
+			}
+
+			@Override
+			public BashProcessStatus status(long processExecuteId) {
+				if (processExecuteId < 0) {return null;}
+				
+				Set<String> grepSet = new HashSet<>();
+				grepSet.add(TRACE_KEY_NAME+":"+processExecuteId);
+				
+				List<BashProcessStatus> list = this.status(grepSet);
+				return (list != null && list.size()==1)?list.get(0):null;
+			}
+
+			@Override
+			public List<BashProcessStatus> status(String... greps) {
+				return this.status(new HashSet<String>(Arrays.asList(greps)));
+			}
+			
+			@Override
+			public List<BashProcessStatus> status(Set<String> grepSet) {
+				if (grepSet == null || grepSet.isEmpty()) {return null;}
+	
+				String grepInfo = "ps -ef | grep -v grep";
+				for(String item : grepSet) {
+					grepInfo += " | grep "+item.trim();
+				}
+				return this.status(grepInfo);
+			}
+			
+			@Override
+			public List<BashProcessStatus> status(String grepInfo) {
+				this.checkSystem();
+				
+				if (StringUtils.isEmpty(grepInfo)) {return null;}
+				
+				BashProcessRequest request = new BashProcessRequest();
+				request.setScript(grepInfo);
+				
+				BashProcessResponse response = this._execute(request,"command",null,true);
+				
+		    	if (!StringUtils.isEmpty(response.getError())) {
+		    		throw new SystemException(response.getError());
+		    	}
+		    	
+		    	if (StringUtils.isEmpty(response.getConsole())) {
+		    		return null;
+		    	}
+
+		    	String uid = response.getConsole().split(" ")[0];
+
+		    	List<BashProcessStatus> processStatusList = new ArrayList<>();		    	
+				Arrays.asList(response.getConsole().split(uid)).stream()
+						.filter(StringUtils::isNotEmpty)
+						.collect(Collectors.toList())
+						.forEach(data -> processStatusList.add(this.getBashProcessStatus(data,uid)));
+
+		    	return processStatusList;
+			}
+
+		    private BashProcessStatus getBashProcessStatus(String data, String rowSpliter) {
+
+				String[] segments = null;
+				if (data.indexOf(" ") != -1) {
+					data=data.replace(" ","@");
+					while(data.indexOf("@@") > -1) {data = data.replace("@@","@");}		
+					if (data.indexOf("@") != -1) {segments = data.split("@");}
+				}
+
+				if (segments == null) {return null;}
+
+				BashProcessStatus processStatus = new BashProcessStatus();
+				processStatus.setUid(rowSpliter);
+				if (segments.length > 0) {processStatus.setPid(segments[1]);}
+				if (segments.length > 1) {processStatus.setPpid(segments[2]);}
+				if (segments.length > 2) {processStatus.setCpu(segments[3]);}
+				if (segments.length > 3) {processStatus.setStime(segments[4]);}
+				if (segments.length > 4) {processStatus.setTty(segments[5]);}
+				if (segments.length > 5) {processStatus.setTime(segments[6]);}
+				if (segments.length > 7) {
+					String cmd="";
+					for(int i=7 ; i < segments.length ; i++) {
+						cmd += segments[i] + " ";
+					}
+					processStatus.setCmd(cmd.trim());
+				}
+	
+		    	return processStatus;
+		    }
+		    
+			private BashProcessResponse _execute(BashProcessRequest bashRequest, String mode, BashProcessMetaFunction metaFunc, boolean isLog) {
+				this.checkSystem();
+				
+				if(isLog) {
+					log.debug("");
+					log.debug(">> start: bash process execute");
+				}
+				long startTime = System.nanoTime();
+				long processExecuteId = startTime;
+
+				if (bashRequest == null) {return null;}
+				if (StringUtils.isEmpty(bashRequest.getScript())) {return null;}
+				
+				String bashScript=bashRequest.getScript();
+				if (mode.equals("script")) {
+			    	if (bashRequest.getParameters() != null && !bashRequest.getParameters().isEmpty()) {
+			    		for(String param : bashRequest.getParameters()) {
+			    			bashScript += " \""+param+"\"";
+			    		}
+			    	}
+			    	bashScript+= " \"--"+TRACE_KEY_NAME+":" + processExecuteId+"\"";
+		    		bashScript = bashScript.trim();
+					if(isLog) {log.debug(">> bashScript: {}",bashScript);}
+				} else {
+					if(isLog) {log.debug(">> bashCommand: {}",bashScript);}	
+				}
+		    	
+		    	List<String> command=new ArrayList<>();
+		    	command.add("bash");
+		    	command.add("-c");
+		    	command.add(bashScript);
+		    	
+		    	ProcessBuilder processBuilder = new ProcessBuilder(command);    	
+		    	Map<String, String> environment = processBuilder.environment();
+		    	environment.put("TERM", "xterm");
+		    	environment.put("COLOR_ENABLE", "false");
+		    	if (mode.equals("script")) {
+			    	if (bashRequest.getEnvironments() != null) {
+			    		bashRequest.getEnvironments().forEach((k,v) -> environment.put(k,v));
+			    	}
+		    	}
+		    	int exitValue = -1;
+		    	
+		        Date processExecuteDate = null;
+		        Process process = null;
+	            StringBuilder console = new StringBuilder();
+		        StringBuilder error = new StringBuilder();
+		       
+	        	boolean isEnable = true;
+	        	if(isEnable && metaFunc==null) {isEnable=false;}
+	        	if(isEnable && metaFunc.getMeta()==null) {isEnable=false;}
+	        	if(isEnable) {
+	        		BashProcessMeta meta = metaFunc.getMeta();
+	        		if(isLog) {log.debug(">> meta:{}",meta);}
+	        		if(isEnable && StringUtils.isEmpty(meta.getDirectory())) {isEnable=false;}
+	        		if(isEnable && StringUtils.isEmpty(meta.getMessage())) {isEnable=false;}	        		
+		        	if(isEnable){
+		        		String metaFile = meta.getDirectory()+"/"+ processExecuteId;			        	
+		        		if(isLog) {log.debug(">> metaFile:{}",metaFile);}
+		        		File file = new File(FilenameUtils.normalize(meta.getDirectory()));
+		        		if(!file.exists()) {file.mkdirs();}
+		        		if(file.exists() && !file.isDirectory()) {
+		        			throw new SystemException(meta.getDirectory() + " is not directory.");
+		        		}
+
+		        		try (FileWriter fileWriter = new FileWriter(FilenameUtils.normalize(metaFile))){							
+			        		fileWriter.write(meta.getMessage());
+						} catch (IOException e) {
+							throw new SystemException(e.getMessage());
+						}
+		        	}	        		
+	        	}
+		       		        
+		        try {
+
+		        	// start
+		        	processExecuteDate = new Date();
+		            process = processBuilder.start();
+	
+			        String line;
+
+		            // read console
+		            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(process.getInputStream()));            
+		            while ((line = consoleReader.readLine()) != null) {
+		            	console.append(line).append("\n");
+		            	if (mode.equals("script")) {log.debug(">> console:{}",line);}
+		            }
+		            consoleReader.close();
+	
+			        // read error
+		            BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+		            while ((line = errorReader.readLine()) != null) {
+		            	error.append(line).append("\n");
+		            	if (mode.equals("script")) {log.debug(">> error:{}",line);}
+		            }
+		            errorReader.close();
+		            
+		            exitValue = process.waitFor();
+		            if (mode.equals("script")) {log.debug(">> exitValue:{}",exitValue);}
+		            
+		            if (exitValue != 0 && error.length() > 0) {
+		            	error.append("Bash Script exitValue(").append(exitValue).append(") is not success.");
+		            }
+		            
+				} catch (Exception e) {
+					throw new SystemException(e.getMessage());
+					
+				} finally {
+					if (process != null) {
+						Exception exception = null;
+			            try {if (process.getErrorStream() != null) {process.getErrorStream().close();}} catch (IOException e) {exception = e;}
+			            try {if (process.getInputStream() != null) {process.getInputStream().close();}} catch (IOException e) {exception = e;}
+			            try {if (process.getOutputStream() != null) {process.getOutputStream().close();}} catch (IOException e) {exception = e;}
+			            
+			            process.destroy();
+			            if (process.isAlive()) {process.destroyForcibly();}
+			            if (exception != null) {throw new SystemException(exception.getMessage());}
+					}
+				}
+		        
+		        BashProcessResponse bashResponse = new BashProcessResponse();
+		        bashResponse.setProcessExecuteId(processExecuteId);
+		        bashResponse.setProcessExecuteDate(processExecuteDate);
+		        bashResponse.setConsole(console.toString());
+		        bashResponse.setError(error.toString());
+		        bashResponse.setExitValue(String.valueOf(exitValue));
+		        
+		        long executeTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-startTime);
+		        
+		        if(isLog) {log.debug(">> end: bash process execute,{}ms",executeTime);}
+				
+		    	return bashResponse;		        
+			}		    
+		    
+		};
+
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 176 - 0
src/main/java/kr/co/iya/base/support/util/CompressUtilPack.java

@@ -0,0 +1,176 @@
+package kr.co.iya.base.support.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import kr.co.iya.base.exception.SystemException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class CompressUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface CompressUtil {
+		
+		public boolean isZip(String targetPath);
+								
+		public File zip(String targetPath);
+		public File zip(String targetPath, Boolean isRelativePath);
+		public File zip(String targetPath, String zipFilePath, Boolean isRelativePath);
+		
+		public List<String> unZip(String zipFilePath);
+		public List<String> unZip(String zipFilePath, String destPath);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static CompressUtil getCompressUtil() {
+		
+		return new CompressUtil() {
+
+			@Override
+			public boolean isZip(String targetPath) {	
+				File target = new File(targetPath);
+				if(!target.exists() || target.isDirectory()) {return false;}
+
+				boolean result = false;
+				try(ZipFile zipfile = new ZipFile(targetPath)) {
+					result = true;
+				}catch(IOException e) {
+					result = false;
+				}
+				return result;
+			}
+			
+			@Override
+			public File zip(String targetPath) {
+				return this.zip(targetPath, null, false);
+			}
+
+			@Override
+			public File zip(String targetPath, Boolean isRelativePath) {
+				return this.zip(targetPath, null, isRelativePath);
+			}
+			
+			@Override
+			public File zip(String targetPath, String zipFilePath, Boolean isRelativePath) {
+				if(this.isZip(targetPath)) {throw new SystemException(targetPath+" is zip.(already compressed file)");}
+				
+				//target
+				File target = new File(FilenameUtils.normalize(targetPath.trim()));				
+				if(!target.exists()) {throw new SystemException(targetPath+" is not exist.");}
+				if(target.getParent()==null) {throw new SystemException(targetPath+" is not allowed.");}
+								
+				if(StringUtils.isBlank(zipFilePath)) {
+					zipFilePath = target.toPath().toString()+".zip";
+				}
+				
+				//zipFile
+				File zipFile = new File(FilenameUtils.normalize(zipFilePath.trim()));
+				if(!zipFile.exists()) {zipFile.getParentFile().mkdirs();}
+				
+		    	try(ZipArchiveOutputStream zip = new ZipArchiveOutputStream(new FileOutputStream(zipFilePath))) {
+
+		    		log.debug(">> zip: {} -> {}",target.getCanonicalPath(),zipFile.getCanonicalPath());
+		    		
+		    		Files.walk(target.toPath()).forEach(path -> {
+		    			File entryFile = path.toFile();
+		    			
+		    			if(!entryFile.isDirectory()) {
+		    				String entryName = entryFile.toString();    				
+		    				if(isRelativePath) {
+		    					entryName = entryName.substring(target.getParent().length());
+		    				}
+		    				log.debug(">> zip.entryName:{}",entryName);
+		    				try(FileInputStream fis = new FileInputStream(entryFile)) {
+		    					zip.putArchiveEntry(new ZipArchiveEntry(entryFile, entryName));
+		    					IOUtils.copy(fis, zip);
+		    					zip.closeArchiveEntry();
+		    				}catch(IOException e) {
+		    					throw new SystemException(e.getMessage());
+		    				}
+		    			}
+		    		});
+		    		zip.finish();
+		    	}catch(Exception e) {
+		    		throw new SystemException(e.getMessage());
+		    	}
+
+		    	return zipFile;
+			
+			}
+			
+			@Override
+			public List<String> unZip(String zipFilePath) {
+				return this.unZip(zipFilePath, null);
+			}
+			
+			@Override
+			public List<String> unZip(String zipFilePath, String unzipPath) {
+				if(StringUtils.isBlank(zipFilePath)) {throw new SystemException(zipFilePath+" is blank.");}				
+				
+				//zipFile
+				File zipFile = new File(FilenameUtils.normalize(zipFilePath.trim()));
+				if(!zipFile.exists()) {throw new SystemException(zipFilePath+" is not exist.");}				
+				
+				//unzipFile
+				if(StringUtils.isBlank(unzipPath)) {					
+					unzipPath = zipFile.getParent();
+				}
+				File unzipFile = new File(FilenameUtils.normalize(unzipPath.trim()));
+				if(unzipFile.exists() && !unzipFile.isDirectory()) {throw new SystemException(unzipPath+" is not directory.");}
+				unzipFile.mkdirs();
+
+				List<String> list = new ArrayList<>();
+				
+	            try(ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile)))) {
+	                	            	
+	            	log.debug(">> unzip: {} -> {}",zipFile.getCanonicalPath(),unzipFile.getCanonicalPath());
+	            	
+	            	ZipEntry zipEntry = null;
+	                while((zipEntry = zipInputStream.getNextEntry()) != null) {
+	                    int length = 0;
+	                    
+	                    String entryFilePath = FilenameUtils.normalize(unzipPath + File.separator + zipEntry.getName());	                    
+	                    log.debug(">> unzip.entryFilePath:{}",entryFilePath);
+	                    new File(new File(entryFilePath).getParent()).mkdirs();	                    
+	                    
+	                    try(BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(entryFilePath))) {
+	                        while((length = zipInputStream.read()) != -1) {
+	                        	out.write(length);
+	                        }
+	                        zipInputStream.closeEntry();
+	                    }
+	                    list.add(entryFilePath);
+	                }
+	                
+	            } catch (Exception e) {
+	            	throw new SystemException(e.getMessage());
+				}
+
+	            return list;
+			}
+			
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 182 - 0
src/main/java/kr/co/iya/base/support/util/CryptoUtilPack.java

@@ -0,0 +1,182 @@
+package kr.co.iya.base.support.util;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+
+public final class CryptoUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface CryptoUtil {
+		public static final String DEFAULT_AES256_KEY="utcb12345678";		
+		public AES256 getAES256();
+		public SHA256 getSHA256();
+		public SHA512 getSHA512();
+
+	}
+	
+	public interface AES256 {
+		public String encryption(String data);
+		public String encryption(String data,String key);
+		public String decryption(String data);
+		public String decryption(String data,String key);
+	}
+	
+	public interface SHA256 {
+		public String encryption(String data);
+		public boolean isMatch(String encryption,String data);
+	}
+	
+	public interface SHA512 {
+	    public String encryption(String data);
+		public boolean isMatch(String encryption,String data);
+		
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static CryptoUtil getCryptoUtil() {
+		
+		return new CryptoUtil() {
+			@Override
+			public SHA256 getSHA256() {
+				return new SHA256() {
+					@Override
+					public String encryption(String data) {
+						String result=null;
+						try {
+							MessageDigest digest=MessageDigest.getInstance("SHA-256");
+							digest.reset();
+							digest.update(data.getBytes("utf8"));
+							result=String.format("%064x",new BigInteger(1,digest.digest()));
+						}catch(Exception e) {
+							throw new RuntimeException(e.getMessage());
+						}
+						return result;
+					}
+					
+					@Override
+					public boolean isMatch(String encryption,String data) {
+						if(this.encryption(data).equals(encryption)) {return true;}
+						return false;
+					}		
+				};
+			}
+
+			@Override
+			public SHA512 getSHA512() {
+				return new SHA512() {
+					@Override
+					public String encryption(String data) {
+				        String result=null;
+				        try {
+				            MessageDigest digest=MessageDigest.getInstance("SHA-512");
+				            digest.reset();
+				            digest.update(data.getBytes("utf8"));
+				            result=String.format("%0128x",new BigInteger(1,digest.digest()));
+				        }catch(Exception e) {
+				            throw new RuntimeException(e.getMessage());
+				        }
+				        return result;
+				    }
+					
+					@Override
+					public boolean isMatch(String encryption,String data) {
+						if(this.encryption(data).equals(encryption)) {return true;}
+						return false;
+					}		
+				};
+			}
+			
+			@Override
+			public AES256 getAES256(){
+				return new AES256() {
+					@Override
+					public String encryption(String data) {
+						return this.encryption(data, DEFAULT_AES256_KEY);
+					}
+	
+					@Override
+					public String encryption(String data,String key) {
+						String encStr=null;
+						try {	
+							Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
+							cipher.init(Cipher.ENCRYPT_MODE,this.getSecretKeySpec(key),this.getIvParameterSpec(key));
+							byte[] encBytes=cipher.doFinal(data.getBytes("UTF-8"));
+							encStr=Base64.encodeBase64String(encBytes);
+						}catch(Exception e) {	
+							throw new RuntimeException(e.getMessage());
+						}
+						return encStr;
+					}	
+					
+					@Override
+					public String decryption(String data) {
+						return this.decryption(data, DEFAULT_AES256_KEY);	
+					}
+					
+					@Override
+					public String decryption(String data,String key) {
+						String decStr=null;		
+						try {	
+							Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
+							cipher.init(Cipher.DECRYPT_MODE,this.getSecretKeySpec(key),this.getIvParameterSpec(key));
+							byte[] decBytes=Base64.decodeBase64(data.getBytes("UTF-8"));
+							decStr=new String(cipher.doFinal(decBytes),"UTF-8");
+						}catch(Exception e) {
+							throw new RuntimeException(e.getMessage());
+						}
+						return decStr;
+					}	
+					
+					private SecretKeySpec getSecretKeySpec(String key) {
+						SecretKeySpec secretKeySpec=null;
+						try {
+							SecureRandom secureRandom=SecureRandom.getInstance("SHA1PRNG");
+							secureRandom.setSeed(key.getBytes("UTF-8"));
+							
+							KeyGenerator keyGenerator=KeyGenerator.getInstance("AES");
+							keyGenerator.init(256,secureRandom);
+							
+							SecretKey secureKey=keyGenerator.generateKey();
+							secretKeySpec=new SecretKeySpec(secureKey.getEncoded(),"AES");
+						}catch(Exception e) {
+							throw new RuntimeException(e.getMessage());
+						}
+						return secretKeySpec;
+					}
+					
+					private IvParameterSpec getIvParameterSpec(String key) {
+						IvParameterSpec ivParameterSpec=null;
+						try {
+							String ivTemplateStr="beethoven.egmont.overture"; //반드시 16자 이상
+							byte[] ivTemplateBytes=ivTemplateStr.getBytes("UTF-8");
+					
+							byte[] keyBytes=key.getBytes("UTF-8");
+							int max=(keyBytes.length<17?keyBytes.length:16);
+							for(int i=0;i<max;i++) {ivTemplateBytes[i]=keyBytes[i];}
+					
+							byte[] ivParamBytes=Arrays.copyOfRange(ivTemplateBytes,0,16); 
+							ivParameterSpec=new IvParameterSpec(ivParamBytes);
+						}catch(Exception e) {
+							throw new RuntimeException(e.getMessage());
+						}
+						return ivParameterSpec;
+					}
+				};
+			}
+		};
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 539 - 0
src/main/java/kr/co/iya/base/support/util/FileUtilPack.java

@@ -0,0 +1,539 @@
+package kr.co.iya.base.support.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.disk.DiskFileItem;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import kr.co.iya.base.exception.SystemException;
+import kr.co.iya.base.support.aid.AwareAidPack.ApplicationContextAid;
+import kr.co.iya.base.support.etc.Sweeper;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class FileUtilPack{
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface FileUtil {
+
+		@FunctionalInterface
+		public interface FetchData {public abstract byte[] getData(long fetchNo) throws Exception;}
+		
+		@FunctionalInterface
+		public interface FetchFile {public abstract File getFile() throws Exception;}
+
+		@FunctionalInterface
+		public interface FetchInputStream {public abstract InputStream getInputStream() throws Exception;}
+
+	    public static final int FILE_READ_BUFFER_SIZE = 1024*512;  //512KB
+	    public static final int FILE_FLUSH_UNIT_SIZE = 1024*1024*1;  //1MB
+
+	    public MultipartConfigElement getMultipartConfigElement();
+
+	    public String getUniqueName(String directory, String prefix, String fileName);
+	    public String normalizePath(String path); 
+	    public String extractFileName(String path);
+		
+	    public void delete(List<String> list) throws Exception;
+		public boolean delete(String storePath) throws Exception;
+		
+	    public MultipartFile getMultipartFile(String filePath) throws Exception;
+	    public MultipartFile getMultipartFile(File file) throws Exception;	    
+	    public List<MultipartFile> getMultipartFile(String... filePaths) throws Exception;
+	    public List<MultipartFile> getMultipartFile(List<String> filePaths) throws Exception;	    
+	    
+		public StoreInfo store(MultipartFile file, String storeDirectory) throws Exception;
+	    public StoreInfo store(MultipartFile file, String storeDirectory, String[] acceptedFileTypes) throws Exception;	    
+	    public StoreInfo store(MultipartFile file, String storeDirectory, String[] acceptedFileTypes, long maxFileSize) throws Exception;
+	    public List<StoreInfo> store(List<MultipartFile> files, String storeDirectory) throws Exception;	    
+	    public List<StoreInfo> store(List<MultipartFile> files, String storeDirectory, String[] acceptedFileTypes) throws Exception;
+	    public List<StoreInfo> store(List<MultipartFile> files, String storeDirectory, String[] acceptedFileTypes, long maxFileSize) throws Exception;
+	    public <T extends StoreInfo> T store(MultipartFile file, String storeDirectory, String[] acceptedFileTypes, long maxFileSize, Class<T> type) throws Exception;    	
+	    public <T extends StoreInfo> List<T> store(List<MultipartFile> files, String storeDirectory, String[] acceptedFileTypes, long maxFileSize, Class<T> type) throws Exception;	    
+	    public <T extends StoreInfo> List<T> store(MultipartHttpServletRequest request, String storeDirectory, String[] acceptedFileTypes, long maxFileSize, Class<T> type) throws Exception;
+	    
+		public void pushout(String storePath, HttpServletRequest request, HttpServletResponse response) throws Exception;		
+	    public void pushout(String storePath, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;		
+		public void pushout(File file, HttpServletRequest request, HttpServletResponse response) throws Exception;
+		public void pushout(File file, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;	    
+		public void pushout(byte[] bytes, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;
+	    public void pushout(Clob clob, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;
+	    public void pushout(Blob blob, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;
+	    public void pushout(FetchData fetch, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;
+	    public void pushout(FetchFile fetch, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;
+		public void pushout(FetchInputStream fetch, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception;
+	}
+
+    @Data
+    public static class StoreInfo implements Serializable {
+		private static final long serialVersionUID = 1L;
+		private String attachName;
+    	private long attachSize;    	
+    	private String storeDirectory;
+    	private String storeName;
+    	private String storePath;
+    	private String storeDate;
+    	private Map<String,String> systemInfo;
+    }
+    
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static FileUtil getFileUtil(SystemUtil systemUtil) {
+		
+		ApplicationContext context = ApplicationContextAid.getApplicationContext();
+		
+		return new FileUtil() {
+			
+			private MultipartConfigElement multipartConfigElement = context.getBean(MultipartConfigElement.class);
+
+			@Override
+		    public MultipartConfigElement getMultipartConfigElement() {
+				return this.multipartConfigElement;
+		    }
+
+			@Override  
+		    public String getUniqueName(String directory, String prefix, String fileName) {
+				if(prefix==null) {prefix="";}
+				
+				String storeName = prefix.trim().equals("")?fileName:prefix+"_"+fileName;
+				String storeDirectory = this.normalizePath(directory);
+
+				//To prevent path manipulation, FilenameUtils.normalizes a path to a standard format
+				if((new File(storeDirectory, FilenameUtils.normalize(storeName))).exists()) {				
+					int index = 1;
+					while(true) {
+						int extComma = storeName.lastIndexOf(".");    
+						String modifiedFileName = storeName.substring(0, extComma) + "(" + index + ")" + storeName.substring(extComma);
+						//To prevent path manipulation, FilenameUtils.normalizes a path to a standard format
+						if(!(new File(storeDirectory, FilenameUtils.normalize(modifiedFileName))).exists()) {
+							storeName = modifiedFileName;
+							break;
+						} else {
+							index = index+1;
+							storeName = fileName;
+						}
+					}
+				}
+				return storeName;
+			}
+		
+			@Override  
+		    public String normalizePath(String path) {
+				String fileSeparator = "";
+				if(!path.substring(path.length()-1, path.length()).equals(File.separator)) {fileSeparator = File.separator;}
+				return path + fileSeparator;
+			}
+		    
+			@Override  
+		    public String extractFileName(String path) {
+				String name = path.substring(path.lastIndexOf("\\")+1, path.length());
+				if(path.indexOf("/")>-1) {name = path.substring(path.lastIndexOf("/")+1, path.length());}
+				return name;
+			}
+			
+			@Override
+		    public void delete(List<String> list) throws Exception {
+		    	if(list!=null) {
+		    		for(String storePath: list) {this.delete(storePath);}
+		    	}
+		    }
+
+			@Override
+		    public boolean delete(String storePath) throws Exception {
+				boolean isDeleted = false;
+				if(StringUtils.isBlank(storePath)) {return isDeleted;}
+				File file = new File(FilenameUtils.normalize(storePath));
+				if(!file.exists()) { file.delete(); isDeleted = true;}
+				log.debug(">> isDeleted:{}, storePath:{}", isDeleted, storePath);
+				return isDeleted;
+			}
+			
+			
+			@Override
+		    public List<MultipartFile> getMultipartFile(String... filePaths) throws Exception {
+		    	return this.getMultipartFile(Arrays.asList(filePaths));
+		    }
+
+		    @Override
+		    public List<MultipartFile> getMultipartFile(List<String> filePaths) throws Exception {
+		    	if(filePaths==null || filePaths.isEmpty()) {return null;}
+		    	List<MultipartFile> list = new ArrayList<>();
+		    	for(String filePath : filePaths) {list.add(this.getMultipartFile(filePath));}
+		    	return list;
+		    }
+		    
+		    @Override
+		    public MultipartFile getMultipartFile(String filePath) throws Exception {
+		    	if(StringUtils.isBlank(filePath)) {return null;}
+		    	return this.getMultipartFile(new File(FilenameUtils.normalize(filePath)));
+		    }
+		    
+		    @Override
+		    public MultipartFile getMultipartFile(File file) throws Exception {
+		    	if(!file.exists()) {return null;}
+		    	String fieldName = "file"+String.valueOf(System.currentTimeMillis());    	
+		    	FileItem fileItem = new DiskFileItem(fieldName, Files.probeContentType(file.toPath()), false, file.getName(), (int) file.length(), file.getParentFile());
+		    	IOUtils.copy(new FileInputStream(file), fileItem.getOutputStream());
+		    	return new CommonsMultipartFile(fileItem);
+		    }
+
+			@Override
+		    public StoreInfo store(MultipartFile file, String storeDirectory) throws Exception {
+		    	return this.store(file, storeDirectory, null);
+		    }
+		    
+		    @Override
+		    public StoreInfo store(MultipartFile file, String storeDirectory, String[] acceptedFileTypes) throws Exception {
+		    	return this.store(file, storeDirectory, null, this.getMultipartConfigElement().getMaxFileSize());
+		    }
+		    
+		    @Override
+		    public StoreInfo store(MultipartFile file, String storeDirectory, String[] acceptedFileTypes, long maxFileSize) throws Exception {
+		    	return this.store(file, storeDirectory, acceptedFileTypes, maxFileSize, StoreInfo.class);
+		    }	    		    
+		    
+		    @Override
+		    public List<StoreInfo> store(List<MultipartFile> files, String storeDirectory) throws Exception {
+		    	return this.store(files, storeDirectory, null);
+		    }
+		    
+		    @Override
+		    public List<StoreInfo> store(List<MultipartFile> files, String storeDirectory, String[] acceptedFileTypes) throws Exception {
+		    	return this.store(files, storeDirectory, acceptedFileTypes, this.getMultipartConfigElement().getMaxFileSize());
+		    }
+		    
+		    @Override
+		    public List<StoreInfo> store(List<MultipartFile> files, String storeDirectory, String[] acceptedFileTypes, long maxFileSize) throws Exception {
+		    	return this.store(files, storeDirectory, acceptedFileTypes, maxFileSize, StoreInfo.class);
+		    }
+		    
+			@Override
+		    public <T extends StoreInfo> T store(MultipartFile file, String storeDirectory, String[] acceptedFileTypes, long maxFileSize, Class<T> type) throws Exception{
+				this.checkFile(file, acceptedFileTypes, maxFileSize);
+				return this.storeFile(file, storeDirectory, type);
+		    }	
+		    
+			@Override
+		    public <T extends StoreInfo> List<T> store(List<MultipartFile> files, String storeDirectory, String[] acceptedFileTypes, long maxFileSize, Class<T> type) throws Exception {
+				if(CollectionUtils.isEmpty(files)) {log.debug(">> List<MultipartFile> files is empty");return null;}
+				List<T> list = new ArrayList<>();
+				for(MultipartFile file : files) {this.checkFile(file, acceptedFileTypes, maxFileSize);}
+				for(MultipartFile file : files) {list.add(this.storeFile(file, storeDirectory, type));}
+				return list;	
+		    }
+		    
+			@Override
+			public <T extends StoreInfo> List<T> store(MultipartHttpServletRequest request, String storeDirectory, String[] acceptedFileTypes, long maxFileSize, Class<T> type) throws Exception {
+				if(!StoreInfo.class.isAssignableFrom(type)) {throw new SystemException(type.getCanonicalName()+") is not implemented. Class must implement kr.co.iya.base.support.util.vo.StoreInfo");}
+
+				List<T> list = new ArrayList<T>();			
+				Iterator<String> iterator = request.getFileNames();
+				while(iterator.hasNext()) {
+					String uploadFormName = (String)iterator.next();	
+					T vo = this.store(request.getFile(uploadFormName), storeDirectory, acceptedFileTypes, maxFileSize, type);
+					list.add(vo);
+				}
+
+				return list;
+			}
+
+			@Override
+		    public void pushout(String storePath, HttpServletRequest request, HttpServletResponse response) throws Exception {
+				String fileName = this.extractFileName(storePath);
+				this.pushout(storePath, fileName, request, response);
+			}
+			
+		    @Override
+		    public void pushout(String storePath, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+				File file = StringUtils.isBlank(storePath)?null:new File(FilenameUtils.normalize(storePath));
+				this.pushout(file, fileName, request, response);
+			}
+			
+			@Override
+		    public void pushout(File file, HttpServletRequest request, HttpServletResponse response) throws Exception {
+				String fileName = (file != null && file.exists())?file.getName():null;
+				this.pushout(file, fileName, request, response);
+			}
+
+			@Override
+		    public void pushout(File file, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+				this.pushout(()->{return file;}, fileName, request, response);
+			}	  
+			
+		    @Override
+		    public void pushout(byte[] bytes, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception{
+		        if(bytes == null) {throw new SystemException("bytes is null.");}
+				if(StringUtils.isBlank(fileName)) {throw new SystemException("fileName is empty.");}
+
+				this.buildDownloadHeader(fileName, request, response);				
+		        if(bytes != null) {response.setContentLength(bytes.length);}
+		        
+				OutputStream out = response.getOutputStream();
+				InputStream inputStream = new ByteArrayInputStream(bytes);
+				FileCopyUtils.copy(inputStream, out);
+				inputStream.close(); 
+		        out.flush();
+		        out.close();
+			}
+		    
+		    @Override
+		    public void pushout(Clob clob, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		        if(clob == null) {throw new SystemException("clob is null.");}
+		        if(StringUtils.isBlank(fileName)) {throw new SystemException("fileName is empty.");}
+
+		        this.buildDownloadHeader(fileName, request, response);	
+
+		        int contentLength = -1;
+		        if(contentLength>0) {response.setContentLength(contentLength);}
+		        
+		        Reader readerStream = clob.getCharacterStream();
+		        
+		        char[] readBuf = new char[FILE_READ_BUFFER_SIZE];
+		        int readCount = 0, writeCount = 0;
+		        while((readCount = readerStream.read(readBuf))>-1) {
+		            response.getOutputStream().write(new String(readBuf, 0, readCount).getBytes("UTF-8"));
+		            writeCount = writeCount+readCount;
+		            if(writeCount>FILE_FLUSH_UNIT_SIZE) {
+		                //log.debug(">>flush: {}", writeCount);                
+		                response.getOutputStream().flush();
+		                writeCount = 0;
+		            }
+		        }
+		        response.getOutputStream().flush();
+		        readerStream.close();
+		        response.getOutputStream().close();
+		    }
+
+		    @Override
+		    public void pushout(Blob blob, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		        if(blob == null) {throw new SystemException("blob is null.");}
+		        if(StringUtils.isBlank(fileName)) {throw new SystemException("fileName is empty.");}
+
+		        this.buildDownloadHeader(fileName, request, response);
+		        
+		        int contentLength = (int)blob.length();
+		        if(contentLength>0) {response.setContentLength(contentLength);}
+
+		        InputStream inputStream = blob.getBinaryStream();
+		        
+		        byte[] readBuf = new byte[FILE_READ_BUFFER_SIZE];
+		        int readCount = 0, writeCount = 0;
+		        while((readCount = inputStream.read(readBuf))>-1) {
+		            response.getOutputStream().write(readBuf, 0, readCount);
+		            writeCount = writeCount+readCount;            
+		            if(writeCount>FILE_FLUSH_UNIT_SIZE) {               
+		                response.getOutputStream().flush();
+		                writeCount = 0;
+		            }
+		        }
+		        response.getOutputStream().flush();
+		        inputStream.close();
+		        response.getOutputStream().close();        
+		    }
+
+		    @Override    
+		    public void pushout(FetchFile fetch, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		    	if(fetch == null) {throw new SystemException("fetch is null.");}
+		    	if(StringUtils.isBlank(fileName)) {throw new SystemException("fileName is empty.");}
+		    	
+		    	File file = fetch.getFile();
+				if(file==null || !file.exists()) {throw new SystemException("file not found.");}
+
+				this.buildDownloadHeader(fileName, request, response);
+				
+				int contentLength = (int)file.length();
+		        if(contentLength>0) {response.setContentLength(contentLength);}
+		        
+				OutputStream out = response.getOutputStream();
+				FileInputStream fis = new FileInputStream(file);
+				FileCopyUtils.copy(fis, out);
+		        fis.close(); 
+		        out.flush();
+		        out.close();
+		    }
+		    			
+		    @Override    
+		    public void pushout(FetchData fetch, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		        if(fetch == null) {throw new SystemException("fetch is null.");}
+		        if(StringUtils.isBlank(fileName)) {throw new SystemException("fileName is empty.");}
+
+		        this.buildDownloadHeader(fileName, request, response);
+		        
+		        int contentLength = -1;
+		        if(contentLength>0) {response.setContentLength(contentLength);}
+		        
+		        int fetchNo = 0, writeCount = 0;
+		        while(true) {
+		        	byte[] bytes = fetch.getData(fetchNo++);
+					if(bytes == null || bytes.length == 0) {break;}	
+		            response.getOutputStream().write(bytes);		            
+		            writeCount = writeCount+bytes.length;	            
+		            if(writeCount>FILE_FLUSH_UNIT_SIZE) {
+		                //log.debug(">>flush: {}", writeCount);                
+		                response.getOutputStream().flush();
+		                writeCount = 0;
+		            }
+		        }
+		        response.getOutputStream().flush();
+		        response.getOutputStream().close();
+		    }
+		    
+		    @Override  
+		    public void pushout(FetchInputStream fetch, String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception{
+		    	if(fetch == null) {throw new SystemException("fetch is null.");}
+		    	if(StringUtils.isBlank(fileName)) {throw new SystemException("fileName is empty.");}
+
+				this.buildDownloadHeader(fileName, request, response);
+		        
+				int contentLength = -1;
+		        if(contentLength>0) {response.setContentLength(contentLength);}
+		        
+		        InputStream inputStream = fetch.getInputStream();
+		        
+		        byte[] readBuf = new byte[FILE_READ_BUFFER_SIZE];
+		        int readCount = 0, writeCount = 0;
+		        while((readCount = inputStream.read(readBuf))>-1) {
+		            response.getOutputStream().write(readBuf, 0, readCount);
+		            writeCount = writeCount+readCount;            
+		            if(writeCount>FILE_FLUSH_UNIT_SIZE) {          
+		                response.getOutputStream().flush();
+		                writeCount = 0;
+		            }
+		        }	        
+		        response.getOutputStream().flush();
+		        inputStream.close();
+		        response.getOutputStream().close();  
+		    }
+		    
+		    private void buildDownloadHeader(String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		        String normalizeFn = Sweeper.normalizeFilename(fileName);		        
+		        String userAgent = request.getHeader("User-Agent");
+		        log.debug(">> User-Agent: {}", userAgent);
+		        
+		        String disposition = null;
+		        if(userAgent.indexOf("MSIE")>-1 || userAgent.indexOf("Trident")>-1) {
+		        	disposition = "attachment;filename=" + URLEncoder.encode(normalizeFn, "UTF-8").replaceAll("\\+", Character.toString((char)32))+";";
+		            log.debug(">> Content-Disposition(MSIE): {}", disposition); 
+		        } else if(userAgent.indexOf("Chrome")>-1) {
+		        	StringBuilder sb = new StringBuilder(); 
+		        	for(int i = 0; i < normalizeFn.length(); i++) { 
+		        		char c = normalizeFn.charAt(i); 
+		        		sb.append(c>'~'?URLEncoder.encode(""+c, "UTF-8"):c);
+		        	}
+		        	disposition = "attachment;filename=\"" + sb.toString() + "\";";
+		            log.debug(">> Content-Disposition(Chrome): {}", disposition);
+		        } else {
+		        	disposition = "attachment;filename=\"" + new String(normalizeFn.getBytes("UTF-8"), "8859_1") + "\";";
+		            log.debug(">> Content-Disposition: {}", disposition);
+		        }
+		        
+		       response.setHeader("Content-Type", "application/octet-stream");
+		       response.setHeader("Content-Disposition", disposition);
+		       response.setHeader("Content-Transfer-Encoding", "binary;");
+		       response.setHeader("Pragma", "no-cache;");
+		       response.setHeader("Expires", "-1;");
+		    } 
+		    
+			private void checkFile(MultipartFile file, String[] acceptedFileTypes, long maxFileSize) {
+				if(file == null || file.isEmpty()) {throw new SystemException("file is empty.");}
+
+				String originalFilename = file.getOriginalFilename();
+				if(StringUtils.isBlank(originalFilename)) {return;}
+
+				//Check maxFileSize
+				if(file.getSize() > maxFileSize) {
+					throw new SystemException("Attach file " + originalFilename + " size("+file.getSize()+") is over max size("+maxFileSize+")");
+				}
+			
+				//Check acceptedFileTypes
+				if(acceptedFileTypes != null) {
+					String fileExt = "";	
+					if(originalFilename.indexOf(".")>0) {					
+						fileExt = originalFilename.substring(originalFilename.lastIndexOf(".")+1, originalFilename.length());
+						boolean isEnableFileType = false;
+						for(int i = 0;i<acceptedFileTypes.length;i++) {
+							if(acceptedFileTypes[i].toLowerCase().equals(fileExt.toLowerCase())) {isEnableFileType=true; break;}
+						}
+						if(!isEnableFileType) {
+							throw new SystemException("Attach file " + originalFilename + " type("+fileExt+") is not allowed.");
+						}
+					}
+
+					if(fileExt.equals("")) {
+						throw new SystemException("Attach file " + originalFilename + " type is not exist.");					
+					}
+				}
+			}
+						
+			private <T extends StoreInfo> T storeFile(MultipartFile mFile, String storeDirectory, Class<T> type) throws Exception {
+				if(mFile == null || mFile.isEmpty()) {throw new SystemException("file is empty.");}
+				if(type == null) {throw new SystemException("clazz is empty.");}
+				if(StringUtils.isBlank(storeDirectory)) {throw new SystemException("storeDirectory is empty.");}
+				
+				String originalFilename = mFile.getOriginalFilename();			
+				if(originalFilename==null || originalFilename.equals("")) {return null;}
+
+				String transDate = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(System.currentTimeMillis());				
+				String storeName = this.getUniqueName(storeDirectory, transDate, originalFilename);				
+				String storePath = this.normalizePath(storeDirectory) + storeName;					
+
+				//To prevent path manipulation, FilenameUtils.normalizes a path to a standard format
+				storeDirectory = FilenameUtils.normalize(storeDirectory);
+				File dir = new File(storeDirectory);
+				if(!dir.isDirectory()) {dir.mkdirs();}
+			
+				//To prevent path manipulation, FilenameUtils.normalizes a path to a standard format
+				storePath = FilenameUtils.normalize(storePath);
+				mFile.transferTo(Paths.get(storePath));
+
+				T storeTag = type.getDeclaredConstructor().newInstance();
+				storeTag.setAttachName(originalFilename);
+				storeTag.setAttachSize(mFile.getSize());
+				storeTag.setStoreDirectory(storeDirectory);
+				storeTag.setStoreName(storeName);
+				storeTag.setStorePath(storePath);
+				storeTag.setStoreDate(transDate);
+				storeTag.setSystemInfo(systemUtil.getSystemInfo());
+				
+				return storeTag;	
+			}
+						
+		};
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 1146 - 0
src/main/java/kr/co/iya/base/support/util/GcpUtilPack.java

@@ -0,0 +1,1146 @@
+package kr.co.iya.base.support.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.threeten.bp.Duration;
+
+import com.google.api.gax.paging.Page;
+import com.google.cloud.RetryOption;
+import com.google.cloud.bigquery.BigQuery;
+import com.google.cloud.bigquery.BigQuery.DatasetListOption;
+import com.google.cloud.bigquery.BigQueryException;
+import com.google.cloud.bigquery.CsvOptions;
+import com.google.cloud.bigquery.Dataset;
+import com.google.cloud.bigquery.DatasetId;
+import com.google.cloud.bigquery.DatasetInfo;
+import com.google.cloud.bigquery.ExtractJobConfiguration;
+import com.google.cloud.bigquery.Job;
+import com.google.cloud.bigquery.JobId;
+import com.google.cloud.bigquery.JobInfo;
+import com.google.cloud.bigquery.JobInfo.WriteDisposition;
+import com.google.cloud.bigquery.LoadJobConfiguration;
+import com.google.cloud.bigquery.QueryJobConfiguration;
+import com.google.cloud.bigquery.Schema;
+import com.google.cloud.bigquery.StandardTableDefinition;
+import com.google.cloud.bigquery.Table;
+import com.google.cloud.bigquery.TableDefinition;
+import com.google.cloud.bigquery.TableId;
+import com.google.cloud.bigquery.TableInfo;
+import com.google.cloud.bigquery.TableResult;
+import com.google.cloud.spring.autoconfigure.core.GcpProperties;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.Storage;
+
+import kr.co.iya.base.context.GcpContext;
+import kr.co.iya.base.context.GcpContext.BigQueryHandler;
+import kr.co.iya.base.context.GcpContext.BucketObjectInfo;
+import kr.co.iya.base.context.GcpContext.StorageHandler;
+import kr.co.iya.base.exception.SystemException;
+import kr.co.iya.base.support.aid.BeanAidPack.BeanAid;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class GcpUtilPack  {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public interface GcpUtil {
+
+		public GcpContext getContext();
+		
+		public StorageHandler getDefaultStorageHandler();		
+		public BigQueryHandler getDefaultBigQueryHandler();
+		
+		public StorageHandler getStorageHandler(String projectId);		
+		public BigQueryHandler getBigQueryHandler(String projectId);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static GcpUtil getGcpUtil(BeanAid beanAid) {
+		
+		return new GcpUtil() {
+			
+			private GcpContext context = null;
+			private Map<String,String> systemInfo = null;
+			
+			@Override
+			public GcpContext getContext() {
+				
+				if(this.context == null) {
+					this.context = beanAid.getBean(GcpContext.class);
+				}
+				
+				if(this.context == null || !this.context.isEnabled()) {
+
+					BigQuery defaultBigQuery = beanAid.getBean(BigQuery.class);
+					//log.debug(">> defaultBigQuery: {}",defaultBigQuery == null?null:defaultBigQuery.hashCode());
+					
+					Storage defaultStorage = beanAid.getBean(Storage.class);		
+					//log.debug(">> defaultStorage: {}",defaultStorage == null?null:defaultStorage.hashCode());
+					
+					this.context.initContext(beanAid.getBean(GcpProperties.class), defaultBigQuery, defaultStorage);
+					
+					this.getDefaultBigQueryHandler();
+					this.getDefaultStorageHandler();					
+				}	
+				
+				return this.context;
+			}
+			
+			@Override
+			public StorageHandler getDefaultStorageHandler() {
+				return this.getStorageHandler(this.getContext().getDefaultProjectId());
+			}
+			
+			@Override
+			public BigQueryHandler getDefaultBigQueryHandler() {
+				return this.getBigQueryHandler(this.getContext().getDefaultProjectId());
+			}
+			
+			@Override
+			public BigQueryHandler getBigQueryHandler(String projectId) {
+				BigQueryHandler handler = this.getContext().getBigQueryHandler(projectId);
+				if(handler != null) {return handler;}
+				
+				BigQuery bigQuery = this.getContext().getBigQuery(projectId);
+				if(bigQuery == null) {return null;}
+				
+				this.getContext().setBigQueryHandler(projectId, this.buildBigQueryHandler(bigQuery));
+				return this.getBigQueryHandler(projectId);
+			}
+			
+			@Override
+			public StorageHandler getStorageHandler(String projectId){
+				StorageHandler handler = this.getContext().getStorageHandler(projectId);
+				if(handler != null) {return handler;}
+				
+				Storage storage = this.getContext().getStorage(projectId);
+				if(storage == null) {return null;}
+				
+				this.getContext().setStorageHandler(projectId, this.buildStorageHandler(storage));
+				return this.getStorageHandler(projectId);
+			}
+						
+			private BigQueryHandler buildBigQueryHandler(BigQuery bigQuery) {
+				return new BigQueryHandler() {
+					
+					@Override
+					public BigQuery getBigQuery() {
+						return bigQuery;
+					}
+					
+					@Override
+					public Map<String,Object> convert(Table table){
+						if(table == null) {return null;}
+						Map<String,Object> map = new HashMap<>();
+						map.put("tableId", table.getTableId());
+						map.put("etag", table.getEtag());
+						map.put("generatedId", table.getGeneratedId());
+						map.put("selfLink", table.getSelfLink());
+						map.put("friendlyName", table.getFriendlyName());
+						map.put("description", table.getDescription());
+						map.put("expirationTime", table.getExpirationTime());
+						map.put("creationTime", table.getCreationTime());
+						map.put("lastModifiedTime", table.getLastModifiedTime());
+						map.put("numBytes", table.getNumBytes());
+						map.put("numLongTermBytes", table.getNumLongTermBytes());
+						map.put("numTimeTravelPhysicalBytes", table.getNumTimeTravelPhysicalBytes());
+						map.put("numTotalLogicalBytes", table.getNumTotalLogicalBytes());
+						map.put("numActiveLogicalBytes", table.getNumActiveLogicalBytes());
+						map.put("numLongTermLogicalBytes", table.getNumLongTermLogicalBytes());
+						map.put("numTotalPhysicalBytes", table.getNumTotalPhysicalBytes());
+						map.put("numActivePhysicalBytes", table.getNumActivePhysicalBytes());
+						map.put("numLongTermPhysicalBytes", table.getNumLongTermPhysicalBytes());
+						map.put("numRows", table.getNumRows());
+						map.put("definition", table.getDefinition().toString());
+						map.put("encryptionConfiguration", table.getEncryptionConfiguration());
+						map.put("labels", table.getLabels());
+						map.put("requirePartitionFilter", table.getRequirePartitionFilter());
+						map.put("defaultCollation", table.getDefaultCollation());
+						map.put("cloneDefinition", table.getCloneDefinition());
+						map.put("tableConstraints", table.getTableConstraints());				
+						return map;
+					}
+					
+					@Override	
+					public Map<String,Object> convert(Blob blob){
+						Map<String,Object> map = new HashMap<>();
+						map.put("systemInfo",getSystemInfo());						
+						map.put("bucket",blob.getBucket());
+						map.put("path",blob.getName());
+						map.put("name",extractFileName(blob.getName()));
+						map.put("size",blob.getSize());
+						map.put("directory",blob.isDirectory());
+						map.put("createTime",blob.getCreateTimeOffsetDateTime() == null?null:blob.getCreateTimeOffsetDateTime().atZoneSameInstant(ZoneId.systemDefault()).toString());			
+						map.put("updateTime",blob.getUpdateTimeOffsetDateTime() == null?null:blob.getUpdateTimeOffsetDateTime().atZoneSameInstant(ZoneId.systemDefault()).toString());
+						map.put("contentType",blob.getContentType());
+						map.put("downloadStorePath","");
+						map.put("downloadTime","");				
+						map.put("metadata",blob.getMetadata());
+						return map;
+					}
+					
+					@Override
+					public Map<String,Object> convert(Dataset dataset) {
+						if(dataset == null) {return null;}
+						Map<String,Object> map = new HashMap<>();
+						map.put("datasetId", dataset.getDatasetId());
+						map.put("creationTime", dataset.getCreationTime());
+						map.put("defaultTableLifetime", dataset.getDefaultTableLifetime());
+						map.put("description", dataset.getDescription());
+						map.put("etag", dataset.getEtag());
+						map.put("friendlyName", dataset.getFriendlyName());
+						map.put("generatedId", dataset.getGeneratedId());
+						map.put("lastModified", dataset.getLastModified());
+						map.put("location", dataset.getLocation());
+						map.put("selfLink", dataset.getSelfLink());
+						map.put("acl", dataset.getAcl());
+						map.put("labels", dataset.getLabels());
+						map.put("defaultEncryptionConfiguration", dataset.getDefaultEncryptionConfiguration());
+						map.put("defaultPartitionExpirationMs", dataset.getDefaultPartitionExpirationMs());
+						map.put("defaultCollation", dataset.getDefaultCollation());
+						map.put("externalDatasetReference", dataset.getExternalDatasetReference());
+						map.put("storageBillingModel", dataset.getStorageBillingModel());
+				        return map;
+					}
+
+					@Override
+					public List<Map<String,Object>> convert(List<?> list){						
+						if(list == null) {return null;}
+						List<Map<String,Object>> result = new ArrayList<>();
+						list.forEach(i->{
+							if(i instanceof Dataset){result.add(this.convert((Dataset)i));}
+							if(i instanceof Table){result.add(this.convert((Table)i));}
+							if(i instanceof Blob){result.add(this.convert((Blob)i));}
+						});
+						return result;
+					}
+					
+					@Override
+				    public List<Dataset> listDatasets(String projectId) {
+				    	Page<Dataset> datasets = bigQuery.listDatasets(projectId,DatasetListOption.all());				    	
+						List<Dataset> list = new ArrayList<>();
+						datasets.iterateAll().forEach(dataset->{list.add(dataset);});
+						return list;
+				    }
+
+					@Override
+				    public Dataset getDataset(String projectId, String datasetName) {
+				    	return bigQuery.getDataset(DatasetId.of(projectId,datasetName));
+				    }
+				    
+					@Override
+				    public boolean isExistDataset(String projectId, String datasetName) {
+				    	return this.getDataset(projectId,datasetName) == null?false:true;
+				    }
+
+					@Override
+				    public Dataset createDataset(String projectId, String datasetName) {
+				    	if(this.isExistDataset(projectId, datasetName)) {throw new SystemException("dataset("+datasetName+") is already exists.");}
+				    	return bigQuery.create(DatasetInfo.newBuilder(projectId,datasetName).build());
+				    }
+
+					@Override
+				    public List<Table> listTables(String projectId, String datasetName) {
+				    	if(!this.isExistDataset(projectId, datasetName)) {throw new SystemException("dataset("+datasetName+") is not found.");}
+				    	Page<Table> tables = bigQuery.listTables(DatasetId.of(projectId, datasetName));						
+				    	List<Table> list = new ArrayList<>();    
+				    	tables.iterateAll().forEach(table->{list.add(table);});
+				    	return list;
+				    }
+
+					@Override
+				    public Table getTable(String projectId, String datasetName, String tableName) {
+				    	return bigQuery.getTable(TableId.of(projectId, datasetName, tableName));
+				    }
+
+					@Override
+				    public boolean isExistTable(String projectId, String datasetName, String tableName) {
+				    	return this.getTable(projectId, datasetName, tableName) == null?false:true;
+				    }
+				    
+					@Override			
+				    public Table createTable(String projectId, String datasetName, String tableName, Schema schema) {
+				    	if(this.isExistTable(projectId, datasetName, tableName)) {throw new SystemException("table("+tableName+") is already exists.");}				       
+				    	TableId tableId = TableId.of(projectId, datasetName, tableName);
+				        TableDefinition tableDefinition = StandardTableDefinition.of(schema == null?Schema.of():schema);				        
+				        return bigQuery.create(TableInfo.newBuilder(tableId, tableDefinition).build());    	
+				    }
+					 
+					@Override	
+					public Table createTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri) {
+						return this.createTableFromCsv(projectId, datasetName, tableName, null, bucketUri);
+					}
+					
+
+					@Override	
+				    public Table createTableFromCsv(String projectId, String datasetName, String tableName, Schema schema, String bucketUri) {
+						return this.createTableFromCsv(projectId, datasetName, tableName, schema, bucketUri, null);
+					}
+				    
+					@Override			
+				    public Table createTableFromCsv(String projectId, String datasetName, String tableName, Schema schema, String bucketUri, CsvOptions csvOptions) {
+						return this._workTableFromCsv(projectId, datasetName, tableName, schema, bucketUri, csvOptions, null);
+				    }
+					
+					@Override	
+				    public TableResult executeQuery(String projectId, String datasetName, String query, boolean isLegacySql) {
+						TableResult result = null;
+						try{
+							QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(query)
+									.setUseLegacySql(isLegacySql)
+									.build();
+							
+							JobId jobId = JobId.of(UUID.randomUUID().toString());
+							Job queryJob = bigQuery.create(JobInfo.newBuilder(queryConfig).setJobId(jobId).build()).waitFor();
+	
+						    if(queryJob == null) {
+						    	throw new RuntimeException("Job no longer exists");
+						    }
+						    
+						    if(queryJob.getStatus().getError() != null) {
+						    	throw new RuntimeException(queryJob.getStatus().getError().toString());
+						    }
+	
+						    result = queryJob.getQueryResults();
+						    
+				    	} catch (BigQueryException | InterruptedException e) {
+				        	throw new SystemException(e.getMessage());
+				        }
+						
+						return result;
+					}
+
+					
+					@Override	
+					public Table createTableFromQuery(String projectId, String datasetName, String tableName, String query, boolean isLegacySql) {
+						if(this.isExistTable(projectId, datasetName, tableName)) {throw new SystemException("table("+tableName+") is already exists.");}
+				    	return this._workTableFromQuery(projectId, datasetName, tableName, WriteDisposition.WRITE_TRUNCATE, query, isLegacySql);
+					}
+					
+					@Override			
+				    public Table truncateTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri) {
+						return this.truncateTableFromCsv(projectId, datasetName, tableName, bucketUri, null);
+					}
+					
+					@Override			
+				    public Table truncateTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri, CsvOptions csvOptions) {
+						return this._workTableFromCsv(projectId, datasetName, tableName, null, bucketUri, csvOptions, WriteDisposition.WRITE_TRUNCATE);
+				    }
+				   
+					@Override			
+					public Table truncateTableFromQuery(String projectId, String datasetName, String tableName, String query, boolean isLegacySql) {
+						if(!this.isExistTable(projectId, datasetName, tableName)) {throw new SystemException("table("+tableName+") is not exist.");}
+						return this._workTableFromQuery(projectId, datasetName, tableName, WriteDisposition.WRITE_TRUNCATE, query, isLegacySql);
+					}
+					 
+					@Override
+					public Table appendTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri) {
+						return this.appendTableFromCsv(projectId, datasetName, tableName, bucketUri, null);				
+					}
+
+					@Override
+					public Table appendTableFromCsv(String projectId, String datasetName, String tableName, String bucketUri, CsvOptions csvOptions) {
+						return this._workTableFromCsv(projectId, datasetName, tableName, null, bucketUri, csvOptions, WriteDisposition.WRITE_APPEND);
+					}
+						
+					@Override
+				    public Table appendTableFromQuery(String projectId, String datasetName, String tableName, String query, boolean isLegacySql) {
+						if(!this.isExistTable(projectId, datasetName, tableName)) {throw new SystemException("table("+tableName+") is not exist.");}
+						return this._workTableFromQuery(projectId, datasetName, tableName, WriteDisposition.WRITE_APPEND, query, isLegacySql);				    	
+				    }
+				    
+					@Override
+				    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri) {
+						return this.extractQueryToCsv(projectId, datasetName, query, isLegacySql, bucketUri, ",");
+					}
+					
+					@Override
+				    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri, String fieldDelimiter) {	
+						return this.extractQueryToCsv(projectId, datasetName, query, isLegacySql, bucketUri, fieldDelimiter, true);						
+					}
+					
+					@Override
+				    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri, String fieldDelimiter, boolean printHeader) {
+						return this.extractQueryToCsv(projectId, datasetName, query, isLegacySql, bucketUri, fieldDelimiter, printHeader, false);						
+					}
+					
+					@Override
+				    public List<Blob> extractQueryToCsv(String projectId, String datasetName, String query, boolean isLegacySql, String bucketUri, String fieldDelimiter, boolean printHeader, boolean isCompressed) {
+						Table table = null;
+						List<Blob> list = null;
+						try{
+							String tableName = "tb_utcb"+(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date(System.currentTimeMillis()))).toString();
+							table = this.createTableFromQuery(projectId, datasetName, tableName, query, isLegacySql);
+							list = this.extractTableToCsv(projectId, datasetName, tableName, bucketUri, fieldDelimiter, printHeader, isCompressed);
+						} catch(Exception e) {
+							throw e;
+						} finally {
+							if(table != null) {table.delete();}
+						}
+						return list;	
+					}
+				    
+					@Override
+				    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri) {
+				    	return this.extractTableToCsv(projectId, datasetName, tableName, bucketUri, null);
+				    }
+				    
+					@Override
+				    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri, String fieldDelimiter) {
+						return this.extractTableToCsv(projectId, datasetName, tableName, bucketUri, fieldDelimiter, true);						
+					}
+					
+					@Override
+				    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri, String fieldDelimiter, boolean printHeader) {
+						return this.extractTableToCsv(projectId, datasetName, tableName, bucketUri, fieldDelimiter, printHeader);							
+					}
+				    
+					@Override
+				    public List<Blob> extractTableToCsv(String projectId, String datasetName, String tableName, String bucketUri, String fieldDelimiter, boolean printHeader, boolean isCompressed) {
+						return this._workTableToBucket(projectId, datasetName, tableName, bucketUri, "CSV", fieldDelimiter, printHeader, isCompressed?"gzip":null);
+					}
+
+					private Table _workTableFromQuery(String projectId, String datasetName, String tableName, WriteDisposition writeDisposition, String query, boolean isLegacySql) {
+						try{
+				    		TableId tableId = TableId.of(projectId, datasetName, tableName);
+				    		log.debug(">> tableId:{}",tableId);
+				    		
+				    		QueryJobConfiguration queryConfig = null;
+				    		
+					        if(writeDisposition == null) {
+					    		queryConfig = QueryJobConfiguration.newBuilder(query)
+					    				.setDestinationTable(tableId)
+					    				.setAllowLargeResults(true)
+					    				.setWriteDisposition(WriteDisposition.WRITE_EMPTY)
+					    				.setUseLegacySql(isLegacySql)
+					    				.build();
+					        }				    		
+				    		if(WriteDisposition.WRITE_TRUNCATE.equals(writeDisposition)) {
+					    		queryConfig = QueryJobConfiguration.newBuilder(query)
+					    				.setDestinationTable(tableId)
+					    				.setAllowLargeResults(true)
+					    				.setWriteDisposition(WriteDisposition.WRITE_TRUNCATE)
+					    				.setUseLegacySql(isLegacySql)
+					    				.build();
+				    		}
+				    		if(WriteDisposition.WRITE_APPEND.equals(writeDisposition)) {
+						    	queryConfig = QueryJobConfiguration.newBuilder(query)
+						    			.setDestinationTable(tableId)
+						    			.setAllowLargeResults(true)
+						    			.setWriteDisposition(WriteDisposition.WRITE_APPEND)
+						    			.setUseLegacySql(isLegacySql)
+						    			.build();
+				    		}
+				    		
+				    		TableResult result = bigQuery.query(queryConfig);
+				    		log.debug(">> extract rows:{}",result.getTotalRows());
+							 
+				    	} catch (BigQueryException | InterruptedException e) {
+				        	throw new SystemException(e.getMessage());
+				        }
+
+				    	return this.getTable(projectId, datasetName, tableName);
+					}
+
+					private Table _workTableFromCsv(String projectId, String datasetName, String tableName, Schema schema, String bucketUri, CsvOptions csvOptions, WriteDisposition writeDisposition) {
+						try{
+					        TableId tableId = TableId.of(projectId, datasetName, tableName);
+					        log.debug(">> tableId:{}",tableId);
+					        
+					        String sourceUri = bucketUri.startsWith("gs://")?bucketUri:"gs://"+bucketUri;
+					        
+					        LoadJobConfiguration configuration = null;
+
+					        if(writeDisposition == null) {
+					        	configuration = LoadJobConfiguration.newBuilder(tableId, sourceUri)
+					        			.setFormatOptions(csvOptions == null?CsvOptions.newBuilder().setSkipLeadingRows(1).build():csvOptions)
+					        			.setWriteDisposition(WriteDisposition.WRITE_EMPTY)
+						        		.setSchema(schema)
+						        		.setAutodetect(schema == null?true:false)
+						        		.build();
+					        }
+					        if(WriteDisposition.WRITE_TRUNCATE.equals(writeDisposition)) {		        
+						        configuration = LoadJobConfiguration.builder(tableId, sourceUri)
+						        		.setFormatOptions(csvOptions == null?CsvOptions.newBuilder().setSkipLeadingRows(1).build():csvOptions)
+					                    .setWriteDisposition(WriteDisposition.WRITE_TRUNCATE)
+					                    .build();
+					        }
+					        if(WriteDisposition.WRITE_APPEND.equals(writeDisposition)) {		        
+						        configuration = LoadJobConfiguration.builder(tableId, sourceUri)
+						        		.setFormatOptions(csvOptions == null?CsvOptions.newBuilder().setSkipLeadingRows(1).build():csvOptions)			        		
+					                    .setWriteDisposition(WriteDisposition.WRITE_APPEND)
+					                    .build();
+					        }
+					        
+					        Job job = bigQuery.create(JobInfo.of(configuration))
+					        		.waitFor(RetryOption.initialRetryDelay(Duration.ofSeconds(1)),RetryOption.totalTimeout(Duration.ofMinutes(60)));
+
+				    		if (job == null) {
+				    			log.debug(">> job not executed since it no longer exists.");
+				    			return null;
+				    		}
+					        if (job.getStatus().getError() != null) {
+					        	log.debug(">> bigQuery was unable to load into the table due to an error:"+ job.getStatus().getError());
+					        	return null;
+					        }
+				    		log.debug(">> data(CVS) load successful");
+					        
+				        } catch (BigQueryException | InterruptedException e) {
+				        	throw new SystemException(e.getMessage());
+				        }	
+				    	
+				    	return this.getTable(projectId, datasetName, tableName);
+					}
+							
+					private List<Blob> _workTableToBucket(String projectId, String datasetName, String tableName, String bucketUri, String dataFormat, String fieldDelimiter, Boolean printHeader, String compressed){
+				    	try{			
+				    		TableId tableId = TableId.of(projectId, datasetName, tableName);
+				    		String targetUri = bucketUri.startsWith("gs://")?bucketUri:"gs://"+bucketUri;
+				    		 
+				    		ExtractJobConfiguration configuration = ExtractJobConfiguration.newBuilder(tableId, targetUri)
+				    				.setFieldDelimiter(fieldDelimiter == null?",":fieldDelimiter)
+				    				.setPrintHeader(printHeader)
+				    				.setCompression(compressed)
+				    				.setFormat(dataFormat)
+				    				.build();
+				    	    
+				    		Job job = bigQuery.create(JobInfo.of(configuration))
+				    				.waitFor(RetryOption.initialRetryDelay(Duration.ofSeconds(1)),RetryOption.totalTimeout(Duration.ofMinutes(60)));    		
+
+				    		if (job == null) {
+				    			log.debug(">> job not executed since it no longer exists.");
+				    			return null;
+				    		}
+				    		
+				    		if (job.getStatus().getError() != null) {
+				    			log.debug(">> bigQuery was unable to extract due to an error: \n" + job.getStatus().getError());
+				    			return null;
+				    		}
+				    		log.debug(">> Table export successful");
+				    		
+				        } catch (BigQueryException | InterruptedException e) {
+				        	throw new SystemException(e.getMessage());
+				        }
+
+						if(bucketUri.startsWith("gs://")) {bucketUri = bucketUri.substring("gs://".length());}
+						String bucket = bucketUri.indexOf("/")!=-1?bucketUri.substring(0,bucketUri.indexOf("/")):bucketUri;
+						String remotePath = bucketUri.indexOf("/")!=-1?bucketUri.substring(bucketUri.indexOf("/")+1):"";
+						
+						List<Blob> list = getDefaultStorageHandler().listObjects(bucket,remotePath);
+						
+				    	return list;
+				    }	
+					
+				};
+			}
+			
+			private StorageHandler buildStorageHandler(Storage storage) {
+				return new StorageHandler() {
+
+					@Override
+					public Storage getStorage() {
+						return storage;
+					}
+					
+					@Override	
+					public Map<String,Object> convert(Blob blob){						
+						Map<String,Object> map = new HashMap<>();	
+						map.put("systemInfo",getSystemInfo());
+						map.put("bucket",blob.getBucket());
+						map.put("path",blob.getName());
+						map.put("name",extractFileName(blob.getName()));
+						map.put("size",blob.getSize());
+						map.put("directory",blob.isDirectory());
+						map.put("createTime",blob.getCreateTimeOffsetDateTime() == null?null:blob.getCreateTimeOffsetDateTime().atZoneSameInstant(ZoneId.systemDefault()).toString());			
+						map.put("updateTime",blob.getUpdateTimeOffsetDateTime() == null?null:blob.getUpdateTimeOffsetDateTime().atZoneSameInstant(ZoneId.systemDefault()).toString());
+						map.put("contentType",blob.getContentType());
+						map.put("downloadStorePath","");
+						map.put("downloadTime","");				
+						map.put("metadata",blob.getMetadata());
+						
+						return map;
+					}
+					
+					@Override	
+					public List<Map<String,Object>> convert(List<Blob> list){
+						if(list == null) {return null;}
+						List<Map<String,Object>> result = new ArrayList<>();
+						list.forEach(i->result.add(this.convert(i)));
+						return result;
+					}
+					
+					@Override	
+					public boolean isExistBucket(String bucket) {
+						return this.getBucket(bucket) == null?false:true;
+					}
+					
+					@Override	
+					public boolean isExistObject(String bucket, String remotePath) {
+						return this.getObject(bucket, remotePath) == null?false:true;
+					}
+					
+					@Override
+					public boolean isDirectory(String bucket, String remotePath) {
+						Blob blob = storage.get(BlobId.of(bucket, trimSeparator(remotePath)));				
+			        	//log.debug(">> [isDirectory] blob:{}",blob);
+						if(blob != null && !blob.isDirectory()) {return false;}			        	
+						return true;
+					}
+					
+					@Override	
+				    public Bucket getBucket(String bucket) {
+						return storage.get(bucket);
+					}
+					
+					@Override	
+					public Blob getObject(String bucket, String remotePath) {
+						return storage.get(bucket, remotePath);
+					}
+					
+					@Override
+					public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetRemotePath) {
+						return this.move(sourceBucket, sourceRemotePath, sourceBucket, targetRemotePath);
+					}
+
+					@Override
+					public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetRemotePath, boolean isOverWrite){
+						return this.move(sourceBucket, sourceRemotePath, sourceBucket, targetRemotePath, isOverWrite);
+					}
+					
+					@Override
+					public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetBucket, String targetRemotePath) {
+						return this._move(sourceBucket, sourceRemotePath, targetBucket, targetRemotePath, true, 0);
+					}
+
+					@Override
+					public List<Blob> move(String sourceBucket, String sourceRemotePath, String targetBucket, String targetRemotePath, boolean isOverWrite){						
+						long startTime = System.nanoTime();
+						List<Blob> list = this._move(sourceBucket, sourceRemotePath, targetBucket, targetRemotePath, isOverWrite, 0);
+						log.debug(">> move.time:{}",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-startTime));
+						return list;						
+					}
+
+					@Override	
+					public List<String> listBuckets(){
+						List<String> list = new ArrayList<>();
+						Page<Bucket> buckets = storage.list();
+						buckets.iterateAll().forEach(bucket->list.add(bucket.getName()));
+					    return list;
+					}
+					
+					@Override
+					public List<Blob> listObjects(String bucket) {
+						return this.listObjects(bucket,"");
+					}
+					
+					@Override
+					public List<Blob> listObjects(String bucket, String remotePath) {
+						remotePath = trimSeparator(remotePath);
+						if(remotePath.indexOf("*")!=-1) {
+							remotePath = remotePath.substring(0,remotePath.indexOf("*"));
+						}else {
+							Blob object = storage.get(BlobId.of(bucket,remotePath));
+							if(object != null && !object.isDirectory()) {
+								List<Blob> result = new ArrayList<>();
+								result.add(object);
+								return result;
+							}
+						}
+						remotePath = trimSeparator(remotePath+"/");
+						
+						Page<Blob> blobs = storage.list(bucket,Storage.BlobListOption.currentDirectory(),Storage.BlobListOption.prefix(remotePath));
+						if(blobs == null) {return null;}
+	
+						List<Blob> result = new ArrayList<>();
+						blobs.iterateAll().forEach(blob->{result.add(blob);});
+						return result;
+					}
+					
+					@Override
+					public List<Blob> upload(String bucket, String storePath, String remotePath) throws IOException{
+						return this.upload(bucket, storePath, remotePath, UploadType.stream, true);
+					}
+					
+					@Override
+					public List<Blob> upload(String bucket, String storePath, String remotePath, UploadType uploadType) throws IOException{
+						return this.upload(bucket, storePath, remotePath, uploadType, true);
+					}
+					
+					@Override
+					public List<Blob> upload(String bucket, String storePath, String remotePath, boolean isOverWrite) throws IOException{
+						return this.upload(bucket, storePath, remotePath, UploadType.stream, isOverWrite);
+					}
+
+					@Override
+					public List<Blob> upload(String bucket, String storePath, String remotePath, UploadType uploadType, boolean isOverWrite) throws IOException{
+						long startTime = System.nanoTime();
+						List<Blob> list = this._upload(bucket, storePath, remotePath, uploadType, true);						
+						log.debug(">> upload.time:{}",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-startTime));
+						return list;
+					}
+					
+					@Override
+					public BucketObjectInfo download(String bucket, String remotePath, OutputStream outputStream) {
+						Blob blob = storage.get(BlobId.of(bucket, trimSeparator(remotePath)));
+					    blob.downloadTo(outputStream);    
+						return this._convert(blob,null,System.currentTimeMillis());
+					}
+					
+					@Override
+					public BucketObjectInfo download(String bucket, String remotePath, File file) {
+						Blob blob = storage.get(BlobId.of(bucket, trimSeparator(remotePath)));
+						String downloadStorePath = file.getPath();			
+						if(file.exists()) {file.delete();}
+					    blob.downloadTo(Paths.get(downloadStorePath));
+						return this._convert(blob,downloadStorePath,System.currentTimeMillis());
+					}
+					
+					@Override
+					public List<BucketObjectInfo> download(String bucket, String remotePath, String storePath) {
+						return this.download(bucket, remotePath, storePath, true);
+					}
+					
+					@Override
+					public List<BucketObjectInfo> download(String bucket, String remotePath, String storePath, boolean isOverWrite) {
+						long startTime = System.nanoTime();
+						List<BucketObjectInfo> list = this._download(bucket, remotePath, storePath, isOverWrite);
+						log.debug(">> download.time:{}",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-startTime));
+						return list;
+					}
+					
+					@Override
+					public void delete(String bucket, String remotePath) {
+						this._delete(bucket, remotePath);
+					}
+
+					@Override
+					public Blob compose(String bucket, List<String> sources, String targetPath) {
+						return this._compose(bucket, sources, targetPath);
+					}
+					
+					private BucketObjectInfo _convert(Blob blob, String downloadStorePath, long downloadTime) {
+						BucketObjectInfo info = new BucketObjectInfo();				
+						info.setSystemInfo(getSystemInfo());
+						info.setBucket(blob.getBucket());
+						info.setPath(blob.getName());
+						info.setName(extractFileName(blob.getName()));
+						info.setSize(blob.getSize());
+						info.setDirectory(blob.isDirectory());
+						info.setCreateTime(blob.getCreateTimeOffsetDateTime() == null?null:blob.getCreateTimeOffsetDateTime().atZoneSameInstant(ZoneId.systemDefault()));			
+						info.setUpdateTime(blob.getUpdateTimeOffsetDateTime() == null?null:blob.getUpdateTimeOffsetDateTime().atZoneSameInstant(ZoneId.systemDefault()));
+						info.setContentType(blob.getContentType());
+						info.setDownloadStorePath(downloadStorePath);
+						info.setDownloadTime(downloadTime);				
+						info.setMetadata(blob.getMetadata());
+						return info;
+					}
+					
+					private void _delete(String bucket, String remotePath) {
+						Blob blob = storage.get(bucket, remotePath);
+						if(blob != null) {
+							storage.delete(bucket, remotePath, Storage.BlobSourceOption.generationMatch(blob.getGeneration()));
+						}else {											
+							Page<Blob> page = storage.list(bucket,Storage.BlobListOption.prefix(remotePath));
+							if(page.iterateAll().iterator().hasNext()) {
+								for(Blob item : page.iterateAll()){
+									if(item.isDirectory()) {continue;}
+									this.delete(bucket,item.getName());
+								}								
+							}
+						}
+						return;
+					}
+										
+					private Blob _compose(String bucket, List<String> sources, String targetPath) {
+						if(sources == null || sources.isEmpty()) {throw new SystemException("sources is null or empty.");}
+
+						if(sources.size()>32) {
+							String fileName = extractFileName(targetPath);
+							String subPath = targetPath.replace(fileName,"")+System.nanoTime()+fileName;
+							
+							Blob composeBlob = null;
+							Blob subBlob = null;
+							try{
+								List<String> subList1 = sources.subList(0,32);					
+								subBlob = this._compose(bucket, subList1, subPath);
+								
+								List<String> subList2 = sources.subList(32,sources.size());
+								subList2.set(0, subBlob.getName());
+								
+								composeBlob = this._compose(bucket, subList2, targetPath);
+
+							} catch(Exception e) {
+								throw new SystemException(e.getMessage());
+							} finally {
+								if(subBlob != null) {subBlob.delete();}
+							}
+							
+							return composeBlob;							
+						}
+						
+					    Storage.BlobTargetOption precondition;
+					    if (storage.get(bucket, targetPath) == null) {
+					    	precondition = Storage.BlobTargetOption.doesNotExist();
+					    } else {
+					    	precondition = Storage.BlobTargetOption.generationMatch(storage.get(bucket, targetPath).getGeneration());
+					    }
+						
+					    Storage.ComposeRequest composeRequest = Storage.ComposeRequest.newBuilder()
+					                .addSource(sources)
+					                .setTarget(BlobInfo.newBuilder(bucket, targetPath).build())
+					                .setTargetOptions(precondition)
+					                .build();
+					    
+					    return storage.compose(composeRequest);
+					}
+					
+					private List<Blob> _move(String sourceBucket, String sourceRemotePath, String targetBucket, String targetRemotePath, boolean isOverWrite, int depth) {
+						sourceRemotePath = trimSeparator(sourceRemotePath);
+						targetRemotePath = trimSeparator(targetRemotePath);
+
+						//같은 버킷, 같은 경로 체크
+						if(sourceBucket.equals(targetBucket) && sourceRemotePath.equals(targetRemotePath)) {
+							log.debug(">> [move] same bucket, same path");
+							return null;
+						}
+
+						//소스원격경로가 파일인 경우
+						Blob blob = storage.get(BlobId.of(sourceBucket, sourceRemotePath));
+						log.debug(">> [move] blob({}): {}",sourceRemotePath,blob != null?"object":"directory");
+
+			        	if(blob != null && !blob.isDirectory()) {
+
+							//덮어쓰기가 아닌 경우, 존재여부 검사 
+							if(!isOverWrite && this.isExistObject(targetBucket, targetRemotePath)){
+								throw new SystemException("버킷("+targetBucket+") 타켓원격경로("+targetRemotePath+")에 동일이름의 파일이 존재 합니다.");
+							}
+							
+							//타켓원격경로가 "/" 로 끝나면 폴더로 인지하여 업로드 할 파일의 이름을 붙여서 원격경로에 저장할 이름을 만듬
+							//타켓원격경로가 존재하는 디렉토리면 이 디렉토리에 저장할 이름을 만듬
+							boolean isEnable=false;
+							if(!isEnable && (targetRemotePath.substring(targetRemotePath.length()-1).equals("/"))) {isEnable=true;}
+							if(!isEnable && (this.isExistObject(targetBucket, targetRemotePath) && this.isDirectory(targetBucket, targetRemotePath))) {isEnable=true;}
+							if(isEnable) {targetRemotePath = trimSeparator(targetRemotePath+"/"+extractFileName(sourceRemotePath));}
+														
+							BlobId source = BlobId.of(sourceBucket, sourceRemotePath);
+							BlobId target = BlobId.of(targetBucket, targetRemotePath);
+							
+							Storage.BlobTargetOption precondition;
+							blob = storage.get(targetBucket, targetRemotePath);
+							if(blob == null){
+								precondition = Storage.BlobTargetOption.doesNotExist();
+							}else{
+								precondition = Storage.BlobTargetOption.generationMatch(blob.getGeneration());
+							}
+	
+						    storage.copy(Storage.CopyRequest.newBuilder().setSource(source).setTarget(target, precondition).build());
+						    blob = storage.get(target);
+						    if(depth == 0) {
+						    	storage.get(source).delete();
+						    }
+
+						    List<Blob> list = new ArrayList<>();
+						    list.add(blob);
+						    
+						    return list;
+			        	}
+			        	
+						//소스원격경로(폴더) to 타켓원격경로(파일) 체크
+						if(!this.isDirectory(targetBucket, targetRemotePath)) {
+							throw new SystemException("폴더("+sourceRemotePath+")를 파일("+targetRemotePath+")로 이동 할 수 없습니다.");
+						}
+								        	
+			        	//소스원격경로(폴더) empty 체크
+						Page<Blob> sourcePage = storage.list(sourceBucket,Storage.BlobListOption.prefix(sourceRemotePath));
+						if(!sourcePage.iterateAll().iterator().hasNext()) {
+							log.debug(">> [move] sourceRemotePath({}) is empty.",sourceRemotePath);
+							return null;
+						}
+						
+						//타켓원격경로(폴더)로 이동
+						List<Blob> list = new ArrayList<>();
+						try{
+							if(!isOverWrite && depth == 0) {
+								Page<Blob> targetPage = storage.list(sourceBucket,Storage.BlobListOption.prefix(targetRemotePath));
+								if(targetPage.iterateAll().iterator().hasNext()) {
+									final Set<String> checkSet = new HashSet<>();
+									final String _sPath = sourceRemotePath;
+									final String _tPath = targetRemotePath;
+									targetPage.iterateAll().forEach(t->{
+										//log.debug(">> [move] target name:{}",t.getName());
+										checkSet.add(t.getName().replace(_tPath,_sPath));
+									});				
+									sourcePage.iterateAll().forEach(s->{
+										if(checkSet.contains(s.getName())) {
+											throw new SystemException("소스원격경로("+s.getName()+")와 타켓원격경로("+s.getName().replace(_sPath,_tPath)+")의 파일 이름이 동일합니다.");
+										}
+									});
+								}
+							}
+							
+							for(Blob item : sourcePage.iterateAll()){
+								String _sourceRemotePath = item.getName();
+								String _targetRemotePath = item.getName().replace(sourceRemotePath,targetRemotePath);
+								depth=1;
+								List<Blob> _list = this._move(sourceBucket, _sourceRemotePath, targetBucket, _targetRemotePath, isOverWrite, depth++);
+								list.addAll(_list);
+								depth--;
+							}
+						}catch(Exception e){
+							//오류시 이동 파일 삭제
+							if(list != null && !list.isEmpty()) {
+								list.forEach(i->{
+									log.debug(">> [move] 예외발생 이동된 파일 삭제:{}",i.getName());
+									this.delete(targetBucket,i.getName());
+								});
+							}
+							throw e;
+						}
+						
+						//소스원격경로(폴더) 객체 삭제
+						if(depth==1) {
+							for(Blob item : sourcePage.iterateAll()){
+								if(item.isDirectory()) {continue;}
+								this.delete(sourceBucket,item.getName());
+							}
+						}
+						
+					    return list;
+					}
+									
+					private List<Blob> _upload(String bucket, String storePath, String remotePath, UploadType uploadType, boolean isOverWrite) throws IOException{
+						if(StringUtils.isBlank(storePath)) {throw new SystemException("storePath("+storePath+") is empty.");}
+						
+						//업로드할 대상 존재여부 체크
+						File source = new File(FilenameUtils.normalize(storePath));				
+						if(!source.exists()) {log.debug(">> [upload] storePath({}) is not exist.",storePath); return null;}
+						
+						List<Blob> list = new ArrayList<>();
+
+						//업로드할 대상이 파일인 경우
+						if(source.isFile()) {
+							String _remotePath = trimSeparator(remotePath);
+							if(_remotePath.equals("")) {_remotePath=source.getName();}
+
+							//원격경로가 "/" 로 끝나면 폴더로 인지하여 업로드 할 파일의 이름을 붙여서 원격경로에 저장할 이름을 만듬
+							//원격경로가 존재하는 디렉토리면 이 디렉토리에 저장할 이름을 만듬
+							boolean isEnable=false;
+							if(!isEnable && (_remotePath.substring(_remotePath.length()-1).equals("/"))) {isEnable=true;}
+							if(!isEnable && (this.isExistObject(bucket, _remotePath) && this.isDirectory(bucket, _remotePath))) {isEnable=true;}
+							if(isEnable) {_remotePath = trimSeparator(_remotePath+"/"+source.getName());}
+							
+							//덮어쓰기가 아닌 경우, 존재여부 검사 
+							if(!isOverWrite && this.isExistObject(bucket, _remotePath)){
+								throw new SystemException("버킷("+bucket+") 원격경로("+_remotePath+")에 동일이름의 파일이 존재 합니다.");
+							}
+							
+							//blobInfo 구성
+							log.debug(">> [upload] remotePath: {}",_remotePath);
+							BlobId blobId = BlobId.of(bucket, _remotePath);
+							BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();			
+							
+							//precondition(createFrom수행시 사전체크 사항) 구성
+						    Storage.BlobWriteOption precondition;
+						    Blob blob = storage.get(bucket, _remotePath);
+						    if(blob == null){
+						    	//요청이 실행되기 전에 객체가 생성되면 요청이 실패
+						    	precondition = Storage.BlobWriteOption.doesNotExist();
+						    } else {
+						    	//요청이 실행되기 전에 기존 객체의 세대(generation)가 변경되면 요청이 실패, 다른 곳(process or thread)에서 변경여부 체크
+						    	precondition = Storage.BlobWriteOption.generationMatch(blob.getGeneration());
+						    }							
+
+							if(uploadType.equals(UploadType.buffer)) {				
+								list.add(storage.createFrom(blobInfo, Paths.get(storePath), precondition));
+							}
+
+							if(uploadType.equals(UploadType.stream)) {					
+								list.add(storage.createFrom(blobInfo, new FileInputStream(new File(storePath)), precondition));
+							}
+						}
+						
+						//업로드할 대상이 폴더인 경우						
+						if(source.isDirectory()) {
+							
+							if(!this.isDirectory(bucket, remotePath)) {
+								throw new SystemException("폴더("+storePath+")를 파일("+remotePath+")로 업로드 할 수 없습니다.");
+							}
+							
+							String _storePath = trimSeparator("/"+storePath);
+							String _basePath = trimSeparator(remotePath);					
+							String _remotePath = "";
+
+							for(File f : source.listFiles()) {
+								String _sourcePath = trimSeparator("/"+f.getPath());
+								if(f.isFile()) {_remotePath = _basePath + "/" + f.getName();}
+								if(f.isDirectory()) {_remotePath = _basePath + "/" + _sourcePath.replace(_storePath,"");}
+								_remotePath = trimSeparator(_remotePath);
+
+								if(!_remotePath.equals("")) {
+									try{
+										List<Blob> _list = this._upload(bucket, f.getPath(), _remotePath, uploadType, isOverWrite);
+										list.addAll(_list);
+									}catch(Exception e) {
+										if(list != null && !list.isEmpty()) {
+											list.forEach(i->{
+												log.debug(">> [upload] 예외발생 업로드된 파일 삭제:{}",i.getName());
+												delete(bucket,i.getName());
+											});
+										}
+										throw e;
+									}
+								}
+							}
+						}
+
+						return list;
+					}
+						
+					private List<BucketObjectInfo> _download(String bucket, String remotePath, String storePath, boolean isOverWrite) {
+
+						//다운로드 대상인 원격지경로가 파일인 경우 다운로드
+						Blob blob = storage.get(BlobId.of(bucket, trimSeparator(remotePath)));
+						log.debug(">> [download] blob({}): {}",remotePath,blob != null?"object":"directory");				
+			        	if(blob != null && !blob.isDirectory()) {
+			        		
+			        		String downloadStorePath = storePath;
+			        		
+			        		//디렉토리 경로 생성 후 다운로드 경로 구성
+			        		File target = new File(FilenameUtils.normalize(downloadStorePath));			        		
+			        		if(!target.exists()) {target.mkdirs();target.delete();}			        		
+			        		if(target.isDirectory()) {	        			
+			        			downloadStorePath = downloadStorePath + File.separator + extractFileName(blob.getName());
+			        			downloadStorePath = FilenameUtils.normalize(downloadStorePath);
+			        		}
+
+							//덮어쓰기가 아닌 경우, 존재여부 검사 
+							if(!isOverWrite && (new File(FilenameUtils.normalize(downloadStorePath))).exists()) {
+								throw new SystemException("다운로드경로("+downloadStorePath+")에 파일이 존재 합니다. (isOverWrite:"+isOverWrite+")");
+							}
+
+							//다운로드 수행
+						    blob.downloadTo(Paths.get(downloadStorePath));
+						    
+						    List<BucketObjectInfo> list = new ArrayList<>();
+						    list.add(this._convert(blob,downloadStorePath,System.currentTimeMillis()));
+						    
+							return list;    		
+			        	}
+			        	
+			        	//다운로드 대상인 원격지경로(폴더) empty 체크
+			        	String prefix = trimSeparator(remotePath);
+						Page<Blob> page = storage.list(bucket,Storage.BlobListOption.prefix(prefix));
+						if(!page.iterateAll().iterator().hasNext()) {
+							log.debug(">> [download] remotePath({}) is empty.",remotePath);
+							return null;
+						}
+						
+						//저장경로가 파일인 경우 체크 
+						File file = new File(FilenameUtils.normalize(storePath));
+						if(file.exists() && file.isFile()) {
+							throw new SystemException("저장경로("+storePath+")가 파일입니다. 원격지경로("+remotePath+")가 폴더인 경우 존재하는 파일경로를 지정 할 수 없습니다.");
+						}
+						
+						//다운로드 대상인 원격지경로가 폴더인 경우 다운로드
+						List<BucketObjectInfo> list = new ArrayList<>();												
+						try{
+							file.mkdirs();				
+							String baseStorePath = file.getPath();
+														
+					        for(Blob item : page.iterateAll()) {
+					        	if(item.isDirectory()) {continue;}
+					        	
+					        	//slash(/)로 끝나면 디렉토리이며, 디렉토리는 skip
+					        	String appendpath = item.getName().replace(remotePath,"");
+					        	if(appendpath.substring(appendpath.length()-1).equals("/")) {continue;}
+					        	
+					        	//다운로드저장경로 만들기
+					        	String downloadStorePath = FilenameUtils.normalize(baseStorePath+"/"+appendpath);
+					        	log.debug(">> [download] downloadStorePath:{}",downloadStorePath);
+					        	
+					        	//다운로드저장경로 디렉토리 구성
+					        	File _dirPath = new File(FilenameUtils.getFullPath(downloadStorePath));
+					        	_dirPath.mkdirs();
+					        	
+								//덮어쓰기가 아닌 경우, 존재여부 검사 
+								if(!isOverWrite && (new File(FilenameUtils.normalize(downloadStorePath))).exists()) {
+									throw new SystemException("저장경로에("+downloadStorePath+")에 동일이름 파일이 존재 합니다. (isOverWrite:\"+isOverWrite+\")");
+								}
+								
+								//다운로드 수행
+								blob = storage.get(BlobId.of(bucket, item.getName()));
+							    blob.downloadTo(Paths.get(downloadStorePath));
+							    
+							    list.add(this._convert(blob,downloadStorePath,System.currentTimeMillis()));
+					        }
+						
+						}catch(Exception e){
+							//오류시 저장된 파일 삭제
+							if(list != null && !list.isEmpty()) {
+								list.forEach(i->{
+									log.debug(">> [download] 예외발생 다운로드된 파일 삭제:{}",i.getName());
+									(new File(i.getDownloadStorePath())).delete();
+								});
+							}
+							throw e;
+						}
+						
+				        return list;
+					}
+						
+				};
+			}
+			
+			private String extractFileName(String path) {
+				path = path.trim();
+				String name = path.substring(path.lastIndexOf("\\")+1, path.length());
+				if(path.indexOf("/")>-1) {name = path.substring(path.lastIndexOf("/")+1, path.length());}
+				return name;
+			}
+		    
+		    private String trimSeparator(String path) {
+		    	path = path.trim();
+		    	if(path.indexOf("\\") == 0) {path = path.replace("\\","/");}
+				if(path.indexOf("/") == 0) {path = this.trimSeparator(path.substring(1));}
+				while(path.indexOf("//")>0) {path = path.replace("//","/");}				
+				return path;
+			}	
+		    
+		    private Map<String,String> getSystemInfo() {
+		    	if(this.systemInfo == null) {
+			    	SystemUtil systemUtil = beanAid.getBean(SystemUtil.class);
+			    	this.systemInfo=systemUtil.getSystemInfo();
+		    	}
+		    	return this.systemInfo;
+		    }
+
+		};
+		
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 337 - 0
src/main/java/kr/co/iya/base/support/util/HttpRequestUtilPack.java

@@ -0,0 +1,337 @@
+package kr.co.iya.base.support.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import kr.co.iya.base.support.aid.RequestAttributesAidPack.RequestAttributesAid;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class HttpRequestUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public enum DeviceType {mobile,mobileCS,pc,jvm};
+	public enum OsType {windows,linux,macintosh,ios,android,java,etc};
+	public enum BrowserType {ie,edge,chrome,kakaotalk,opera,firefox,safari,feign,etc};
+
+	public static class ClientInfo {
+		private String userAgent;
+		
+		private String ip = null;
+
+		private DeviceType deviceType;
+		private OsType osType;
+		private BrowserType browserType;
+		
+		private boolean isMobileCS;
+		private boolean isFeignRequest;
+		
+		public ClientInfo() {};
+
+		public ClientInfo(String ip, String userAgent, DeviceType deviceType, OsType osType, BrowserType browserType, boolean isMobileCS,boolean isFeignRequest) {
+			this.ip = ip;
+			this.userAgent = userAgent;
+			this.deviceType = deviceType;
+			this.osType = osType;
+			this.browserType = browserType;
+			this.isMobileCS = isMobileCS;
+			this.isFeignRequest = isFeignRequest;
+		}
+
+		public String getIp() {return ip;}
+		public String getUserAgent() {return userAgent;}
+		public DeviceType getDeviceType() {return deviceType;}
+		public OsType getOsType() {return osType;}
+		public BrowserType getBrowserType() {return browserType;}	
+		public boolean isMobileCS() {return isMobileCS;}
+		public boolean isFeignRequest() {return isFeignRequest;}
+
+		@Override
+		public String toString() {
+			return "ClientInfo [userAgent=" + userAgent + ", ip=" + ip + ", deviceType=" + deviceType + ", osType="
+					+ osType + ", browserType=" + browserType + ", isMobileCS=" + isMobileCS + ", isFeignRequest="
+					+ isFeignRequest + "]";
+		}
+	}
+
+	public static class RefererInfo {
+		private String rawData;
+		private String protocol;
+		private String domain;
+		private int port;
+		private String path;
+		private String query;
+    	
+		public RefererInfo(String rawData, String protocol, String domain, int port, String path, String query) {
+			this.rawData = rawData;
+			this.protocol = protocol;
+			this.domain = domain;			
+			this.port = port;
+			this.path = path;
+			this.query = query;
+		}
+		
+		public String getRawData() {return rawData;}
+		public String getProtocol() {return protocol;}
+		public String getDomain() {return domain;}
+		public int getPort() {return port;}
+		public String getPath() {return path;}
+		public String getQuery() {return query;}
+
+		@Override
+		public String toString() {
+			return "Referer [rawData=" + rawData + ", protocol=" + protocol + ", domain=" + domain + ", port=" + port
+					+ ", path=" + path + ", query=" + query + "]";
+		}
+
+    }
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface HttpRequestUtil {
+		
+		public HttpServletRequest getHttpServletRequest();
+		
+		public ServletContext getServletContext();
+			
+		public String getForwardView(String view);
+		public String getRedirectView(String view);
+
+		public String getParamThrouSession(String param,HttpServletRequest request);
+		public List<String> getRequestMappingUrl(ApplicationContext cxt);	
+		public boolean isRequestPathCheck(HttpServletRequest request,List<String> urlPattrenList);
+		public String getCookieValue(HttpServletRequest request,String cookieName);
+		
+		public String getRequestPageURI(HttpServletRequest request);			
+		public String getDigestPageURI(HttpServletRequest request,String pageURI);
+
+		public String getRemoteAddress();
+		public String getRemoteAddress(HttpServletRequest request);
+		
+		public RefererInfo getRefererInfo(HttpServletRequest request);
+		public ClientInfo getClientInfo(HttpServletRequest request);
+		
+		public OsType getOsType(HttpServletRequest request);
+		public DeviceType getDeviceType(HttpServletRequest request);
+		public BrowserType getBrowserType(HttpServletRequest request);
+		
+		//public Device getCurrentDevice(HttpServletRequest request);			
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static HttpRequestUtil getHttpRequestUtil(RequestAttributesAid requestAttributesAid) {
+		if(requestAttributesAid==null) {log.debug(">> param[requestAttributesAid] is null."); return null;}
+		
+		return new HttpRequestUtil() {
+
+			@Override
+			public HttpServletRequest getHttpServletRequest() {
+				return requestAttributesAid.getHttpServletRequest();			
+			}
+			
+			@Override
+			public ServletContext getServletContext() {
+				return requestAttributesAid.getServletContext();
+			}
+
+			@Override	
+			public String getForwardView(String view) {
+				return "forward:"+view;
+			}
+			
+			@Override		
+			public String getRedirectView(String view) {
+				return "redirect:"+view;
+			}
+
+			@Override
+			public String getParamThrouSession(String param, HttpServletRequest request) {
+		        String value=request.getParameter(param);
+		        HttpSession session = requestAttributesAid.getHttpServletRequest().getSession();
+		        if(value==null || value.equals("")) {value=(session.getAttribute(param)==null?"":session.getAttribute(param).toString());}        
+		        if(value!=null && !value.equals("")) {session.setAttribute(param,value);}        
+		        return value;   
+			}
+
+			@Override
+			public List<String> getRequestMappingUrl(ApplicationContext cxt) {
+				RequestMappingHandlerMapping mapping=cxt.getBean(RequestMappingHandlerMapping.class);		
+				Map<RequestMappingInfo,HandlerMethod> map = mapping.getHandlerMethods();	
+				
+				List<String> list = new ArrayList<>();		
+				for(RequestMappingInfo key : map.keySet()) {
+					list.addAll(key.getPatternsCondition().getPatterns());
+				}	
+				return list;
+			}
+
+			@Override
+			public boolean isRequestPathCheck(HttpServletRequest request, List<String> urlPattrenList) {
+				if(urlPattrenList==null) {return false;}
+				
+				AntPathMatcher antPathMatcher=new AntPathMatcher();
+				String context=(request.getContextPath().equals("/")?request.getContextPath():request.getContextPath()+"/");
+				String checkPath=request.getRequestURI();
+				if(checkPath.lastIndexOf(";")!=-1) {checkPath=checkPath.substring(0, checkPath.indexOf(";"));}
+				for(int i=0;i<urlPattrenList.size();i++) {
+					if(urlPattrenList.get(i)!=null && !urlPattrenList.get(i).equals("")) {
+						String pattern=context+(urlPattrenList.get(i).equals("/")?"":urlPattrenList.get(i));
+						if(antPathMatcher.match(pattern,checkPath)) {return true;}
+					}
+				}
+				return false;
+			}
+
+			@Override
+			public String getCookieValue(HttpServletRequest request, String cookieName) {
+				String cookieValue="";
+				Cookie[] cookies=request.getCookies();
+				if(cookies != null && cookies.length>0) {
+				    for(int i=0;i<cookies.length;i++) {
+				    	if(cookies[i].getName().equals(cookieName)) {
+				    		cookieValue=cookies[i].getValue();
+				    		break;
+				    	}
+				    }
+				}
+				return cookieValue;
+			}
+
+			@Override
+			public String getRequestPageURI(HttpServletRequest request) {
+				return this.getDigestPageURI(request,request.getRequestURI());
+			}
+
+			@Override
+			public String getDigestPageURI(HttpServletRequest request,String pageURI) {
+				if(pageURI==null || pageURI.equals("")) {return pageURI;}
+				
+				String requestUri=pageURI;
+				if(requestUri.startsWith(request.getContextPath())) {
+					requestUri=requestUri.substring(request.getContextPath().length(),requestUri.length());
+				}
+				return requestUri;
+			}
+
+			@Override
+			public String getRemoteAddress() {
+				return this.getRemoteAddress(requestAttributesAid.getHttpServletRequest());
+			}
+
+			@Override
+			public String getRemoteAddress(HttpServletRequest request) {
+		        String ip = request.getHeader("X-Forwarded-For");
+		        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}  
+		        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}  
+		        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}  
+		        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}  
+		        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}		        
+		        if(ip.indexOf(",")>-1) {ip=ip.split(",")[0];}
+		        
+				return ip;
+			}
+			
+			@Override			
+			public RefererInfo getRefererInfo(HttpServletRequest request) {
+				String rawData = request.getHeader("referer");
+				if(rawData==null || rawData.equals("")) {return null;}
+
+				String protocol = rawData.substring(0,rawData.indexOf("://"));
+
+				String temp = rawData.substring(rawData.indexOf("://")+3);
+				temp = temp.indexOf("/")>-1?temp.substring(0,temp.indexOf("/")):temp;
+				String domain=temp.indexOf(":")>-1?domain=temp.substring(0,temp.indexOf(":")):temp;
+
+				int port=temp.indexOf(":")>-1?port=Integer.parseInt(temp.substring(temp.indexOf(":")+1)):(protocol.equals("https")?443:80);
+
+				String path = rawData.substring(rawData.indexOf("://")+3);
+				path = path.indexOf("/")>-1?path.substring(path.indexOf("/")):"/";
+				if(path!=null && !path.equals("") && path.indexOf("?")>-1) {path=path.substring(0,path.indexOf("?"));}		
+
+				String query = rawData.indexOf("?")>-1?rawData.substring(rawData.indexOf("?")+1):null;
+
+				return  new RefererInfo(rawData,protocol,domain,port,path,query);	
+			}
+			
+			@Override			
+			public ClientInfo getClientInfo(HttpServletRequest request) {
+				String userAgent = request.getHeader("User-Agent");
+				log.debug(">> userAgent:{}",userAgent);
+
+				DeviceType  device = null;
+				if (userAgent == null) {device=null;}
+				else if(userAgent.indexOf("Mobile")>-1 && userAgent.indexOf("MobileCS")!=-1) {device=DeviceType.mobileCS;}
+				else if(userAgent.indexOf("Mobile")>-1 && userAgent.indexOf("MobileCS")==-1) {device=DeviceType.mobile;}				
+				else if(userAgent.indexOf("Java")>-1){device = DeviceType.jvm;} //feign 서버간호출
+				else {device = DeviceType.pc;}		
+
+				OsType osType = null;				
+				if (userAgent == null) {userAgent=null;}
+				else if(userAgent.indexOf("Windows")>-1) {osType = OsType.windows;}
+				else if(userAgent.indexOf("Linux")>-1) {osType = OsType.linux;}
+				else if(userAgent.indexOf("Macintosh")>-1) {osType = OsType.macintosh;}
+				else if(userAgent.indexOf("iPhone")>-1) {osType = OsType.ios;}
+				else if(userAgent.indexOf("iPad")>-1) {osType = OsType.ios;}
+				else if(userAgent.indexOf("Java")>-1) {osType = OsType.java;} //feign 서버간호출
+				else {osType = OsType.etc;}
+
+				BrowserType browser = null;
+				if (userAgent == null) {browser=null;}
+				else if(userAgent.indexOf("Trident")>-1) {browser=BrowserType.ie;}
+				else if(userAgent.indexOf("Edge")>-1) {browser=BrowserType.edge;}
+				else if(userAgent.indexOf("Firefox")>-1) {browser=BrowserType.firefox;}
+				else if(userAgent.indexOf("KAKAOTALK")>-1) {browser=BrowserType.kakaotalk;}
+				else if(userAgent.indexOf("Chrome")>-1) {
+					if(userAgent.indexOf("Opera")>-1 || userAgent.indexOf("OPR")>-1) {browser=BrowserType.opera;}					
+					else {browser=BrowserType.chrome;}
+				}
+				else if(userAgent.indexOf("Safari")>-1 && userAgent.indexOf("Chrome")==-1) {browser=BrowserType.safari;}
+
+				boolean isMobileCS = userAgent.indexOf("MobileCS")>-1?true:false;
+
+				ClientInfo clientInfo = new ClientInfo(this.getRemoteAddress(request),userAgent,device,osType,browser,isMobileCS,false);
+				log.debug(">> clientInfo:{}",clientInfo.toString());
+				
+				return clientInfo;
+			}
+
+			@Override			
+			public OsType getOsType(HttpServletRequest request) {
+				return this.getClientInfo(request).getOsType();	
+			}
+			
+			@Override			
+			public BrowserType getBrowserType(HttpServletRequest request) {
+				return this.getClientInfo(request).getBrowserType();
+			}
+			
+			@Override			
+			public DeviceType getDeviceType(HttpServletRequest request) {
+				return this.getClientInfo(request).getDeviceType();			
+			}
+
+			//@Override
+			//public Device getCurrentDevice(HttpServletRequest request) {
+			//	return DeviceUtils.getCurrentDevice(request);
+			//}
+			
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 307 - 0
src/main/java/kr/co/iya/base/support/util/JsonUtilPack.java

@@ -0,0 +1,307 @@
+package kr.co.iya.base.support.util;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.google.json.JsonSanitizer;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class JsonUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface JsonUtil {
+
+		public static final String JSON_ROOT_KEY="json";
+
+		public Gson getGson();
+		public String toJson(Object obj);
+		public <T> T fromJson(String json, Class<T> type);
+		public <T> T fromJson(String json, Type typeOfT);
+
+		public boolean isJsonObject(Object object);
+		public JSONObject getJsonObject(Map<String,Object> map);
+		public JSONObject getJsonObject(String jsonString);
+
+		public boolean isJsonArray(Object object);
+		public JSONArray getJsonArray(List<Map<String, Object>> list);
+		public JSONArray getJsonArray(String jsonString);
+
+		public String getJsonString(Map<String,Object> map);
+		public String getJsonString(List<Map<String, Object>> list);
+
+		public Map<String,Object> getMap(Object obj);
+		public Map<String,Object> getMap(JSONObject jsonObject);
+	    public Map<String,Object> getMap(String jsonString);
+
+	    public List<Map<String,Object>> getList(Object obj);
+	    public List<Map<String,Object>> getList(JSONArray jsonArray);
+	    public List<Map<String,Object>> getList(String jsonString);	
+
+		public Map<String,String> getSerialMap(Object object);
+		public Map<String,String> getSerialMap(String jsonRootKey,Object object);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static JsonUtil getJsonUtil(boolean isLog) {
+		
+		return new JsonUtil() {
+			
+			@Override
+			public Gson getGson() {
+				return new GsonBuilder().disableHtmlEscaping().setLenient().create();
+			}
+
+			@Override
+			public String toJson(Object obj) {
+				return this.getGson().toJson(obj);
+			}
+			
+			@Override
+			public <T> T fromJson(String json, Class<T> type) {
+				return this.getGson().fromJson(JsonSanitizer.sanitize(json), type);
+			}
+			
+			@Override
+			public <T> T fromJson(String json, Type typeOfT) {
+				return this.getGson().fromJson(json, typeOfT);
+			}
+
+			@Override
+			public boolean isJsonObject(Object object) {
+				boolean isEnable=false;
+				try {
+					if(!isEnable && object instanceof org.json.simple.JSONObject) {isEnable=true;}
+					if(!isEnable && object instanceof String) {isEnable=this.isJsonObject(new JSONParser().parse(String.valueOf(object)));}
+				}catch (Exception e) {
+					//if(isLog) {log.debug("Not parsable: {}",object);}
+				}
+				return isEnable;
+			}
+
+			@Override
+			public boolean isJsonArray(Object object) {
+				boolean isEnable=false;
+				try {
+					if(!isEnable && object instanceof org.json.simple.JSONArray) {isEnable=true;}
+					if(!isEnable && object instanceof String) {isEnable=this.isJsonArray(new JSONParser().parse(String.valueOf(object)));}
+				}catch (Exception e) {
+					//if(isLog) {log.debug("Not parsable: {}",object);}
+				}
+				return isEnable;
+			}
+
+			@Override
+			public JSONObject getJsonObject(Map<String,Object> map) {
+				return new JSONObject(map);		
+			}
+
+			@Override
+			public JSONObject getJsonObject(String jsonString) {
+				if(!this.isJsonObject(jsonString)) {return null;}
+				JSONObject jsonObject=null;
+				try {
+					JSONParser parser=new JSONParser();
+					jsonObject=(JSONObject)parser.parse(jsonString);
+				} catch (ParseException e) {
+					if(isLog) {log.debug("Not parsable: {}",jsonString);}
+				}
+				return jsonObject;
+			}
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public JSONArray getJsonArray(List<Map<String, Object>> list) {
+				JSONArray jsonArray=new JSONArray();
+				for(Map<String, Object> map : list ) {
+					jsonArray.add(this.getJsonObject(map));
+				}
+				return jsonArray;
+			}
+			
+			@Override	
+			public JSONArray getJsonArray(String jsonString) {
+				if(!this.isJsonArray(jsonString)) {return null;}
+				JSONArray jsonArray=null;
+				try {
+					JSONParser parser=new JSONParser();
+					jsonArray=(JSONArray)parser.parse(jsonString);
+				} catch (ParseException e) {
+					if(isLog) {log.debug("Not parsable: {}",jsonString);}
+				}
+				return jsonArray;
+			}
+
+			@Override
+			public String getJsonString(Map<String, Object> map) {
+				return this.getJsonObject(map).toJSONString();
+			}
+
+			@Override
+			public String getJsonString(List<Map<String, Object>> list) {
+				return this.getJsonArray(list).toJSONString();
+			}
+
+			@Override
+			public Map<String,Object> getMap(Object obj) {
+				return this.getMap(this.toJson(obj));
+			}
+			
+			@Override
+			public Map<String, Object> getMap(JSONObject jsonObject) {
+				Map<String,Object> map=null;
+				try {
+					//To prevent server-side JSON injections, sanitize all data before serializing it to JSON
+					String wellFormedJson = JsonSanitizer.sanitize(jsonObject.toJSONString());
+					Type type = new TypeToken<Map<String,Object>>() {}.getType();
+					map=this.getGson().fromJson(wellFormedJson,type);
+				} catch (Exception e) {
+					if(isLog) {log.debug("Not convertable: {}",jsonObject.toJSONString());}
+				}
+		        return map;
+			}
+			
+		    @Override
+		    public Map<String,Object> getMap(String jsonString) {
+		        if(jsonString==null || jsonString.equals("")) {return null;}
+		        String modifiedJsonString=jsonString;
+
+		        //if(isLog) {log.debug("try1: {}",modifiedJsonString);}
+		        if(this.isJsonObject(modifiedJsonString)) {
+		            return this.getMap(this.getJsonObject(modifiedJsonString));            
+		        }
+		        
+		        modifiedJsonString=modifiedJsonString.replaceAll("'","\"");
+		        //if(isLog) {log.debug("try2: {}",modifiedJsonString);}
+		        if(this.isJsonObject(modifiedJsonString)) {
+		            return this.getMap(this.getJsonObject(modifiedJsonString));  
+		        }
+
+		        modifiedJsonString=modifiedJsonString.replaceAll("\"",""); 
+		        //modifiedJsonString=modifiedJsonString.replaceAll("\\ {","\\ {\"");
+		        modifiedJsonString=modifiedJsonString.replaceAll(":","\":\"");
+		        modifiedJsonString=modifiedJsonString.replaceAll(",","\",\"");
+		        modifiedJsonString=modifiedJsonString.replaceAll("\\}","\"\\}");
+		        
+		        //if(isLog) {log.debug("try3: {}",modifiedJsonString);}
+		        if(this.isJsonObject(modifiedJsonString)) {
+		            return this.getMap(this.getJsonObject(modifiedJsonString));  
+		        }
+
+		        return null;
+		    }
+		    
+			@Override
+		    public List<Map<String,Object>> getList(Object obj){
+		    	return this.getList(this.toJson(obj));
+		    }
+		    
+			@Override
+			public List<Map<String, Object>> getList(JSONArray jsonArray) {
+				List<Map<String, Object>> list=new ArrayList<>();
+				if(jsonArray!=null) {
+					for(int i=0;i<jsonArray.size();i++) {
+						Map<String, Object> map=this.getMap((JSONObject)jsonArray.get(i));
+						list.add(map);
+					}
+				}
+				return list;
+			}	
+			
+			@Override
+			public List<Map<String,Object>> getList(String jsonString) {
+				List<Map<String,Object>> list=null;
+				try {
+					TypeToken<List<Map<String,Object>>> tokenType=new TypeToken<List<Map<String,Object>>>() {};			
+					list=this.getGson().fromJson(jsonString,tokenType.getType());
+				} catch (Exception e) {
+					if(isLog) {log.debug("Not convertable: {}",jsonString);}
+				}
+		        return list;
+			}
+			
+	
+			@Override
+			public Map<String,String> getSerialMap(Object object) {	
+				return this.getSerialMap(JSON_ROOT_KEY,object);
+			}
+			
+			@Override
+			public Map<String,String> getSerialMap(String jsonRootKey,Object object) {
+				Object parseObj = object;
+				if(object instanceof String) {
+					try {
+						parseObj = new JSONParser().parse((String)object);
+					} catch (ParseException e) {
+						if(isLog) {log.debug("Not parsable: {}",object);}
+					}
+				}
+				return this.makeSerialMap(jsonRootKey,parseObj);
+			}
+			
+			private Map<String,String> makeSerialMap(String baseKey,Object object) {
+				Map<String,String> serialMap=new HashMap<>();
+				
+				if(this.isJsonObject(object)) {
+					//log.debug(">> isJsonObject");
+					JSONObject jsonObject=(JSONObject)object;
+					Iterator<?> keys=jsonObject.keySet().iterator();
+					while(keys.hasNext()) {
+						String key=(String)keys.next();
+						
+						String mainKey=baseKey+"."+key;
+						Object value=jsonObject.get(key);
+						if(value instanceof String) {
+							//log.debug(">>>> Object String {}:{}",mainKey,(String)value);
+							serialMap.put(mainKey,(String)value);
+						} else {
+							//log.debug(">>>> Object Object {}:{}",mainKey,(value==null?"":value.toString()));
+							serialMap.put(mainKey,(value==null?"":value.toString()));
+							serialMap.putAll(this.makeSerialMap(mainKey,value));
+						}
+					}
+				}else if(this.isJsonArray(object)) {
+					//log.debug(">> isJsonArray");
+					JSONArray jsonArray=(JSONArray)object;
+					for(int i=0;i<jsonArray.size();i++) {
+						Object value=jsonArray.get(i);
+						String mainKey=baseKey+"["+String.valueOf(i)+"]";
+						if(value instanceof String) {
+							//log.debug(">>>> Array String {}:{}",mainKey,(String)value);
+							serialMap.put(mainKey,(String)value);
+						} else {
+							//log.debug(">>>> Array Object {}:{}",mainKey,value);
+							serialMap.put(mainKey,value.toString());
+							serialMap.putAll(this.makeSerialMap(mainKey,value));
+						}
+					}	
+				}else{
+					//log.debug(">> isString");
+					//log.debug(">>>> String String {}:{}",baseKey,(object==null?"":object.toString()));
+					serialMap.put(baseKey,(object==null?"":object.toString()));
+				}
+				
+				return serialMap;
+			}
+			
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 156 - 0
src/main/java/kr/co/iya/base/support/util/KafkaUtilPack.java

@@ -0,0 +1,156 @@
+package kr.co.iya.base.support.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.kafka.common.header.Headers;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.MessageHeaders;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import kr.co.iya.base.config.KafkaConfig.KafkaProperties;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import kr.co.iya.base.support.util.ObjectMapperUtilPack.ObjectMapperUtil;
+import kr.co.iya.base.support.util.SystemUtilPack.SystemUtil;
+import kr.co.iya.base.support.util.TimeUtilPack.TimeUtil;
+import lombok.Builder;
+import lombok.Data;
+
+//@Slf4j
+public class KafkaUtilPack {
+	
+	public interface KafkaUtil {
+		
+		public static final String OFFSET = KafkaHeaders.OFFSET;
+		public static final String CONSUMER = KafkaHeaders.CONSUMER;
+		public static final String TIMESTAMP_TYPE = KafkaHeaders.TIMESTAMP_TYPE;
+		public static final String RECEIVED_TOPIC = KafkaHeaders.RECEIVED_TOPIC;
+		public static final String RECEIVED_PARTITION = KafkaHeaders.RECEIVED_PARTITION;
+		public static final String RECEIVED_KEY = KafkaHeaders.RECEIVED_KEY;		
+		public static final String RECEIVED_TIMESTAMP = KafkaHeaders.RECEIVED_TIMESTAMP;
+
+		public static final String KAFKA_PREFIX = "kafka_";
+		public static final String DEFAULT_PREFIX = "x-utcb-";
+		public static final String DEFAULT_HEADER_KEY_UUID = DEFAULT_PREFIX+"uuid";	
+		public static final String DEFAULT_HEADER_KEY_SERVICE_NAME = DEFAULT_PREFIX+"publisherService";
+		public static final String DEFAULT_HEADER_KEY_SERVER_IP = DEFAULT_PREFIX+"publisherServerIp";	
+        
+		public String getBootstrapServers();
+		
+		public Map<String,String> buildHeader();	
+		public Map<String,String> addDefault(Map<String,String> header);		
+		public Map<String,String> convertMap(Headers headers);
+		public Map<String,String> convertMap(MessageHeaders headers);
+		public String extract(Headers headers,String key);
+		public String extract(MessageHeaders headers,String key);
+		public String convertFormatDate(long timestamp);
+
+		public String toJson(Object payload);
+		public <T> T readValue(String payload, Class<T> type);
+		public <T> T readValue(String content, TypeReference<T> valueTypeRef);		
+	}
+
+	@Data @Builder
+	public static class KafkaMeta {
+		private String applicationName;
+		private KafkaProperties properties;
+		private ObjectMapperUtil objectMapperUtil;
+		private JsonUtil jsonUtil;
+		private TimeUtil timeUtil;
+		private SystemUtil systemUtil;
+	}
+	
+	public static KafkaUtil getKafkaUtil(KafkaMeta meta) {
+		
+		return new KafkaUtil() {
+
+			@Override
+			public String getBootstrapServers() {return meta.getProperties().getBootstrapServers();}
+			
+			@Override
+			public Map<String, String> buildHeader() {
+				Map<String,String> map = new HashMap<>();
+				map.put(DEFAULT_HEADER_KEY_UUID, UUID.randomUUID().toString());
+				map.put(DEFAULT_HEADER_KEY_SERVICE_NAME, meta.getApplicationName());
+				map.put(DEFAULT_HEADER_KEY_SERVER_IP, meta.getSystemUtil().getServerIp());				
+				return map;
+			}
+						
+			@Override
+			public Map<String,String> addDefault(Map<String,String> header){
+				if(header==null) {return this.buildHeader();}
+				Map<String,String> map = new HashMap<>(header);	
+				for(String key:map.keySet()) {
+					if(key.equals(DEFAULT_HEADER_KEY_UUID)||key.equals(DEFAULT_HEADER_KEY_SERVICE_NAME)||key.equals(DEFAULT_HEADER_KEY_SERVER_IP)) {continue;}
+					if(key.startsWith(KAFKA_PREFIX) || key.startsWith(DEFAULT_PREFIX)) {throw new RuntimeException("header key can't start with ["+KAFKA_PREFIX+","+DEFAULT_PREFIX+"]");}
+				}			
+				if(!header.containsKey(DEFAULT_HEADER_KEY_UUID)) {map.put(DEFAULT_HEADER_KEY_UUID, UUID.randomUUID().toString());}
+				if(!header.containsKey(DEFAULT_HEADER_KEY_SERVICE_NAME)) {map.put(DEFAULT_HEADER_KEY_SERVICE_NAME, meta.getApplicationName());}
+				if(!header.containsKey(DEFAULT_HEADER_KEY_SERVER_IP)) {map.put(DEFAULT_HEADER_KEY_SERVER_IP, meta.getSystemUtil().getServerIp());}
+				return header;
+			}	
+
+			@Override
+			public Map<String, String> convertMap(Headers headers) {
+				if(headers==null) {return null;}
+                Map<String,String> map = new HashMap<>();                
+                headers.forEach(h->map.put(h.key(),new String(h.value())));
+				return map;
+			}
+			
+			@Override
+			public Map<String,String> convertMap(MessageHeaders headers){
+				if(headers==null) {return null;}
+		        Map<String,String> map = new HashMap<>();
+		        headers.keySet().forEach(key->{
+		        	Object value = headers.get(key);
+		        	map.put(key, key.startsWith(KAFKA_PREFIX)?String.valueOf(value):new String((byte[])value));		        	
+		        });		        
+				return map;				
+			}
+			
+			@Override
+			public String extract(Headers headers,String key) {
+				if(headers==null) {return null;}
+				try{headers.forEach(h->{if(h.key().equals(key)) {throw new RuntimeException(new String((byte[])h.value()));}});}
+				catch(Exception e) {return e.getMessage();}
+				return null;
+			}
+			
+			@Override
+			public String extract(MessageHeaders headers,String key) {
+				boolean isEnable=true;
+				if(isEnable && headers==null) {isEnable=false;}
+				if(isEnable && (key==null || key.equals(""))) {isEnable=false;}	
+				if(isEnable && !headers.containsKey(key)) {isEnable=false;}
+				return isEnable?new String(headers.get(key,byte[].class)):null;
+			}
+
+			@Override 
+			public String convertFormatDate(long timestamp) {return meta.getTimeUtil().getFormatDate(timestamp,"yyyy-MM-dd HH:mm:ss.SSS");}
+
+			@Override
+			public String toJson(Object obj) {
+				return meta.getJsonUtil().toJson(obj);
+			}
+
+			@Override
+			public <T> T readValue(String payload, Class<T> type) {
+				try{return meta.getObjectMapperUtil().getMapper().readValue(payload, type);}
+				catch(Exception e){throw new RuntimeException(e.getCause());}
+			}
+
+			@Override
+			public <T> T readValue(String payload,TypeReference<T> valueTypeRef) {
+				try{return meta.getObjectMapperUtil().getMapper().readValue(payload, valueTypeRef);}
+				catch(Exception e){throw new RuntimeException(e.getCause());}
+			}
+		};
+	};
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	
+}

+ 176 - 0
src/main/java/kr/co/iya/base/support/util/LobConverterUtilPack.java

@@ -0,0 +1,176 @@
+package kr.co.iya.base.support.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class LobConverterUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface LobConverterUtil {
+
+		public String doClobToString(Object object);
+		public String doClobToString(Clob clob);
+	    public void doClobToFileRender(Clob clob,String attachFileName,HttpServletRequest request,HttpServletResponse response) throws Exception;
+
+	    public byte[] doBlobToBytes(Object object);
+		public byte[] doBlobToBytes(Blob blob);
+	    public void doBlobToFileRender(Blob blob,String attachFileName,HttpServletRequest request,HttpServletResponse response) throws Exception;
+
+	    public Map<String,String> doGetMethodToMap(Object object);		
+		public Object getSpelParsingValue(Object rootObj,String spel);	
+		public Object getExtractValue(Object object,String refString);	
+	}	
+	
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static LobConverterUtil getLobConverterUtil(FileUtil fileUtil) {
+		if(fileUtil==null) {log.debug(">> param[fileUtil] is null."); return null;}
+		
+		return new LobConverterUtil() {
+
+			@Override
+			public String doClobToString(Object object) {
+				Clob clob=null;
+				if(object instanceof Clob) {clob=(Clob)object;}
+				if(clob==null) {return "";}		
+				return this.doClobToString(clob);
+			}
+
+			@Override
+			public String doClobToString(Clob clob) {
+				if(clob==null) {return "";}
+				
+				StringBuilder outBuf=new StringBuilder();
+				BufferedReader bufReader=null;
+				try {		
+					String readLine="";
+					bufReader=new BufferedReader(clob.getCharacterStream());
+					while((readLine=bufReader.readLine())!=null) {outBuf.append(readLine).append("\n");}
+				}catch(Exception e) {
+					log.debug(">> exception skip: {}",e.getMessage());
+					//e.printStackTrace();
+				}finally {
+					try {
+						if(bufReader!=null) {bufReader.close();}
+					}catch(IOException e) {
+						log.debug(">> exception skip: {}",e.getMessage());
+						//e.printStackTrace();
+					}
+				}
+
+				return outBuf.toString();
+			}
+
+			@Override
+		    public void doClobToFileRender(Clob clob,String attachFileName,HttpServletRequest request,HttpServletResponse response) throws Exception {
+			    fileUtil.pushout(clob, attachFileName, request, response);
+		    }
+
+			@Override	
+			public byte[] doBlobToBytes(Object object) {
+				Blob blob=null;
+				if(object instanceof Blob) {blob=(Blob)object;}
+				if(blob==null) {return null;}
+				return this.doBlobToBytes(blob);
+			}
+			
+			@Override	
+			public byte[] doBlobToBytes(Blob blob) {
+				if(blob==null) {return null;}
+				
+				 BufferedInputStream bufInputStream=null;
+				 byte[] bytes=null;
+				try {
+					bufInputStream=new BufferedInputStream(blob.getBinaryStream());
+			        int blobSize=(int)blob.length();
+			        bytes=new byte[blobSize];    
+			        bufInputStream.read(bytes,0,blobSize);
+				}catch(Exception e) {
+					log.debug(">> exception skip: {}",e.getMessage());
+					//e.printStackTrace();
+				}finally {
+					try {
+						if(bufInputStream!=null) {bufInputStream.close();}
+					}catch(IOException e) {
+						log.debug(">> exception skip: {}",e.getMessage());
+						//e.printStackTrace();
+					}
+				}
+				return bytes;
+			}
+
+		   @Override
+		    public void doBlobToFileRender(Blob blob,String attachFileName,HttpServletRequest request,HttpServletResponse response) throws Exception {
+		       fileUtil.pushout(blob, attachFileName, request, response);
+		    }
+
+			@Override
+			public Map<String,String> doGetMethodToMap(Object object) {
+				if(object==null) {return null;}
+
+		        Map<String,String> map=new HashMap<>();
+
+		        Method[] methods=object.getClass().getDeclaredMethods();
+		        for(Method method:methods) {
+		        	if(!method.getName().startsWith("get")) {continue;}
+
+		        	String value=null;  
+		            try {
+		            	Object methodInvokeOject=object.getClass().getDeclaredMethod(method.getName()).invoke(object);
+		        		if(value==null && method.getReturnType().isPrimitive()) {value=String.valueOf(methodInvokeOject);}
+		        		if(value==null && method.getReturnType().toString().indexOf("java.lang.String")!=-1) {value=(methodInvokeOject==null?"":methodInvokeOject.toString());}
+		        		if(value==null && methodInvokeOject!=null) {
+		            		Map<String,String> mapSub=this.doGetMethodToMap(methodInvokeOject);
+		            		if(mapSub!=null) {value=mapSub.toString();}
+		        		}
+		            }catch(Exception e) {}
+		            map.put(method.getName(),value);
+		        }
+
+		        return map;
+			}
+
+			@Override
+			public Object getSpelParsingValue(Object rootObj,String spel) {
+				ExpressionParser parser=new SpelExpressionParser();
+				Expression exp=parser.parseExpression(spel);
+				EvaluationContext context=new StandardEvaluationContext(rootObj);		
+				return exp.getValue(context);
+			}
+			
+			@Override	
+			public Object getExtractValue(Object object,String refString) {
+				if(object==null) {return null;}
+				if(refString==null || refString.equals("")) {return null;}
+				if(refString.indexOf(".")==-1) {return this.getSpelParsingValue(object,refString);}
+				Object objectChild=this.getSpelParsingValue(object,refString.substring(0,refString.indexOf(".")));
+				return this.getExtractValue(objectChild,refString.substring(refString.indexOf(".")+1,refString.length()));
+			}
+			
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+}

+ 59 - 0
src/main/java/kr/co/iya/base/support/util/LocaleUtilPack.java

@@ -0,0 +1,59 @@
+package kr.co.iya.base.support.util;
+
+import java.util.Locale;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.servlet.LocaleResolver;
+
+import kr.co.iya.base.support.aid.AwareAidPack.ThreadLocalAid;
+import kr.co.iya.base.support.aid.RequestAttributesAidPack.RequestAttributesAid;
+
+public final class LocaleUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface LocaleUtil {
+		
+		public Locale getLocale();
+		public void setLocale(Locale locale);
+
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static LocaleUtil getLocaleUtil(RequestAttributesAid requestAttributesAid) {
+		
+		return new LocaleUtil() {
+			
+			@Autowired(required=false)
+			private LocaleResolver resolver;
+			
+			@Override
+			public Locale getLocale() {
+				if(this.resolver==null) {return Locale.getDefault();}
+				Locale locale = null;
+				if(requestAttributesAid.getServletRequestAttributes()!=null && this.resolver!=null) {			
+					locale = this.resolver.resolveLocale(requestAttributesAid.getHttpServletRequest());
+				}else{
+					if(!ThreadLocalAid.getThreadLocalMap().containsKey("locale")) {ThreadLocalAid.getThreadLocalMap().put("locale",Locale.KOREAN);}			
+					locale = (Locale)ThreadLocalAid.getThreadLocalMap().get("locale");
+				}
+				return locale;
+			}
+
+			@Override
+			public void setLocale(Locale locale) {
+				if(this.resolver==null) {return;}
+				
+				if(requestAttributesAid.getServletRequestAttributes()!=null && this.resolver!=null) {
+					this.resolver.setLocale(requestAttributesAid.getHttpServletRequest(), requestAttributesAid.getHttpServletResponse(), locale);
+				}else{
+					ThreadLocalAid.getThreadLocalMap().put("locale",locale);
+				}
+			}
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 434 - 0
src/main/java/kr/co/iya/base/support/util/MaskUtilPack.java

@@ -0,0 +1,434 @@
+package kr.co.iya.base.support.util;
+
+import java.text.NumberFormat;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import kr.co.iya.base.context.MaskContext;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class MaskUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface MaskUtil {
+		public static final char MASK_CHAR='*';
+		
+		default public String maskName(String value) {return this.maskName(value,MASK_CHAR);}
+		default public String maskJuminNo(String value) {return this.maskJuminNo(value,MASK_CHAR);}
+		default public String maskDriverLicense(String value) {return this.maskDriverLicense(value,MASK_CHAR);}
+		default public String maskPassPort(String value) {return this.maskPassPort(value,MASK_CHAR);}
+
+		default public String maskPhone(String value) {return this.maskPhone(value,MASK_CHAR);}
+		default public String maskEmail(String value) {return this.maskEmail(value,MASK_CHAR);}
+		default public String maskIp(String value) {return this.maskIp(value,MASK_CHAR);}
+		default public String maskAddress(String value) {return this.maskAddress(value,MASK_CHAR);}
+		default public String maskBankAccount(String value) {return this.maskBankAccount(value,MASK_CHAR);}
+		default public String maskCreditCard(String value) {return this.maskCreditCard(value,MASK_CHAR);}
+		default public String maskMoney(String value) {return this.maskMoney(value,MASK_CHAR);}
+		
+		default public String maskFront(String value, int length) {return this.maskFront(value,length,MASK_CHAR);}
+		default public String maskMiddle(String value, int start, int end) {return this.maskMiddle(value,start,end,null,MASK_CHAR);}
+		default public String maskBack(String value, int length) {return this.maskBack(value,length,MASK_CHAR);}
+		default public String maskAll(String value) {return this.maskAll(value,MASK_CHAR);}
+		
+		public String maskName(String value, char maskChar);
+		public String maskJuminNo(String value, char maskChar);
+		public String maskDriverLicense(String value, char maskChar);
+		public String maskPassPort(String value, char maskChar);
+		
+		public String maskPhone(String value, char maskChar);
+		public String maskEmail(String value, char maskChar);
+		public String maskIp(String value, char maskChar);
+		public String maskAddress(String value, char maskChar);
+		public String maskBankAccount(String value, char maskChar);
+		public String maskCreditCard(String value, char maskChar);
+		public String maskMoney(String value, char maskChar);
+		
+		public String maskFront(String value, int length, char maskChar);
+		public String maskMiddle(String value, int start, int end, String splitRegex, char maskChar);
+		public String maskBack(String value, int length, char maskChar);
+		public String maskAll(String value, char maskChar);
+
+		public String maskRegex(String value, String regex, char maskChar);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+		
+	public static MaskUtil getMaskUtil(MaskContext maskContext) {
+		
+		return new MaskUtil() {
+
+			@Override
+			public String maskName(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				if(!this.isHangul(value)){
+					if(value.length()>5) {return this.maskMiddle(value,2,2,(value.indexOf(" ")>1?" ":null),maskChar);}
+					else {return this.maskAuto(value, maskChar);}
+				}
+				
+				int length = value.length();
+				
+				if(length<2) {return value;}
+				
+				if(length<5) {
+					int  pos = length==2?1:length-2;
+					return value.substring(0,pos)+this.maskFront(value.substring(pos),1);
+				}
+
+				return this.maskMiddle(value,2,value.length()<7?1:2,(value.indexOf(" ")>1?" ":null),maskChar);
+			}
+			
+			@Override
+			public String maskJuminNo(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				// 생년월일, 성별 이외 뒤 6자리 마스킹 : 800101-1******
+				Pattern pattern = Pattern.compile("^(\\d{2}([0]\\d|[1][0-2])([0][1-9]|[1-2]\\d|[3][0-1])[-]*[1-4])(\\d{6})$"); 
+				Matcher matcher = pattern.matcher(value); 
+				if(!matcher.find()) {return this.maskForeignerNo(value,maskChar);}
+				return new StringBuffer(matcher.group(1)).append(this.getMaskString(6,maskChar)).toString(); 
+			}
+
+			@Override
+			public String maskDriverLicense(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}	
+				
+				// 운전면허번호, 앞3자리, 뒤4자리 이외 마스킹 : 대구 0*-****56-71, 11-0*-****56-71
+				Pattern pattern = Pattern.compile("^(\\d{2}|[가-힣]{2})[.-[ ]]?(\\d{2})[.-[ ]]?(\\d{6})[.-[ ]]?(\\d{2})$"); 
+				Matcher matcher = pattern.matcher(value); 
+				if(!matcher.find()) {return value;}
+
+				StringBuilder builder= new StringBuilder();
+		        for(int i=1;i<= matcher.groupCount();i++) {
+		        	if(i==2) {
+		        		builder.append(this.maskBack(matcher.group(i), 1, maskChar));
+		        	}else if(i==3) {
+		        		builder.append(this.maskFront(matcher.group(i), 4, maskChar));
+		        	}else {
+		        		builder.append(matcher.group(i));
+		        	}
+		        	
+		        	builder.append(i<matcher.groupCount()?"-":"");
+	
+		        }
+				return builder.toString();
+			}
+
+			@Override
+			public String maskPassPort(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				// 뒤 4자리 이외 마스킹 : *****4678
+				Pattern pattern = Pattern.compile("^([a-zA-Z]{1}[0-9a-zA-Z]{1})(\\d{3})(\\d{4})$"); 
+				Matcher matcher = pattern.matcher(value); 
+				if(!matcher.find()) {return value;}
+
+				return this.getMaskString(value.length()-4, maskChar)+matcher.group(3);
+			}
+
+			@Override
+			public String maskPhone(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+
+	        	char zero='0';
+	        	
+				// 국번 뒤 2자리 마스킹, 번호 뒤 2자리 마스킹 : 010-99**-12**, 0100-99**-12**,070-99**-12**,0700-99**-12**, 02-****-1234, +82-10-****-5678
+				String regExp = "^(\\d{3})[.-[ ]]?(\\d{3,4})[.-[ ]]?(\\d{4})$";
+				if(value.charAt(3)==zero) {regExp = "^(\\d{3,4})[.-[ ]]?(\\d{4})[.-[ ]]?(\\d{4})$";}
+				else if(value.startsWith("02")) {regExp = "^(\\d{2})[.-[ ]]?(\\d{3,4})[.-[ ]]?(\\d{4})$";}
+				else if(value.startsWith("+82")) {regExp="^(\\+82)[.-[ ]]?([1]\\d{1}|[2]|[3-9]\\d{1})?[.-[ ]]?(\\d{3,4})[.-[ ]]?(\\d{4})$";}
+
+	        	log.debug(">> regExp:{}",regExp);
+
+	        	Pattern pattern = Pattern.compile(regExp);
+				Matcher matcher = pattern.matcher(value);
+				if(!matcher.find()) {return value;}
+				
+				String result = "";
+				int groupCount = matcher.groupCount();
+	        	//log.debug(">> groupCount:{}",groupCount);
+
+		        for(int i=1;i<=groupCount;i++) {
+		        	//log.debug(">> matcher.group(i):{}",matcher.group(i));
+		        	if(matcher.group(i)==null) {continue;}
+		        	if(i==1) {
+		        		String temp=matcher.group(i);
+		        		if(temp.startsWith("01") || temp.startsWith("07")){temp=matcher.group(i).substring(0,3);}
+		        		else if(temp.lastIndexOf("00")!=-1) {temp=temp.substring(0,temp.length()-2);}
+		        		else if(temp.lastIndexOf("0")!=-1) {temp=temp.substring(0,temp.length()-1);}		
+		        		result += temp;
+		        	} else {
+		        		if(i==groupCount-2) {
+		        			result += matcher.group(i);
+		        		}
+			        	if(i==groupCount-1) {
+			        		String temp = this.maskBack(matcher.group(i), 2, maskChar);
+			        		while(temp.startsWith("0")) {temp = temp.substring(1);}			        				
+			        		result += temp;
+			        	}
+			        	if(i==groupCount-0) {
+			        		result += this.maskBack(matcher.group(i), 2, maskChar);
+			        	}       	
+		        	}
+		        	result += i<matcher.groupCount()?"-":"";
+		        }
+
+				return result;
+			}
+
+			@Override
+			public String maskEmail(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				// 계정 앞 3자리 외 마스킹 : abc****@hanmail.net
+				Pattern pattern = Pattern.compile("^([a-zA-Z0-9._%+-]+)(@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6})$");	
+				Matcher matcher = pattern.matcher(value);
+				if(!matcher.find()) {return value;}
+				
+				StringBuilder builder= new StringBuilder();
+				if(matcher.group(1).length()>3) {
+					builder.append(this.maskBack(matcher.group(1),matcher.group(1).length()-3,maskChar));
+				}else{					
+					builder.append(this.maskAuto(matcher.group(1),maskChar));
+				}
+				builder.append(matcher.group(2));
+				return builder.toString();
+			}
+
+			@Override
+			public String maskIp(String value, char maskChar) {		
+				if(!this.isApply(value)) {return value;}
+				String result = this.maskIpv4(value, maskChar);
+				return result.equals(value)?this.maskIpv6(value, maskChar):result;
+			}
+			
+			@Override
+			public String maskAddress(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}				
+				if(value.indexOf(" ")==-1) {return this.maskRegex(value, "[0-9]", maskChar).trim();}
+				
+				int startIndex = -1;
+				String[] parts = value.split(" ");
+				for(int i=0;i<parts.length;i++) {
+					if(Pattern.compile("([0-9])").matcher(parts[i]).find()) {startIndex=i;break;}
+				}
+			
+				String result = value;
+				if(startIndex==-1) {
+					int pos=-1;
+					if(pos==-1 && (value.indexOf("로")>-1 && value.indexOf("로")==value.lastIndexOf("로"))) {pos = value.indexOf("로");} //순서변경 금지 
+					if(pos==-1 && (value.indexOf("읍")>-1 && value.indexOf("읍")==value.lastIndexOf("읍"))) {pos = value.indexOf("읍");} //순서변경 금지
+					if(pos==-1 && (value.indexOf("면")>-1 && value.indexOf("면")==value.lastIndexOf("면"))) {pos = value.indexOf("면");} //순서변경 금지
+					if(pos==-1 && (value.indexOf("동")>-1 && value.indexOf("동")==value.lastIndexOf("동"))) {pos = value.indexOf("동");} //순서변경 금지
+					if(pos==-1 && (value.indexOf("로 ")>-1 && value.indexOf("로 ")==value.lastIndexOf("로 "))) {pos = value.indexOf("로 ");} //순서변경 금지 
+					if(pos==-1 && (value.indexOf("읍 ")>-1 && value.indexOf("읍 ")==value.lastIndexOf("읍 "))) {pos = value.indexOf("읍 ");} //순서변경 금지
+					if(pos==-1 && (value.indexOf("면 ")>-1 && value.indexOf("면 ")==value.lastIndexOf("면 "))) {pos = value.indexOf("면 ");} //순서변경 금지
+					if(pos==-1 && (value.indexOf("동 ")>-1 && value.indexOf("동 ")==value.lastIndexOf("동 "))) {pos = value.indexOf("동 ");} //순서변경 금지
+					if(pos!=-1) {result = value.substring(0,pos+1)+this.maskRegex(value.substring(pos+1), "\\S", maskChar);}
+				}else {
+					String block1="",block2="";
+					for(int i=0;i<parts.length;i++) {
+						if(i<startIndex) {
+							block1=block1+" "+parts[i];
+						}else {
+							block2=block2+" "+parts[i];
+						}	
+					}
+					//log.debug(">> startIndex:{}",startIndex);
+					//log.debug(">> block1:{}",block1);
+					//log.debug(">> block2:{}",block2);
+					//result = block1+" "+this.maskRegex(block2.trim(), "\\S", maskChar);
+					result = block1+" "+maskChar+maskChar+maskChar; //마스킹되어야 하는 부분을 모두 *** 3자리로 표현
+				}
+
+				return result.trim();
+			}
+
+			@Override
+			public String maskCreditCard(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				int group4Cnt=value.replace("-","").replace(".","").length()-12;
+
+				//앞3자리, 뒤4자리 이외 마스킹 : 123*-****-****-1234
+				Pattern pattern = Pattern.compile("^([234569][0-9]{3})[-.[ ]]?([0-9]{4})[-.[ ]]?([0-9]{4})[-.[ ]]?([0-9]{"+group4Cnt+"})$");					
+				Matcher matcher = pattern.matcher(value);
+				if(!matcher.find()) {return value;}
+				
+				StringBuilder builder= new StringBuilder();
+				builder.append(this.maskBack(matcher.group(1),1, maskChar));
+				builder.append("-");
+				builder.append(this.getMaskString(4,maskChar));
+				builder.append("-");
+				builder.append(this.getMaskString(4,maskChar));
+				builder.append("-");
+				builder.append(matcher.group(4));
+				return builder.toString();
+			}
+
+			@Override
+			public String maskMoney(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				// 뒤 3자리 외 마스킹 : ***,***,000
+				String temp = value.replace(",","");
+				if(!Pattern.matches("^[0-9]*$", temp)) {return value;}				
+				temp = NumberFormat.getInstance().format(Integer.parseInt(temp));
+				return this.maskMiddle(temp, 0, 3, ",", maskChar);
+			}
+			
+			@Override
+			public String maskBankAccount(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				//앞3자리, 뒤4자리 이외 마스킹 : 123-***-**3456
+				if(value.length()<8) {return value;}
+				return this.maskMiddle(value,3,4,(value.indexOf("-")>0?"-":null),maskChar);
+			}
+
+			@Override
+			public String maskFront(String value, int length, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				if(value.length()<length) {return this.maskAll(value);}
+				return this.getMaskString(length, maskChar)+value.substring(length);
+			}
+
+			@Override
+			public String maskMiddle(String value, int sRemainCnt, int eRemainCnt, String splitRegex, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				
+				if(value == null || value.equals("")){return value;}
+				if(value.length()<sRemainCnt+eRemainCnt) {return value;}
+				
+				if(splitRegex==null || splitRegex.equals("")) {
+					StringBuilder builder= new StringBuilder();
+					builder.append(value.substring(0,sRemainCnt));
+					builder.append(this.getMaskString(value.length()-(sRemainCnt+eRemainCnt), maskChar));
+					builder.append(value.substring(value.length()-eRemainCnt));
+					return builder.toString();
+				
+				}
+				
+				String array[] = value.split(splitRegex);
+				int sIndex=0;
+				int eIndex=array.length-1;
+				
+				if(array[sIndex].length()>sRemainCnt) {array[sIndex]=this.maskBack(array[sIndex], array[sIndex].length()-sRemainCnt, maskChar);}
+				if(array[eIndex].length()>eRemainCnt) {array[eIndex]=this.maskFront(array[eIndex], array[eIndex].length()-eRemainCnt, maskChar);}
+				
+				StringBuilder builder= new StringBuilder();
+				for(int i=0;i<array.length;i++) {
+					builder.append((i==sIndex||i==eIndex)?array[i]:this.maskAll(array[i]));
+					builder.append(i<eIndex?splitRegex:"");
+				}
+				return builder.toString();
+			}
+			
+			@Override
+			public String maskBack(String value, int length, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				if(value.length()<length) {return this.maskAll(value);}
+				return value.substring(0,value.length()-length)+this.getMaskString(length, maskChar);
+			}
+			
+			@Override
+			public String maskAll(String value, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				return this.getMaskString(value.length(), maskChar);
+			}
+
+			@Override
+			public String maskRegex(String value, String regex, char maskChar) {
+				if(!this.isApply(value)) {return value;}
+				return value.replaceAll(regex,String.valueOf(maskChar));
+			}
+
+			private String maskForeignerNo(String value, char maskChar) {
+				// 생년월일, 성별 이외 뒤 6자리 마스킹 : 800101-1******
+				Pattern pattern = Pattern.compile("^(\\d{2}([0]\\d|[1][0-2])([0][1-9]|[1-2]\\d|[3][0-1])[-]*[5-8])(\\d{4})(\\d{1})(\\d{1})$"); 
+				Matcher matcher = pattern.matcher(value);
+				if(!matcher.find()) {return value;}
+				StringBuilder builder= new StringBuilder();
+				builder.append(matcher.group(1));
+				builder.append(this.getMaskString(6,maskChar));
+				return builder.toString();
+			}
+						
+			private String maskIpv4(String value, char maskChar) {				
+				//127.123.xxx.253
+				Pattern patternIPv4 = Pattern.compile("^([01]?\\d?\\d|2[0-4]\\d|25[0-5])\\.([01]?\\d?\\d|2[0-4]\\d|25[0-5])\\.([01]?\\d?\\d|2[0-4]\\d|25[0-5])\\.([01]?\\d?\\d|2[0-4]\\d|25[0-5])$");				
+				Matcher matcherIPv4 = patternIPv4.matcher(value);
+				if(!matcherIPv4.find()) {return value;}
+				
+				String result = "";
+		        for(int i=1;i<= matcherIPv4.groupCount();i++) {		            
+		            result += i==3?this.getMaskString(3, maskChar):matcherIPv4.group(i);
+		            result += i<matcherIPv4.groupCount()?".":"";
+		        }
+				return result;
+			}
+			
+			private String maskIpv6(String value, char maskChar) {	
+				//0000:0000:0000:0000:0000:0000:0000:0000
+				Pattern patternIPv6 = Pattern.compile("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$");
+				Matcher matcherIPv6 = patternIPv6.matcher(value);
+				if(!matcherIPv6.find()) {return value;}
+				
+				String array[]=value.split(":");				
+				int applyIndex=array.length-1;
+				int applyCount=2;
+				
+				while(true) {
+					if(!array[applyIndex].equals("")) {
+						if(array[applyIndex].indexOf(".")!=-1) {
+							array[applyIndex]=this.maskIpv4(array[applyIndex],maskChar);
+							applyCount=0;
+						}else{
+							array[applyIndex]=this.getMaskString(array[applyIndex].length(),maskChar);
+							applyCount--;
+						}
+					}						
+					if(applyCount==0 || applyIndex==0) {break;}
+					applyIndex--;						
+				}
+				
+				StringBuilder builder = new StringBuilder();
+		        for(int i=0;i<array.length;i++) {
+		        	builder.append(array[i]);
+		        	builder.append(array.length-i==1?"":":");
+		        }
+				return builder.toString();					
+			}
+			
+			private String maskAuto(String value, char maskChar) {
+				if(value.length()==1) {return String.valueOf(maskChar);}
+				int maskCount=value.length()/2+value.length()%2;
+				int sRemainCnt=(maskCount==1?1:maskCount/2);
+				int eRemainCnt=value.length()-(sRemainCnt+maskCount);
+				return this.maskMiddle(value,sRemainCnt,eRemainCnt,null,maskChar);
+			}
+			
+			private String getMaskString(int length, char maskChar) {
+				char[] array = new char[length];
+				Arrays.fill(array,maskChar);
+				return String.valueOf(array);
+			}
+
+			private boolean isHangul(String value) {
+				return value==null?false:value.matches(".*[ㄱ-ㅎㅏ-ㅣ가-힣]+.*");
+			}
+			
+			private boolean isApply(String value) {
+				if(value==null || value.equals("")) {return false;}
+				return maskContext==null?true:maskContext.isMaskApply();
+			}
+		};
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 68 - 0
src/main/java/kr/co/iya/base/support/util/MessageUtilPack.java

@@ -0,0 +1,68 @@
+package kr.co.iya.base.support.util;
+
+import java.util.Locale;
+
+import org.springframework.context.MessageSource;
+
+import kr.co.iya.base.support.util.LocaleUtilPack.LocaleUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class MessageUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface MessageUtil {
+
+		public static final String messageSourceName="messageSource";
+
+		public String getMessage(String messageCode);		
+		public String getMessage(String messageCode,String arg1);
+		public String getMessage(String messageCode,String arg1,String arg2);
+		public String getMessage(String messageCode,String arg1,String arg2,String arg3);
+		public String getMessage(String messageCode,Object[] paramArrayOfObject);
+		public String getMessage(String messageCode,Object[] paramArrayOfObject, Locale paramLocale);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static MessageUtil getMessageUtil(LocaleUtil localeUtil, MessageSource messageSource) {
+		if(localeUtil==null) {log.debug(">> param[localeUtil] is null."); return null;}
+		if(messageSource==null) {log.debug(">> param[messageSource] is null."); return null;}
+		
+		return new MessageUtil() {
+
+			@Override
+			public String getMessage(String messageCode) {
+				return this.getMessage(messageCode,"");
+			}
+
+			@Override
+			public String getMessage(String messageCode,String arg1) {
+				return this.getMessage(messageCode,arg1,null);
+			}
+
+			@Override
+			public String getMessage(String messageCode,String arg1,String arg2) {
+				return this.getMessage(messageCode,arg1,arg2,null);
+			}
+
+			@Override
+			public String getMessage(String messageCode,String arg1,String arg2,String arg3) {
+				return this.getMessage(messageCode,new String[] {arg1,arg2,arg3});
+			}
+
+			@Override
+			public String getMessage(String messageCode,Object[] paramArrayOfObject) {
+				return this.getMessage(messageCode, paramArrayOfObject, localeUtil.getLocale());
+			}
+				
+			@Override
+			public String getMessage(String messageCode,Object[] paramArrayOfObject, Locale paramLocale) {
+				return messageSource.getMessage(messageCode, paramArrayOfObject, paramLocale);
+			}
+		};	
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 80 - 0
src/main/java/kr/co/iya/base/support/util/ObjectMapperUtilPack.java

@@ -0,0 +1,80 @@
+package kr.co.iya.base.support.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.json.JsonSanitizer;
+
+public final class ObjectMapperUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface ObjectMapperUtil {
+				
+		public ObjectMapper getMapper();
+		
+		public <T> T convert(Object obj,Class<T> type);
+		public <T> T convert(Object obj,TypeReference<T> typeReference);
+		
+		public <T> List<T> convertList(List<?> list, Class<T> type);
+		
+		public <T> T readValue(String content, Class<T> type) throws Exception;
+		public <T> T readValue(String content, TypeReference<T> typeReference) throws Exception;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static ObjectMapperUtil getObjectMapperUtil() {
+		ObjectMapper mapper=new ObjectMapper();
+		mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);	
+		mapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
+		
+		return new ObjectMapperUtil() {
+
+			@Override
+			public ObjectMapper getMapper() {
+				return mapper;
+			}
+
+			@Override
+			public <T> T convert(Object obj,Class<T> type) {
+				if(obj==null) {return null;}
+				return this.getMapper().convertValue(obj, type);
+			}
+
+			@Override
+			public <T> T convert(Object obj,TypeReference<T> typeReference) {
+				if(obj==null) {return null;}
+				return this.getMapper().convertValue(obj, typeReference);
+			}
+
+			@Override
+			public <T> List<T> convertList(List<?> list, Class<T> type) {
+				if(list==null) {return null;}
+				List<T> result =new ArrayList<>();
+				for(int i=0;i<list.size();i++) {result.add(this.getMapper().convertValue(list.get(i), type));}
+				return result;
+			}
+
+			@Override
+			public <T> T readValue(String content, Class<T> type) throws Exception {
+				//To prevent server-side JSON injections, sanitize all data before serializing it to JSON
+				return this.getMapper().readValue(JsonSanitizer.sanitize(content), type);
+			}
+			
+			@Override
+			public <T> T readValue(String content, TypeReference<T> typeReference) throws Exception {
+				//To prevent server-side JSON injections, sanitize all data before serializing it to JSON
+				return this.getMapper().readValue(JsonSanitizer.sanitize(content), typeReference);
+			}
+			
+		};
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 201 - 0
src/main/java/kr/co/iya/base/support/util/PropertiesUtilPack.java

@@ -0,0 +1,201 @@
+package kr.co.iya.base.support.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+
+import kr.co.iya.base.support.aid.BeanAidPack.BeanAid;
+import kr.co.iya.base.support.util.JsonUtilPack.JsonUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class PropertiesUtilPack  {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface PropertiesUtil {
+
+	    public String getProperty(String key);
+		public String getProperty(String propertiesBeanId,String key);
+
+		public Properties getProperties();
+		
+		public Properties getProperties(File file);
+		public Properties getProperties(String jsonString);
+		public Properties getProperties(URL resourceUrl);
+
+		public Properties getPropertiesThrouSystem(Properties refProps,String refStr);  
+	    public Properties getPropertiesThrouSystem(String key,String value,String refStr);
+
+		public String getPropertyParseValue(String key);
+		public String getPropertyParseValue(Properties properties,String key);	
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static PropertiesUtil getPropertiesUtil(BeanAid beanAid, JsonUtil jsonUtil, String defaultProperties) {
+		if(beanAid==null) {log.debug(">> param[beanAid] is null."); return null;}
+		if(jsonUtil==null) {log.debug(">> param[jsonUtil] is null."); return null;}
+		if(defaultProperties==null) {log.debug(">> param[defaultProperties] is null."); return null;}
+		
+		return new PropertiesUtil() {
+
+		    @Override
+			public String getProperty(String key) {
+				return this.getProperty(defaultProperties,key);
+			}
+		    
+		    @Override
+			public String getProperty(String propertiesBeanId,String key) {
+				return beanAid.getBean(propertiesBeanId,Properties.class).getProperty(key);
+			}
+
+		    @Override
+			public Properties getProperties() {
+				if(defaultProperties==null || defaultProperties.equals("")) {return null;}
+				return beanAid.getBean(defaultProperties,Properties.class);
+			}
+
+			@Override
+			public Properties getProperties(File file) {
+				if(file == null) {throw new RuntimeException("file is null.");}
+				if(!file.exists()) {throw new RuntimeException("file is not exist");}
+
+				Properties prop = new Properties();
+				try {
+					prop.load(new FileInputStream(file));
+
+					Enumeration<?> enumeration = prop.propertyNames();
+					while(enumeration.hasMoreElements()) {
+						String key = (String)enumeration.nextElement();
+						String value = this.getPropertyParseValue(prop, key);
+						prop.put(key, value);
+					}
+
+				} catch (Exception e) {
+					throw new RuntimeException(e.getCause());
+				}
+
+				return prop;
+			}
+			
+			@Override
+			public Properties getProperties(String jsonString) {
+				if(jsonString == null || jsonString.equals("")) {throw new RuntimeException("jsonString is null or blank");}
+
+				Map<String, Object> propMap = null;
+				if(jsonUtil.isJsonObject(jsonString)) {propMap=jsonUtil.getMap(jsonString);}
+				if(propMap == null) {return null;}
+
+				Properties prop = new Properties();
+				Iterator<String> keys = propMap.keySet().iterator();
+				while(keys.hasNext()) {
+					String key = keys.next();
+					String value = this.getPropertyParseValue(prop, key);
+					prop.put(key, value);
+				}
+
+				return prop;
+			}
+			
+			@Override
+			public Properties getProperties(URL resourceUrl) {
+				if(resourceUrl==null) {return null;}
+				
+				Properties properties=null;
+				try {
+					ClassLoader classLoader=Thread.currentThread().getContextClassLoader();
+					InputStream is = classLoader.getResourceAsStream(resourceUrl.getPath());
+					if(is!=null) {
+						BufferedInputStream buf=new BufferedInputStream(is);							
+			
+						properties=new Properties();
+						properties.load(buf);
+						
+						buf.close();
+					}
+				}catch(Exception e) {
+					log.debug(">> exception skip: {}",e.getMessage());
+					//e.printStackTrace();
+				}
+				
+				return properties;
+			}
+			
+
+			@Override
+			public Properties getPropertiesThrouSystem(String key, String value, String refStr) {
+				Properties props = new Properties();
+				props.put(key, value);
+				return this.getPropertiesThrouSystem(props, refStr);
+			}
+
+			@Override
+			public Properties getPropertiesThrouSystem(Properties refProps, String refStr) {
+				Properties mainProp = null;
+				if(mainProp == null){mainProp = this.getProperties(refStr);}
+				if(mainProp == null){mainProp = this.getProperties(new File(refStr));}
+				if(mainProp == null){return null;}
+
+				if(refProps != null) {
+					Enumeration<?> refPropEnum = refProps.propertyNames();
+					while (refPropEnum.hasMoreElements()) {
+						String key = (String) refPropEnum.nextElement();
+						mainProp.setProperty(key, refProps.getProperty(key));
+					}
+				}
+
+				Enumeration<?> mainPropEnum = mainProp.propertyNames();
+				while(mainPropEnum.hasMoreElements()) {
+					String key = (String) mainPropEnum.nextElement();
+					System.setProperty(key, mainProp.getProperty(key));
+				}
+
+				return mainProp;
+			}
+
+			@Override
+			public String getPropertyParseValue(String key) {
+				return this.getPropertyParseValue(beanAid.getBean(defaultProperties, Properties.class), key);
+			}
+
+			@Override
+			public String getPropertyParseValue(Properties properties, String key) {
+				if(properties == null) {return "";}
+				if(key == null || key.equals("")) {return "";}
+
+				String value = null;
+				if(key.length() > 4 && key.startsWith("${") && key.endsWith("}")) {
+					String propertyKey = key.substring(2, key.length() - 1);
+					value = properties.getProperty(propertyKey);
+					if(value == null || value.equals("")) {value = propertyKey;}
+				} else {
+					value = properties.getProperty(key);
+					if(value == null || value.equals("")) {value = key;}
+				}
+
+				if(value.length() > 4 && value.startsWith("#{") && value.endsWith("}")) {
+					String expression = value.substring(2, value.length() - 1);
+					
+					ExpressionParser parser = new SpelExpressionParser();
+					Expression exp = parser.parseExpression(expression);
+					value = exp.getValue().toString();
+				}
+
+				return value;
+			}
+		};
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 321 - 0
src/main/java/kr/co/iya/base/support/util/RestTemplateUtilPack.java

@@ -0,0 +1,321 @@
+package kr.co.iya.base.support.util;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.http.ContentDisposition;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.BufferingClientHttpRequestFactory;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import kr.co.iya.base.exception.SystemException;
+import kr.co.iya.base.support.util.RestTemplateUtilPack.RestTemplateUtil.RestAttachType;
+import lombok.Builder;
+import lombok.Data;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class RestTemplateUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface RestTemplateUtil extends RestOperations {
+		
+		@FunctionalInterface
+		public interface FetchBody {public abstract MultiValueMap<String, Object> getBody() throws Exception;}
+
+		public enum RestAttachType {buffer,stream};
+				
+	    public static final int DEFAULT_MAX_CONN_TOTAL = 50;
+	    public static final int DEFAULT_MAX_CONN_ROUTE = 50;
+	    public static final int DEFAULT_CONNECTION_TIMEOUT = 2*1000; //ms
+	    public static final int DEFAULT_READ_TIMEOUT = 5*1000; //ms 
+
+	    public RestTemplateUtil build(RestTemplateHttpClientConfig config);
+	    
+	    public void setRestTemplateUtilFactory(RestTemplateUtilFactory factory);
+	    
+	    public <T> T attachBuffer(URI remoteURI, String fileParamName, MultipartFile files, TypeReference<T> typeReference) throws Exception;
+	    public <T> T attachBuffer(URI remoteURI, String fileParamName, List<MultipartFile> files, TypeReference<T> typeReference) throws Exception;
+	    
+	    public <T> T attachStream(URI remoteURI, String fileParamName, MultipartFile files, TypeReference<T> typeReference) throws Exception;
+	    public <T> T attachStream(URI remoteURI, String fileParamName, List<MultipartFile> files, TypeReference<T> typeReference) throws Exception;
+
+	    public <T> T attach(RestAttachType restAttachType, URI remoteURI, FetchBody fetchBody, TypeReference<T> typeReference) throws Exception;
+	}
+
+	public interface RestTemplateUtilFactory extends FactoryBean<RestTemplateUtil> {
+		public RestTemplateUtil getInstance();
+		public RestTemplateUtil getObject(RestTemplateHttpClientConfig config) throws Exception;
+	}
+	
+	@Data @Builder @ToString
+	public static class RestTemplateHttpClientConfig {	
+		@Builder.Default RestAttachType restAttachType = RestAttachType.buffer;
+		@Builder.Default private int maxConnTotal = RestTemplateUtil.DEFAULT_MAX_CONN_TOTAL;
+		@Builder.Default private int maxConnRoute = RestTemplateUtil.DEFAULT_MAX_CONN_ROUTE;
+		@Builder.Default private int connTimeout = RestTemplateUtil.DEFAULT_CONNECTION_TIMEOUT;
+		@Builder.Default private int readTimeout = RestTemplateUtil.DEFAULT_READ_TIMEOUT;	
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private static class RestTemplateUtilContext {
+    	private static Map<String,RestTemplateUtil> context = new HashMap<>();
+
+    	public static String buildKey(String bufferYn, int... keys) {
+    		StringBuilder builder = new StringBuilder();    		
+    		builder.append("++").append(bufferYn);
+    		for(int i=0;i<keys.length;i++) {builder.append(":").append(keys[i]);}
+    		builder.append("++");
+    		return builder.toString();
+    	}    	
+    	public static boolean isExist(String key){return context.containsKey(key);}
+    	public static RestTemplateUtil get(String key){return isExist(key)?context.get(key):null;}    	
+    	public static synchronized void add(String key,RestTemplateUtil restTemplateUtil){context.put(key, restTemplateUtil);}
+    }
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static RestTemplateUtil getRestTemplateUtil() {
+		
+		return new RestTemplateUtilFactory() {
+
+			@Override
+			public RestTemplateUtil getInstance() {
+				try {return this.getObject();} catch (Exception e) {throw new SystemException(e.getMessage());}
+			}
+			
+			@Override
+			public RestTemplateUtil getObject() throws Exception {
+				return this.getObject(RestAttachType.buffer,RestTemplateUtil.DEFAULT_MAX_CONN_TOTAL,RestTemplateUtil.DEFAULT_MAX_CONN_ROUTE,RestTemplateUtil.DEFAULT_CONNECTION_TIMEOUT,RestTemplateUtil.DEFAULT_READ_TIMEOUT);
+			}
+
+			@Override
+			public RestTemplateUtil getObject(RestTemplateHttpClientConfig config) throws Exception {
+				return this.getObject(config.getRestAttachType(),config.getMaxConnTotal(),config.getMaxConnRoute(),config.getConnTimeout(),config.getReadTimeout());
+			}
+
+			@Override
+			public Class<?> getObjectType() {
+				return RestTemplateUtil.class;
+			}
+
+			private RestTemplateUtil getObject(RestAttachType restAttachType,int maxConnTotal, int maxConnRoute, int connectTimeout, int readTimeout) throws Exception {
+
+				/*******************************************************************************/
+				class _RestTemplateUtil extends RestTemplate implements RestTemplateUtil {
+					
+					private RestTemplateUtilFactory factory;
+					
+					private _RestTemplateUtil(ClientHttpRequestFactory requestFactory) {
+						super(requestFactory);
+					}
+
+					@Override
+				    public RestTemplateUtil build(RestTemplateHttpClientConfig config) {
+						try {
+							return this.factory.getObject(config);
+						} catch (Exception e) {
+							throw new SystemException(e.getMessage());
+						}
+				    }
+					
+					@Override
+				    public void setRestTemplateUtilFactory(RestTemplateUtilFactory factory) {
+						this.factory = factory;
+				    }
+				    
+					@Override
+					public <T> T attachBuffer(URI remoteURI, String fileParamName, MultipartFile file, TypeReference<T> typeReference) throws Exception{
+						return this.attach(RestAttachType.buffer, remoteURI, ()->{return this.makeBody(fileParamName, file, RestAttachType.buffer);}, typeReference);				
+					}
+
+					@Override
+				    public <T> T attachBuffer(URI remoteURI, String fileParamName, List<MultipartFile> files, TypeReference<T> typeReference) throws Exception{
+						return this.attach(RestAttachType.buffer, remoteURI, ()->{return this.makeBody(fileParamName, files, RestAttachType.buffer);}, typeReference);
+					}
+					
+					@Override
+				    public <T> T attachStream(URI remoteURI, String fileParamName, MultipartFile file, TypeReference<T> typeReference) throws Exception{
+						return this.attach(RestAttachType.stream, remoteURI, ()->{return this.makeBody(fileParamName, file, RestAttachType.stream);}, typeReference);
+					}
+
+					@Override
+				    public <T> T attachStream(URI remoteURI, String fileParamName, List<MultipartFile> files, TypeReference<T> typeReference) throws Exception{
+						return this.attach(RestAttachType.stream, remoteURI, ()->{return this.makeBody(fileParamName, files, RestAttachType.stream);}, typeReference);
+					}
+				    
+					@Override
+				    public <T> T attach(RestAttachType restAttachType, URI remoteURI, FetchBody fetchBody, TypeReference<T> typeReference) throws Exception {				
+
+						HttpHeaders headers = new HttpHeaders();
+				    	headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+				    	
+				    	HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(fetchBody.getBody(), headers);
+				    	
+				    	RestTemplateUtil restTemplateUtil = this.factory.getObject(RestTemplateHttpClientConfig.builder().restAttachType(restAttachType).build());
+				    	
+				    	ResponseEntity<Object> response = restTemplateUtil.postForEntity(remoteURI, requestEntity, Object.class);	    	
+				    	//HttpHeaders resHeader = response.getHeaders();
+				    	//HttpStatus resStatus = response.getStatusCode();    	
+				    	Object resBody = response.getBody();
+				    	
+						ObjectMapper mapper = new ObjectMapper();
+						mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);	
+						mapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);			
+				    	T result = mapper.convertValue(resBody, typeReference);
+				    	
+				    	return result;
+					}
+					
+					private MultiValueMap<String, Object> makeBody(String fileParamName, MultipartFile file, RestAttachType restAttachType) {
+						List<MultipartFile> files = new ArrayList<>();
+						files.add(file);
+						return this.makeBody(fileParamName, files, restAttachType);
+					}
+					
+					private MultiValueMap<String, Object> makeBody(String fileParamName, List<MultipartFile> files, RestAttachType restAttachType) {
+				    	MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+				    	for(MultipartFile file : files) {
+				    		if(file==null) {continue;}
+				    		if(restAttachType.equals(RestAttachType.buffer)) {
+					    		try {
+									body.add(fileParamName, new ByteArrayResource(file.getBytes()){public String getFilename(){return file.getOriginalFilename();}});
+								} catch (IOException e) {	
+									throw new SystemException(e.getMessage());
+								}
+				    		}
+				    		if(restAttachType.equals(RestAttachType.stream)) {
+				    			body.add(fileParamName, file.getResource());
+				    		}
+				    	}
+				    	return body;
+					}		
+				}				
+				/*******************************************************************************/
+				
+				String key = RestTemplateUtilContext.buildKey(restAttachType.toString(), maxConnTotal, maxConnRoute, connectTimeout, readTimeout);
+				log.debug(">> RestTemplateUtilContext.key:{}",key);
+				if(RestTemplateUtilContext.isExist(key)) {
+					return RestTemplateUtilContext.get(key);
+				}
+				
+				boolean isBuffer = true; //default
+				if(restAttachType.equals(RestAttachType.buffer)) {isBuffer=true;}
+				if(restAttachType.equals(RestAttachType.stream)) {isBuffer=false;}
+				
+				//HttpClient를 이용해서 connection pool을 사용 (maxConnTotal:최대 커넥션 개수, maxConnRoute: IP:PORT 쌍에 대한 커넥션 수)
+				HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
+				factory.setHttpClient(HttpClientBuilder.create().setMaxConnTotal(maxConnTotal).setMaxConnPerRoute(maxConnRoute).build());
+				factory.setConnectTimeout(connectTimeout);
+				factory.setReadTimeout(readTimeout);
+				factory.setBufferRequestBody(isBuffer); // true: buffer(in-memory), false: streaming 				
+			
+				RestTemplate restTemplate = null;
+				if(isBuffer) {
+					//인터셉터에서 Body Stream을 읽어 소비가 되면 실제 비즈니스 로직에서는 Body가 없어짐
+					//이를 해결하기 위해 requestFactory에 BufferingClientHttpRequestFactory를 사용
+					//ClientHttpRequestInterceptor는 ClientHttpRequest가 BufferRequestBody = true인 경우 만 적용 됨
+					restTemplate = new _RestTemplateUtil(new BufferingClientHttpRequestFactory(factory));			
+					restTemplate.setInterceptors(this.getClientHttpRequestInterceptors());
+				}else {
+					restTemplate = new _RestTemplateUtil(factory);
+				}
+				restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
+				
+				RestTemplateUtil restTemplateUtil = (RestTemplateUtil)restTemplate;
+				restTemplateUtil.setRestTemplateUtilFactory(this);
+				RestTemplateUtilContext.add(key, restTemplateUtil);
+				
+				return restTemplateUtil;
+			}
+			
+			private List<ClientHttpRequestInterceptor> getClientHttpRequestInterceptors() {
+				List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+				interceptors.add(new ClientHttpRequestInterceptor() {
+
+					@Override
+					public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+
+						long start=System.currentTimeMillis();
+						String executionNo="executionNo-"+String.valueOf(start);
+						MediaType contentType = request.getHeaders().getContentType();
+						
+						log.debug(">> [{}], request.uri:{}",executionNo,request.getURI());
+						log.debug(">> [{}], request.method:{}",executionNo,request.getMethod());
+						log.debug(">> [{}], request.headers:{}",executionNo,request.getHeaders());
+
+						if(contentType!=null && contentType.includes(MediaType.MULTIPART_FORM_DATA)) {								
+							String bodyStr = new String(body,"UTF-8");
+							String lines[] = bodyStr.split("\n");
+							String boundary = lines[0].trim();
+							
+							StringBuilder builder = new StringBuilder();							
+							Arrays.asList(lines).stream().forEach(line->{
+								if(line.startsWith(boundary)) {builder.append(line).append("\n");}
+								else if(line.startsWith("Content-Disposition")) {builder.append(line).append("\n");}
+								else if(line.startsWith("Content-Type")) {builder.append(line).append("\n");}
+								else if(line.startsWith("Content-Length")) {builder.append(line).append("\n**첨부파일(로그생략)**\n");}
+							});
+							log.debug(">> [{}], request.body:\n{}",executionNo,builder.toString());
+						}else {
+							log.debug(">> [{}], request.body:{}",executionNo,new String(body,"UTF-8"));		
+						}
+						
+						ClientHttpResponse response = execution.execute(request, body);
+						ContentDisposition contentDisposition = response.getHeaders().getContentDisposition();
+						String fileName = contentDisposition==null?null:contentDisposition.getFilename();
+						
+						log.debug(">> [{}], response.statusCode:{}",executionNo,response.getStatusCode());
+						log.debug(">> [{}], response.statusText:{}",executionNo,response.getStatusText());
+						log.debug(">> [{}], response.headers:{}",executionNo,response.getHeaders());
+						
+						if(fileName!=null && !fileName.equals("")) {
+							log.debug(">> [{}], response.body:{}",executionNo,"**첨부파일(로그생략)**");
+						}else {
+							log.debug(">> [{}], response.body:{}",executionNo,StreamUtils.copyToString(response.getBody(),Charset.defaultCharset()));							
+						}
+						log.debug(">> [{}], execution.time(ms):{}",executionNo,System.currentTimeMillis()-start);
+
+						return response;
+					}
+					
+				});
+				return interceptors;
+			}
+			
+		}.getInstance();
+	}	
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+}

+ 96 - 0
src/main/java/kr/co/iya/base/support/util/SessionUtilPack.java

@@ -0,0 +1,96 @@
+package kr.co.iya.base.support.util;
+
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.springframework.web.context.request.RequestContextHolder;
+
+import kr.co.iya.base.support.aid.RequestAttributesAidPack.RequestAttributesAid;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class SessionUtilPack  {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface SessionUtil {
+	
+		public boolean isEnableHttpSession();		
+		public HttpSession getHttpSession();
+	    public String getHttpSessionId();
+
+	    public Object getAttribute(String name);	 
+	    public void setAttribute(String name, Object object);
+	    public void removeAttribute(String name);
+	    
+	    public void doInvalidate();
+	    public void printSession(Logger logger);
+
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static SessionUtil getSessionUtil(RequestAttributesAid requestAttributesAid) {
+		if(requestAttributesAid==null) {log.debug(">> param[requestAttributesAid] is null."); return null;}
+
+		return new SessionUtil() {
+
+			@Override
+			public boolean isEnableHttpSession() {
+				return RequestContextHolder.getRequestAttributes()==null?false:true;
+			}
+
+			@Override
+			public HttpSession getHttpSession() {
+				return requestAttributesAid.getHttpServletRequest().getSession();
+			}
+			
+			@Override
+			public String getHttpSessionId() {
+				return this.getHttpSession().getId();
+		    }
+
+			@Override
+			public Object getAttribute(String name) {
+				return this.getHttpSession().getAttribute(name);
+		    }
+
+			@Override
+			public void setAttribute(String name, Object object) {
+				this.getHttpSession().setAttribute(name, object);
+		    }
+
+			@Override
+			public void removeAttribute(String name) {
+				this.getHttpSession().removeAttribute(name);
+		    }
+
+			@Override
+			public void doInvalidate() {
+				this.getHttpSession().invalidate();
+			}
+
+			@Override
+			public void printSession(Logger logger) {
+				
+			     String key;
+			     Object value;
+			     HttpSession session=this.getHttpSession();
+
+		         logger.debug(">> Start Print Session");
+		         requestAttributesAid.printApplicationContextInfo();
+			     for (Enumeration<?> e=session.getAttributeNames() ; e.hasMoreElements() ;) {
+			         key=(String)e.nextElement();
+			         value=session.getAttribute(key);
+			         logger.debug(">> key= {}, value= {}",key,value);
+			     } 
+		         logger.debug(">> End Print Session");
+			}
+			
+		};
+	}
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 348 - 0
src/main/java/kr/co/iya/base/support/util/SftpUtilPack.java

@@ -0,0 +1,348 @@
+package kr.co.iya.base.support.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import kr.co.iya.base.exception.SystemException;
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+import kr.co.iya.base.support.util.SftpUtilPack.SftpUtil.AuthType;
+import kr.co.iya.base.support.util.SftpUtilPack.SftpUtil.FetchJob;
+import lombok.extern.slf4j.Slf4j;
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.sftp.FileAttributes;
+import net.schmizz.sshj.sftp.FileMode;
+import net.schmizz.sshj.sftp.RemoteResourceInfo;
+import net.schmizz.sshj.sftp.SFTPClient;
+import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
+
+@Slf4j
+public final class SftpUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface SftpUtil {
+
+		@FunctionalInterface
+		public interface FetchJob {public abstract Object execute(SFTPClient sftp, ChannelUtil util) throws Exception;}
+
+		public enum AuthType {privatekey,password};
+		public Channel build(String remoteHost, String username, String password) throws Exception;
+		public Channel build(String remoteHost, String username, String password, long autoCloseMills) throws Exception;
+		public Channel build(String remoteHost, String username, File privateKeyFile) throws Exception;
+		public Channel build(String remoteHost, String username, File privateKeyFile, long autoCloseMills) throws Exception;
+		
+		public Channel build(String remoteHost, int remotePort, String username, String password) throws Exception;
+		public Channel build(String remoteHost, int remotePort, String username, String password, long autoCloseMills) throws Exception;
+		public Channel build(String remoteHost, int remotePort, String username, File privateKeyFile) throws Exception;
+		public Channel build(String remoteHost, int remotePort, String username, File privateKeyFile, long autoCloseMills) throws Exception;
+	}
+
+	public interface ChannelUtil{
+		public boolean isExist(String remotePath);
+		public boolean isFile(String remotePath);
+		public boolean isDirectory(String remotePath);
+
+		public String getStorePath(String directory, String fileName);
+		public String getUniqueName(String directory, String prefix, String fileName);
+		public String normalizePath(String path);
+		public String extractFileName(String path);
+		public File mkdirs(String storePath);
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static class Channel {
+		private static final int DEFAULT_REMOTE_PORT = 22;
+		
+		private SSHClient ssh;
+		private SFTPClient sftp;
+		
+		private FileUtil fileUtil;
+		private ChannelUtil util;
+
+		private long autoCloseMills = -1;
+		
+		private AuthType authType;		
+		private String remoteHost;
+		private int remotePort;
+		private String username;
+		private String password;
+		private File privateKeyFile;
+
+		public Channel(String remoteHost, String username, String password, FileUtil fileUtil) {this(AuthType.password,remoteHost,DEFAULT_REMOTE_PORT,username,password,null,-1,fileUtil);}
+		public Channel(String remoteHost, String username, String password, long autoCloseMills, FileUtil fileUtil) {this(AuthType.password,remoteHost,DEFAULT_REMOTE_PORT,username,password,null,autoCloseMills,fileUtil);}
+		public Channel(String remoteHost, String username, File privateKeyFile, long autoCloseMills, FileUtil fileUtil) {this(AuthType.privatekey,remoteHost,DEFAULT_REMOTE_PORT,username,null,privateKeyFile,autoCloseMills,fileUtil);}			
+		public Channel(String remoteHost, String username, File privateKeyFile, FileUtil fileUtil) {this(AuthType.privatekey,remoteHost,DEFAULT_REMOTE_PORT,username,null,privateKeyFile,-1,fileUtil);}
+
+		public Channel(String remoteHost, int remotePort, String username, String password, FileUtil fileUtil) {this(AuthType.password,remoteHost,remotePort,username,password,null,-1,fileUtil);}
+		public Channel(String remoteHost, int remotePort, String username, String password, long autoCloseMills, FileUtil fileUtil) {this(AuthType.password,remoteHost,remotePort,username,password,null,autoCloseMills,fileUtil);}
+		public Channel(String remoteHost, int remotePort, String username, File privateKeyFile, long autoCloseMills, FileUtil fileUtil) {this(AuthType.privatekey,remoteHost,remotePort,username,null,privateKeyFile,autoCloseMills,fileUtil);}			
+		public Channel(String remoteHost, int remotePort, String username, File privateKeyFile, FileUtil fileUtil) {this(AuthType.privatekey,remoteHost,remotePort,username,null,privateKeyFile,-1,fileUtil);}
+		
+		private Channel(AuthType authType, String remoteHost, int remotePort, String username, String password, File privateKeyFile, long autoCloseMills, FileUtil fileUtil) {
+			if(StringUtils.isBlank(remoteHost)) {throw new SystemException("remoteHost is empty.");}
+			if(StringUtils.isBlank(username)) {throw new SystemException("username is empty.");}
+			if(authType.equals(AuthType.password) && StringUtils.isBlank(password)) {throw new SystemException("username is empty.");}
+			if(authType.equals(AuthType.privatekey)) {
+				if(privateKeyFile==null) {throw new SystemException("privateKeyFile is null.");}
+				if(privateKeyFile.exists()==false) {throw new SystemException("privateKeyFile is not exists.");}
+			}			
+
+			this.authType = authType;
+			this.remoteHost = remoteHost;
+			this.remotePort = remotePort;
+			this.username = username;
+			this.password = password;			
+			this.privateKeyFile = privateKeyFile;
+			this.autoCloseMills = autoCloseMills;
+			this.fileUtil = fileUtil;
+		}
+		
+		public SFTPClient getSftp() throws IOException {
+			boolean isEnable = true;
+			if(isEnable && (this.ssh == null || this.sftp == null)) {isEnable=false;}
+			if(isEnable && !(this.ssh.isConnected() && this.ssh.isAuthenticated())) {isEnable=false;}
+			if(isEnable && !(this.sftp.getSFTPEngine().getSubsystem().isOpen())) {isEnable=false;}
+			if(isEnable) {return this.sftp;}
+
+			SSHClient ssh = null;
+			if(this.authType.equals(AuthType.password)) {
+				ssh = new SSHClient();
+				ssh.addHostKeyVerifier(new PromiscuousVerifier());
+				ssh.setRemoteCharset(Charset.forName("UTF-8"));		
+				ssh.connect(this.remoteHost,this.remotePort);
+				
+				ssh.authPassword(this.username,this.password);
+			}
+			if(this.authType.equals(AuthType.privatekey)) {		
+				ssh = new SSHClient();
+				ssh.addHostKeyVerifier(new PromiscuousVerifier());
+				ssh.setRemoteCharset(Charset.forName("UTF-8"));						
+				ssh.connect(this.remoteHost,this.remotePort);
+				ssh.authPublickey(this.username,ssh.loadKeys(this.privateKeyFile.getPath()));		
+			}
+
+			this.ssh = ssh;
+			this.sftp = this.ssh.newSFTPClient();
+			//this.sftp.getFileTransfer().setTransferListener(null);
+			
+			if(this.autoCloseMills > 0) {
+				Timer timer = new Timer();
+				timer.schedule(new TimerTask() {
+
+					@Override
+					public void run() {					
+						try {
+							close();
+						}catch(Exception e) {
+							throw new SystemException(e.getMessage());
+						}
+					}
+				}, autoCloseMills);
+			}
+			
+			return this.sftp;
+		}
+		
+		public Object stage(FetchJob job) throws Exception{
+			Object object = job.execute(this.getSftp(),this.getChannelUtil());
+			this.close();			
+			return object;
+		}
+
+		public <T> T stage(FetchJob job, Class<T> type) throws Exception{
+			return this.stage(job, new TypeReference<T>() {
+			    @Override
+			    public java.lang.reflect.Type getType() {return type;}
+			});
+		}
+
+		@SuppressWarnings("unchecked")
+		public <T> T stage(FetchJob job, TypeReference<T> typeReference) throws Exception{
+			Object object = this.stage(job);
+			
+			if(typeReference.getType().equals((new TypeReference<FileAttributes>() {}).getType())) {return (T)object;}
+			if(typeReference.getType().equals((new TypeReference<RemoteResourceInfo>() {}).getType())) {return (T)object;}
+			if(typeReference.getType().equals((new TypeReference<List<RemoteResourceInfo>>() {}).getType())) {return (T)object;}
+			if(typeReference.getType().equals((new TypeReference<List<FileAttributes>>() {}).getType())) {return (T)object;}
+
+			ObjectMapper mapper = new ObjectMapper();
+			mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);	
+			mapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);			
+	    	T result = mapper.convertValue(object, typeReference);
+	    	
+			return result;
+		}
+		
+		public void close() throws IOException {
+			if(this.sftp!=null) {log.debug(">> sftp closed."); this.sftp.close();}
+			if(this.ssh!=null) {log.debug(">> ssh closed."); this.ssh.close();}
+		}
+
+		private ChannelUtil getChannelUtil() {
+			if(this.util!=null) {return this.util;}
+			
+			this.util = new ChannelUtil() {
+
+				@Override
+				public boolean isExist(String remotePath){
+					try {
+						FileAttributes attributes = sftp.statExistence(remotePath);
+						if(attributes!=null) {return true;}
+					}catch(Exception e) {
+						//log.debug(">> message:{}",e.getMessage());
+					}
+					return false;
+				}
+
+				@Override
+				public boolean isFile(String remotePath){
+					try {
+						FileAttributes attributes = sftp.stat(remotePath);
+						if(attributes!=null && attributes.getType().equals(FileMode.Type.REGULAR)) {return true;}
+					}catch(Exception e) {
+						//log.debug(">> message:{}",e.getMessage());
+					}
+					return false;
+				}
+
+				@Override
+				public boolean isDirectory(String remotePath){
+					try {
+						FileAttributes attributes = sftp.stat(remotePath);
+						if(attributes!=null && attributes.getType().equals(FileMode.Type.DIRECTORY)) {return true;}
+					}catch(Exception e) {
+						//log.debug(">> message:{}",e.getMessage());
+					}
+					return false;
+				}
+
+				@Override
+				public String getStorePath(String directory, String fileName) {
+					String transDate = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(System.currentTimeMillis());				
+					String storePath = this.normalizePath(directory) + this.getUniqueName(directory, transDate, fileName);					
+					return storePath;
+				}
+
+				@Override
+				public File mkdirs(String storePath) {
+		    		File file = new File(FilenameUtils.normalize(storePath));
+		    		file.mkdirs();
+		    		return file;
+				}
+				
+				@Override
+				public String getUniqueName(String directory, String prefix, String fileName) {
+					return fileUtil.getUniqueName(directory, prefix, fileName);
+				}
+				
+				@Override
+				public String normalizePath(String path) {
+					return fileUtil.normalizePath(path);
+				}
+			    
+				@Override
+				public String extractFileName(String path) {
+					return fileUtil.extractFileName(path);
+				}								
+			};
+			
+			return this.getChannelUtil();
+		}
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	private static class ChannelContext {
+    	private static Map<String,Channel> context = new HashMap<>();
+
+    	public static String buildKey(AuthType authType, Object... keys) {
+    		StringBuilder builder = new StringBuilder();    		
+    		builder.append("++").append(authType);
+    		for(int i=0;i<keys.length;i++) {builder.append(":").append(keys[i]);}
+    		builder.append("++");
+    		return builder.toString();
+    	}   	
+    	public static boolean isExist(String key){return context.containsKey(key);}
+    	public static Channel get(String key){return isExist(key)?context.get(key):null;}    	
+    	public static synchronized void add(String key,Channel channel){context.put(key, channel);}
+    }
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static SftpUtil getSftpUtil(FileUtil fileUtil) {
+		
+		return new SftpUtil() {
+
+			@Override
+			public Channel build(String remoteHost, String username, String password) throws Exception{
+				return this.build(remoteHost, Channel.DEFAULT_REMOTE_PORT, username, password);
+			}
+			
+			@Override
+			public Channel build(String remoteHost, String username, String password, long autoCloseMills) throws Exception{
+				return this.build(remoteHost, Channel.DEFAULT_REMOTE_PORT, username, password, autoCloseMills);
+			}
+			
+			@Override
+			public Channel build(String remoteHost, String username, File privateKeyFile) throws Exception{
+				return this.build(remoteHost, Channel.DEFAULT_REMOTE_PORT , username, privateKeyFile);
+			}
+
+			@Override
+			public Channel build(String remoteHost, String username, File privateKeyFile, long autoCloseMills) throws Exception{
+				return this.build(remoteHost, Channel.DEFAULT_REMOTE_PORT , username, privateKeyFile, autoCloseMills);				
+			}
+			
+			@Override
+			public Channel build(String remoteHost, int remotePort, String username, String password) throws Exception {
+				return this.build(remoteHost, remotePort, username, password,-1);
+			}
+
+			@Override
+			public Channel build(String remoteHost, int remotePort, String username, File privateKeyFile) throws Exception {
+				return this.build(remoteHost, remotePort, username, privateKeyFile, -1);
+			}
+			
+			@Override
+			public Channel build(String remoteHost, int remotePort, String username, String password, long autoCloseMills) throws Exception {	
+				if(StringUtils.isBlank(remoteHost)) {throw new SystemException("remoteHost is empty.");}
+				if(StringUtils.isBlank(username)) {throw new SystemException("username is empty.");}
+				if(StringUtils.isBlank(password)) {throw new SystemException("password is empty.");}
+
+				String key = ChannelContext.buildKey(AuthType.password,remoteHost,username,password);
+				if(!ChannelContext.isExist(key)) {ChannelContext.add(key,new Channel(remoteHost,remotePort,username,password,autoCloseMills,fileUtil));}		
+				return ChannelContext.get(key);
+			}
+
+			@Override
+			public Channel build(String remoteHost, int remotePort, String username, File privateKeyFile, long autoCloseMills) throws Exception {
+				if(StringUtils.isBlank(remoteHost)) {throw new SystemException("remoteHost is empty.");}
+				if(StringUtils.isBlank(username)) {throw new SystemException("username is empty.");}
+				if(privateKeyFile==null) {throw new SystemException("privateKeyFile is null.");}
+				
+				String key = ChannelContext.buildKey(AuthType.privatekey,remoteHost,username,privateKeyFile.getPath());
+				if(!ChannelContext.isExist(key)) {ChannelContext.add(key,new Channel(remoteHost,remotePort,username,privateKeyFile,autoCloseMills,fileUtil));}
+				return ChannelContext.get(key);
+			}
+
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 203 - 0
src/main/java/kr/co/iya/base/support/util/SystemUtilPack.java

@@ -0,0 +1,203 @@
+package kr.co.iya.base.support.util;
+
+import java.lang.management.ManagementFactory;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.core.env.Environment;
+
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+public final class SystemUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface SystemUtil {
+
+		public final static String PROFILE_LOCAL="local";
+		public final static String PROFILE_DEV="dev";
+		public final static String PROFILE_QA="qa";
+		public final static String PROFILE_PROD="prod";	
+
+		public Environment getEnvironment();
+		public Map<String,String> getSystemInfo();
+		public Map<String,String> getSystemUsage();
+		
+		public String getApplicationName();
+		public String getServerName();			
+		public String getServerIp();
+		public String getServerPort();
+		public String getServerIpPort();
+		public String getServerInstanceCode();
+		public String getServerInstanceCode(String serverIpPort);
+		public String getServerInstanceCode(String serverIp,String serverPort);
+		public String getHeartBeat();
+		public String[] getActiveProfiles();
+		public boolean isLocal();
+		public boolean isDev();
+		public boolean isStg();
+		public boolean isProd();
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static SystemUtil getSystemUtil(Environment environment, String serverName, String serverPort, String applicationName) {
+		if(environment==null) {log.debug(">> param[environment] is null."); return null;}
+		if(serverPort==null) {log.debug(">> param[serverPort] is null."); return null;}
+
+		return new SystemUtil() {
+
+			private Map<String,String> systemInfo=null;
+			
+			@Override
+			public Map<String,String> getSystemInfo(){
+				if(this.systemInfo==null) {
+					this.systemInfo = new HashMap<>();
+					systemInfo.put("serverName", this.getServerName());
+					systemInfo.put("serverIp", this.getServerIp());
+					systemInfo.put("serverPort", this.getServerPort());
+					systemInfo.put("serverInstanceCode", this.getServerInstanceCode());
+				}
+				return this.systemInfo;				
+			}
+
+			@Override
+			@SuppressWarnings("restriction")
+			public Map<String,String> getSystemUsage() {
+				com.sun.management.OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(com.sun.management.OperatingSystemMXBean.class);        
+		        Map<String,String> map = new HashMap<>();
+		        map.put("1.cpu",String.format("%.2f",osBean.getSystemCpuLoad()*100));
+		        map.put("2.memory.free",String.format("%.2f", (double)osBean.getFreePhysicalMemorySize()/1024/1024/1024));
+		        map.put("3.memory.total",String.format("%.2f", (double)osBean.getTotalPhysicalMemorySize()/1024/1024/1024));
+		        return map;
+		    }
+			
+			public String getApplicationName() {
+				return applicationName;
+			}
+			
+			@Override
+			public Environment getEnvironment() {
+				return environment;
+			}
+
+			@Override
+			public String getServerName() {
+				if(serverName==null || serverName.equals("")) {
+					try {return InetAddress.getLocalHost().getHostName();}
+					catch (UnknownHostException e) {
+						log.debug(">> exception skip: {}",e.getMessage());
+						//e.printStackTrace();
+					}
+				}
+				return serverName;
+			}
+
+			@Override
+			public String getServerIp() {
+				String hostAddress=null;
+
+				if(!StringUtils.isEmpty(this.getServerPort())) {
+					try(DatagramSocket socket = new DatagramSocket()) {
+						socket.connect(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), Integer.parseInt(this.getServerPort()));
+						hostAddress = socket.getLocalAddress().getHostAddress();		
+						//log.debug("socket.getLocalAddress().getHostAddress():{}",hostAddress);
+					} catch (Exception e) {
+						log.debug(">> exception skip: {}",e.getMessage());
+						//e.printStackTrace();
+					}
+				}
+				
+				if(hostAddress==null || hostAddress.equals("")) {hostAddress="127.0.0.1";}
+				return hostAddress;
+			}
+			
+			@Override
+			public String getServerPort() {
+				return serverPort;
+			}
+					
+			@Override
+			public String getServerIpPort() {
+				return this.getServerIp()+":"+this.getServerPort();
+			}
+			
+			@Override
+			public String getServerInstanceCode() {
+				return this.getServerInstanceCode(this.getServerIp(),serverPort);
+			}
+			
+			@Override
+			public String getServerInstanceCode(String serverIpPort) {
+				if(serverIpPort==null || serverIpPort.equals("")) {return null;}
+				if(serverIpPort.indexOf(":")==-1) {return this.getServerInstanceCode(serverIpPort,"");}
+				
+				String[] array=serverIpPort.split("\\:");
+				if(array.length==2) {return this.getServerInstanceCode(array[0],array[1]);}			
+
+				return null;
+			}
+			
+			@Override
+			public String getServerInstanceCode(String serverIp,String serverPort) {
+				if(serverIp==null || serverIp.equals("")) {return null;}
+				
+				String[] array=serverIp.split("\\.");
+				String hexCode="";
+				for(int i=0;i<array.length;i++) {
+					hexCode += Integer.toHexString(Integer.parseInt(array[i]));
+				}
+				hexCode+=serverPort;
+				return hexCode;
+			}
+
+			@Override
+			public String getHeartBeat() {
+				return this.getServerInstanceCode()+".heartBeat:"+System.currentTimeMillis();
+			}
+		    
+			@Override
+			public String[] getActiveProfiles() {
+				return environment.getActiveProfiles();
+			}
+
+			@Override
+			public boolean isLocal() {
+				return this.isProfile(PROFILE_LOCAL);				
+			}
+
+			@Override
+			public boolean isDev() {
+				return this.isProfile(PROFILE_DEV);
+			}
+
+			@Override
+			public boolean isStg() {
+				return this.isProfile(PROFILE_QA);								
+			}
+
+			@Override
+			public boolean isProd() {
+				return this.isProfile(PROFILE_PROD);				
+			}
+			
+			private boolean isProfile(String target) {
+				String[] profiles = this.getActiveProfiles();
+				if(profiles==null || profiles.length==0) {return false;}
+				for(String profile:profiles) {
+					if(profile.equals(target)) {return true;}
+				}
+				return false;
+			}
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 130 - 0
src/main/java/kr/co/iya/base/support/util/ThreadUtilPack.java

@@ -0,0 +1,130 @@
+package kr.co.iya.base.support.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class ThreadUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@FunctionalInterface
+	public interface MetaFunction {public abstract Object execute();}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface ThreadUtil {
+
+		Map<String, Object> execute(List<ThreadJob> list)  throws Exception;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public static class ThreadJob {
+		private String id;
+		private MetaFunction metafunction;
+
+		public ThreadJob(MetaFunction metafunction) {
+			this(null,metafunction);
+		}
+
+		public ThreadJob(String id,MetaFunction metafunction) {
+			this.id = id;
+			this.metafunction = metafunction;
+		}
+
+		public String getId() {return id;}
+		public void setId(String id) {this.id = id;}
+
+		public MetaFunction getMetaFunction() {return metafunction;}
+		public void setMetaFunction(MetaFunction metafunction) {this.metafunction = metafunction;}
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static ThreadUtil getThreadUtil() {
+
+		return new ThreadUtil() {
+
+			@Override
+			public Map<String, Object> execute(List<ThreadJob> list) throws Exception {
+				
+				List<JobRunnable> threadList = new ArrayList<>();
+				Set<String> idSet = new HashSet<>();
+				
+				for(int i=0;i<list.size();i++) {
+					ThreadJob job = list.get(i);
+					
+					if(StringUtils.isBlank(job.getId())) {job.setId("job"+i);}
+					if(idSet.contains(job.getId())) {throw new RuntimeException("ThreadJob's id ["+job.getId()+"] is duplicate.");}
+					threadList.add(new JobRunnable(job.getId(),job.getMetaFunction()));
+					idSet.add(job.getId());
+				}
+				if(threadList==null || threadList.isEmpty()) {return  null;}
+				
+				log.debug(">> start.thread:{}", idSet.toString());
+				long time = System.nanoTime();
+				
+				Map<String, Object> resultMerge = new HashMap<>();
+
+				//하나의 loop로 병합 금지
+				for(JobRunnable thread:threadList) {thread.start();}
+				for(JobRunnable thread:threadList) {thread.join();}
+				for(JobRunnable thread:threadList) {resultMerge.putAll(thread.getResult());}
+
+				log.debug(">> end.thread:{}, executeTime(ms):{}", idSet.toString(),TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-time));
+
+				return resultMerge;
+			}};
+
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static class JobRunnable implements Runnable {
+		private String id;
+		private MetaFunction metaFunction;
+		private Thread thread;
+		private Object resultObj;
+		
+		public JobRunnable(String id, MetaFunction metaFunction){
+			this.id = id;
+			this.metaFunction = metaFunction;
+			this.thread = new Thread(this, "thread."+id);
+		}
+		
+		@Override
+		public void run() {			
+			log.debug(">> thread.start:{}", this.thread.getName());
+			long time = System.nanoTime();
+			this.resultObj = this.metaFunction.execute();
+			log.debug(">> thread.end:{}, executeTime(ms):{}", this.thread.getName(),TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-time));	
+		}
+
+		public void start(){
+			this.thread.start();
+		}
+		
+		public void join() throws Exception{
+			this.thread.join();
+		}
+		
+		public Map<String,Object> getResult(){
+			Map<String,Object> result = new HashMap<>();
+			result.put(this.id, this.resultObj);
+			return result;
+		}
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 268 - 0
src/main/java/kr/co/iya/base/support/util/TimeUtilPack.java

@@ -0,0 +1,268 @@
+package kr.co.iya.base.support.util;
+
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.lang3.StringUtils;
+
+import kr.co.iya.base.exception.SystemException;
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+public final class TimeUtilPack {
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface TimeUtil {
+
+		public String getCurrentTime();
+		public String getCurrentTime(String format);
+
+		public Date getDate(String date,String format);
+		
+		public String getFormatDate(long timestamp);
+		public String getFormatDate(long timestamp,String format);
+		
+		public String getFormatDate(Date date);
+		public String getFormatDate(Date date,String format);
+		
+		public String getFormatDate(String date,String sourceFormat,String toFormat);
+
+		public String addYear(String date,int addYear);
+		public String addYear(String date,String format,int addYear);
+		
+		public String addMonth(String date,int addMonth);
+		public String addMonth(String date,String format,int addMonth);
+		
+		public String addDay(String date,int addDay);
+		public String addDay(String date,String format,int addDay);
+
+		public String addHour(String date,int addHour);
+		public String addHour(String date,String format,int addHour);
+
+		public String addMinute(String date,int addMinute);
+		public String addMinute(String date,String format,int addMinute);
+
+		public String addSecond(String date,int addSecond);
+		public String addSecond(String date,String format,int addSecond);
+
+		public String addDetailTime(String date,int addYear,int addMonth,int addDay,int addHour,int addMinute,int addSecond);
+		public String addDetailTime(String date,String format,int addYear,int addMonth,int addDay,int addHour,int addMinute,int addSecond);
+
+	 	public long getDifferenceDays(String date1,String date2);
+	 	public long getDifferenceHours(String date1,String date2);
+	 	public long getDifferenceMinutes(String date1,String date2);
+	 	public long getDifferenceSeconds(String date1,String date2);
+	 	public long getDifferenceMillis(String date1,String date2);
+	 	public long getDifference(String date1,String format1,String date2,String format2);
+	 	
+	 	public int getDayOfWeek();
+	 		 	
+	 	public long getTimeStamp(String date,String format);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public static TimeUtil getTimeUtil() {
+
+		return new TimeUtil() {
+			@Override
+			public String getCurrentTime() {
+				return this.getCurrentTime(null);
+			}
+			
+			@Override
+			public String getCurrentTime(String format) {
+				String simpleDateFormat=format;
+				if(simpleDateFormat==null || simpleDateFormat.trim().equals("")) {simpleDateFormat="yyyyMMddHHmmss";}	
+				return (new SimpleDateFormat(simpleDateFormat).format(new Date(System.currentTimeMillis()))).toString();
+			}
+
+			@Override
+			public Date getDate(String date,String format){
+				Date result = null;
+				try {
+					result = (new SimpleDateFormat(format)).parse(StringUtils.rightPad((date==null)?"":date,format.length(),"0"));
+				} catch (ParseException e) {
+					log.debug(">> exception skip: {}",e.getMessage());
+					//e.printStackTrace();
+				}
+				return result;
+			}
+
+			@Override
+			public String getFormatDate(long timestamp) {
+				return this.getFormatDate(new Date(timestamp));
+			}
+			
+			@Override
+			public String getFormatDate(long timestamp,String format) {
+				return this.getFormatDate(new Date(timestamp),format);		
+			}
+			
+			@Override
+			public String getFormatDate(Date date) {
+				return this.getFormatDate(date,"yyyyMMddHHmmss");		
+			}
+			
+			@Override
+			public String getFormatDate(Date date,String format) {
+				if(date==null) {return "";}
+				if(format==null || format.equals("")) {return "";}
+				return (new SimpleDateFormat(format).format(date)).toString();
+			}
+				
+			@Override
+			public String getFormatDate(String source,String sourceFormat,String toFormat) {
+				Date date = null;
+				try {date = this.getDate(source, sourceFormat);}catch(Exception e) {}
+				return this.getFormatDate(date,toFormat);
+			}
+
+			@Override
+			public String addYear(String date,int addYear) {
+				return this.addYear(date,"yyyyMMdd",addYear);
+			}
+
+			@Override
+			public String addYear(String date,String format,int addYear) {
+				return this.addDetailTime(date,format,addYear,0,0,0,0,0);
+			}
+			
+			@Override
+			public String addMonth(String date,int addMonth) {
+				return this.addMonth(date,"yyyyMMdd",addMonth);		
+			}
+			
+			@Override
+			public String addMonth(String date,String format,int addMonth) {
+				return this.addDetailTime(date,format,0,addMonth,0,0,0,0);		
+			}
+
+			@Override
+			public String addDay(String date,int addDay) {
+				return this.addDay(date,"yyyyMMdd",addDay);	
+			}
+			
+			@Override
+			public String addDay(String date,String format,int addDay) {
+				return this.addDetailTime(date,format,0,0,addDay,0,0,0);	
+			}
+
+			@Override
+			public String addHour(String date,int addHour) {
+				return this.addHour(date,"yyyyMMdd",addHour);	
+			}
+			
+			@Override
+			public String addHour(String date,String format,int addHour) {
+				return this.addDetailTime(date,format,0,0,0,addHour,0,0);	
+			}
+
+			@Override
+			public String addMinute(String date,int addMinute) {
+				return this.addMinute(date,"yyyyMMddHHmm",addMinute);
+			}
+			
+			@Override
+			public String addMinute(String date,String format,int addMinute) {
+				return this.addDetailTime(date,format,0,0,0,0,addMinute,0);			
+			}
+			
+			@Override
+			public String addSecond(String date,int addSecond) {
+				return this.addDetailTime(date,"yyyyMMddHHmmss",0,0,0,0,0,addSecond);					
+			}
+			
+			@Override
+			public String addSecond(String date,String format,int addSecond) {
+				return this.addDetailTime(date,format,0,0,0,0,0,addSecond);					
+			}
+
+			@Override
+			public String addDetailTime(String date,int addYear,int addMonth,int addDay,int addHour,int addMinute,int addSecond) {
+				return this.addDetailTime(date,"yyyyMMddHHmmss",addYear,addMonth,addDay,addHour,addMinute,addSecond);
+			}
+
+			@Override
+			public String addDetailTime(String date,String format,int addYear,int addMonth,int addDay,int addHour,int addMinute,int addSecond) {
+				Calendar cal = Calendar.getInstance();
+				cal.setTime(this.getDate(date,format)); 
+			
+				if(addYear!=0) {cal.add(Calendar.YEAR, addYear);}
+				if(addMonth!=0) {cal.add(Calendar.MONTH, addMonth);} 
+				if(addDay!=0) {cal.add(Calendar.DATE, addDay);}
+				if(addHour!=0) {cal.add(Calendar.HOUR, addHour);}
+				if(addMinute!=0) {cal.add(Calendar.MINUTE, addMinute);}
+				if(addSecond!=0) {cal.add(Calendar.SECOND, addSecond);}
+				
+				return this.getFormatDate(cal.getTime(),format);
+			}
+
+			@Override
+		 	public long getDifferenceDays(String date1,String date2) {
+				return this.getDifference(date1,"yyyyMMddHHmmssSSS",date2,"yyyyMMddHHmmssSSS")/(24*60*60*1000);
+			}
+		 	
+			@Override
+		 	public long getDifferenceHours(String date1,String date2) {
+				return this.getDifference(date1,"yyyyMMddHHmmssSSS",date2,"yyyyMMddHHmmssSSS")/(60*60*1000);		 		
+		 	}
+		 	
+			@Override
+		 	public long getDifferenceMinutes(String date1,String date2) {
+				return this.getDifference(date1,"yyyyMMddHHmmssSSS",date2,"yyyyMMddHHmmssSSS")/(60*1000);		 		
+		 	}
+
+			@Override
+		 	public long getDifferenceSeconds(String date1,String date2) {
+				return this.getDifference(date1,"yyyyMMddHHmmssSSS",date2,"yyyyMMddHHmmssSSS")/(1000);		 		
+		 	}
+
+			@Override
+		 	public long getDifferenceMillis(String date1,String date2) {
+				return this.getDifference(date1,"yyyyMMddHHmmssSSS",date2,"yyyyMMddHHmmssSSS");		 		
+			}
+			
+			@Override
+		 	public long getDifference(String date1,String format1,String date2,String format2) {
+				return this.getDate(date2,format2).getTime()-this.getDate(date1,format1).getTime();
+			}
+
+			@Override
+		 	public int getDayOfWeek() {
+				Date date = new Date(System.currentTimeMillis());
+				return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().getDayOfWeek().getValue();
+		 	}
+						
+			@Override
+		 	public long getTimeStamp(String date,String format) {
+				if(date==null || date.equals("")) {return 0;}
+				if(format==null || format.equals("")) {return 0;}
+				
+				SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+				dateFormat.setLenient(false);
+				
+				Timestamp timstamp = null;
+				try {
+				    Date stringToDate = dateFormat.parse(date);
+				    timstamp = new Timestamp(stringToDate.getTime());
+				} catch (ParseException e) {
+				    throw new SystemException(e.getMessage());
+				}
+				
+				return timstamp.getTime();
+			}
+
+		};
+
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 110 - 0
src/main/java/kr/co/iya/base/support/util/TransactionUtilPack.java

@@ -0,0 +1,110 @@
+package kr.co.iya.base.support.util;
+
+import java.util.UUID;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+//@Slf4j
+public final class TransactionUtilPack {
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	public interface TransactionUtil {
+
+	    public TransactionStatus start() throws TransactionException;	  
+	    public TransactionStatus start(int propagationBehavior) throws TransactionException;
+	    public void commit(TransactionStatus txStatus) throws TransactionException;
+	    public void rollback(TransactionStatus txStatus) throws TransactionException;
+	    public void end(TransactionStatus txStatus) throws TransactionException;
+
+	    public String getCurrentTransactionName();
+	    public boolean isLookup(String jndiName);
+	}
+
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public static TransactionUtil getTransactionUtil(PlatformTransactionManager txManager) {
+
+		return new TransactionUtil() {
+	    	// TransactionDefinition.PROPAGATION_REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성
+	    	// TransactionDefinition.PROPAGATION_REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성
+	    	// TransactionDefinition.PROPAGATION_SUPPORT : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 nontransactionally로 실행
+	    	// TransactionDefinition.PROPAGATION_MANDATORY : 부모 트랜잭션 내에서 실행되며 부모 트랜잭션이 없을 경우 예외가 발생
+	    	// TransactionDefinition.PROPAGATION_NOT_SUPPORT : nontransactionally로 실행하며 부모 트랜잭션 내에서 실행될 경우 일시 정지
+	    	// TransactionDefinition.PROPAGATION_NEVER : nontransactionally로 실행되며 부모 트랜잭션이 존재한다면 예외가 발생
+	    	// TransactionDefinition.PROPAGATION_NESTED : 해당 메서드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있음. 둘러싼 트랜잭션이 없을 경우 REQUIRED와 동일하게 작동
+			
+		    @Override
+		    public TransactionStatus start() throws TransactionException {		    	
+		        return this.start(TransactionDefinition.PROPAGATION_REQUIRED);
+		    }
+
+		    @Override
+		    public TransactionStatus start(int propagationBehavior) throws TransactionException {
+		        return this.start(txManager, propagationBehavior);
+		    }
+
+		    @Override
+		    public void commit(TransactionStatus txStatus) throws TransactionException {
+		        if(txStatus!=null && !txStatus.isCompleted()) {
+		            //log.debug(">> transaction commit");
+		            txManager.commit(txStatus);
+		        }
+		    }
+
+		    @Override
+		    public void rollback(TransactionStatus txStatus) throws TransactionException {
+		        if(txStatus!=null && !txStatus.isCompleted()) {
+		            //log.debug(">> transaction rollback");
+		            txManager.rollback(txStatus);
+		        }
+		    }
+
+		    @Override
+		    public void end(TransactionStatus txStatus) throws TransactionException {
+		        //log.debug(">> transaction end");        
+		        txStatus=null;
+		    }
+
+		    @Override
+		    public String getCurrentTransactionName() {
+		    	return TransactionSynchronizationManager.getCurrentTransactionName();
+		    }
+
+		    @Override
+			public boolean isLookup(String jndiName) {
+				boolean isEnable=false;
+				try {
+					Context ctx = new InitialContext();
+					Object obj = ctx.lookup(jndiName);
+					if(obj!=null) {isEnable=true;}
+				} catch (NamingException e) {
+					throw new RuntimeException(e);
+				}
+				return isEnable;
+			}   
+		    
+		    private TransactionStatus start(PlatformTransactionManager txManager,int propagationBehavior) throws TransactionException {
+		    	if(txManager==null) {return null;}
+	            //log.debug(">> transaction start");
+		        DefaultTransactionDefinition txDef=new DefaultTransactionDefinition();
+		        txDef.setPropagationBehavior(propagationBehavior);
+		        txDef.setName("tx"+System.currentTimeMillis()+"#"+UUID.randomUUID().toString());
+		        return txManager.getTransaction(txDef);
+		    }
+		    
+		};
+	}
+	
+	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

+ 37 - 0
src/main/java/kr/co/iya/base/support/view/AttachFileView.java

@@ -0,0 +1,37 @@
+package kr.co.iya.base.support.view;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.servlet.view.AbstractView;
+
+import kr.co.iya.base.support.util.FileUtilPack.FileUtil;
+ 
+public final class AttachFileView extends AbstractView {
+
+	@Autowired
+	private FileUtil fileUtil;
+	
+	@Override
+	protected void renderMergedOutputModel(Map<String,Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		Boolean isCheck=true;
+		
+		if(fileUtil==null) {return;}
+		
+		if(isCheck && model.containsKey("storePath") && model.containsKey("fileName")) {
+			String storePath=(String)model.get("storePath");
+			String fileName=(String)model.get("fileName");
+			this.fileUtil.pushout(storePath, fileName, request, response);
+			isCheck=false;
+		}
+
+		if(isCheck && model.containsKey("storePath")) {
+			String storePath=(String)model.get("storePath");
+			this.fileUtil.pushout(storePath, request, response);
+			isCheck=false;
+		}
+	}
+}

+ 7 - 0
src/main/resources/log4jdbc.log4j2.properties

@@ -0,0 +1,7 @@
+log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
+log4jdbc.dump.sql.maxlinelength=0
+
+#MySQL\uac19\uc740 \uacbd\uc6b0 log4jdbc\uac00 \uae30\ubcf8\uc73c\ub85c com.mysql.jdbc.Driver\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc704\uc5d0 \uc124\uc815\uc744 \ucd94\uac00\ud558\uc9c0 \uc54a\uc73c\uba74 \uc544\ub798\uc640 \uac19\uc740 \ub85c\uadf8\uac00 \ucc0d\ud788\uac8c \ub41c\ub2e4.
+#Loading class 'com.mysql.jdbc.Driver'. This is deprecated. The new driver class is 'com.mysql.cj.jdbc.Driver'.
+#log4jdbc.drivers=com.mysql.cj.jdbc.Driver
+#log4jdbc.auto.load.popular.drivers=false

+ 13 - 0
src/main/resources/lombok.config

@@ -0,0 +1,13 @@
+lombok.log.apacheCommons.flagUsage = error
+lombok.log.log4j.flagUsage = error
+lombok.log.log4j2.flagUsage = error
+lombok.log.xslf4j.flagUsage = error
+lombok.log.fieldName=log
+lombok.log.fieldIsStatic=true
+
+lombok.allArgsConstructor.flagUsage = error
+lombok.requiredArgsConstructor.flagUsage = error
+lombok.data.flagUsage = error
+lombok.equals.flagUsage = error
+lombok.hashCode.flagUsage = error
+lombok.equalsAndhashCode.flagUsage = error

+ 33 - 0
src/main/resources/mask/mask-default.properties

@@ -0,0 +1,33 @@
+############################################################################
+# mask-rule \uc81c\uc57d\uc0ac\ud56d
+#
+#01. key = value\ub85c \uae30\uc220\ub41c\ub2e4.
+#02. Comment \ub294 '#'\uc73c\ub85c \uc2dc\uc791\ub418\uba74 \ub418\ub098, \ubb38\uc790\uc5f4 \uc911\uac04\uc5d0 \uc788\ub294 '#'\uae30\ud638\ub294 Comment\ub85c \uc778\uc2dd\ud558\uc9c0 \uc54a\ub294\ub2e4.
+#03. '=' \ub300\uc2e0 ' '(\uacf5\ubc31)\uc744 \uc0ac\uc6a9\ud574\ub3c4 \ub41c\ub2e4. (\uc989 \uccab\ubc88\uc9f8 \ub098\uc624\ub294 \uacf5\ubc31\uc774 key\uacfc message\ub97c \ub098\ub204\ub294 \uad6c\uc2e4\ub3c4 \ud55c\ub2e4.
+#04. 3\ubc88\uc758 \uc774\uc720\ub85c \uc778\ud558\uc5ec key\uc5d0\ub294 \uc911\uac04\uc5d0 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\ub294\ub2e4.
+#05. \ubc18\uba74, value\uc5d0\ub294 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub41c\ub2e4.
+#06. \ub450 \uc904 \uc774\uc0c1\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \ub77c\uc778\uc758 \ub05d\uc5d0 '\' \ub97c \uc0ac\uc6a9\ud558\uba74 \ub41c\ub2e4. (\ub9c8\uc9c0\ub9c9 \ub77c\uc778\uc5d0\ub294 \uc801\uc6a9\ud558\uc9c0 \uc54a\ub294\ub2e4)
+#07. key,value\ub294 \uc601\uc5b4,\uc22b\uc790\ub85c,\uac00\ub2a5\ud55c-\ud2b9\uc218\ubb38\uc790\ub85c \uad6c\uc131\ud55c\ub2e4(\ud55c\uae00 \uc0ac\uc6a9 \uae08\uc9c0)
+#08. '\' \ubb38\uc790\uac00 \ud544\uc694\ud558\uba74 '\'\uac00 \uc544\ub2c8\ub77c '\\' \uc744 \uc0ac\uc6a9\ud574\uc57c \ud55c\ub2e4. \ub2e8, \ub2e4\uc74c \uc904 ==> & 
+#09. \uc904\uc758 \uccab \uc2dc\uc791 \ube48\uce78\uc740 \uc544\ubb34\ub9ac \ub9ce\ub354\ub77c\ub3c4 \ubb34\uc2dc\ub41c\ub2e4.
+#10. value\uc758 \ub9e8 \ub05d\uc740 \ubc18\ub4dc\uc2dc \ubb38\uc790\ub97c \uae30\uc785\ud574\uc57c \ud55c\ub2e4.
+#11. value\ub294 \ub300\uc18c\ubb38\uc790\ub97c \uad6c\ubd84\ud55c\ub2e4.
+#
+###########################################################################
+mask.default.item={\
+	"name":"maskName('@data')"\
+	,"juminNo":"maskJuminNo('@data')"\
+	,"driverLicense":"maskDriverLicense('@data')"\
+	,"passPort":"maskPassPort('@data')"\
+	,"phone":"maskPhone('@data')"\
+	,"email":"maskEmail('@data')"\
+	,"ip":"maskIp('@data')"\
+	,"address":"maskAddress('@data')"\
+	,"bankAccount":"maskBankAccount('@data')"\
+	,"creditCard":"maskCreditCard('@data')"\
+	,"money":"maskMoney('@data')"\
+	}
+	
+mask.default.list=[${mask.default.item}]
+
+###########################################################################

+ 53 - 0
src/main/resources/message/message-default.properties

@@ -0,0 +1,53 @@
+############################################################################
+# Messag \uc81c\uc57d\uc0ac\ud56d
+#
+#01. key = messag\ub85c \uae30\uc220\ub41c\ub2e4.
+#02. Comment \ub294 '#'\uc73c\ub85c \uc2dc\uc791\ub418\uba74 \ub418\ub098, \ubb38\uc790\uc5f4 \uc911\uac04\uc5d0 \uc788\ub294 '#'\uae30\ud638\ub294 Comment\ub85c \uc778\uc2dd\ud558\uc9c0 \uc54a\ub294\ub2e4.
+#03. '=' \ub300\uc2e0 ' '(\uacf5\ubc31)\uc744 \uc0ac\uc6a9\ud574\ub3c4 \ub41c\ub2e4. (\uc989 \uccab\ubc88\uc9f8 \ub098\uc624\ub294 \uacf5\ubc31\uc774 key\uacfc message\ub97c \ub098\ub204\ub294 \uad6c\uc2e4\ub3c4 \ud55c\ub2e4.
+#04. 3\ubc88\uc758 \uc774\uc720\ub85c \uc778\ud558\uc5ec key\uc5d0\ub294 \uc911\uac04\uc5d0 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\ub294\ub2e4.
+#05. \ubc18\uba74, message\uc5d0\ub294 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub41c\ub2e4.
+#06. \ub450 \uc904 \uc774\uc0c1\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \ub77c\uc778\uc758 \ub05d\uc5d0 '\' \ub97c \uc0ac\uc6a9\ud558\uba74 \ub41c\ub2e4.
+#07. message\ub97c \uc0ac\uc6a9\ud560 \ub54c \ud55c\uae00\uc774 \uc9c0\uc6d0\ub41c\ub2e4.  \uadf8\ub7ec\ub098 key\ub294 \ud55c\uae00\uc9c0\uc6d0\uc774 \uc548\ub41c\ub2e4.
+#08. '\' \ubb38\uc790\uac00 \ud544\uc694\ud558\uba74 '\'\uac00 \uc544\ub2c8\ub77c '\\' \uc744 \uc0ac\uc6a9\ud574\uc57c \ud55c\ub2e4. \ub2e8, \ub2e4\uc74c \uc904 ==> &
+#09. \uc904\uc758 \uccab \uc2dc\uc791 \ube48\uce78\uc740 \uc544\ubb34\ub9ac \ub9ce\ub354\ub77c\ub3c4 \ubb34\uc2dc\ub41c\ub2e4.
+#10. message\uc758 \ub9e8 \ub05d\uc740 \ubc18\ub4dc\uc2dc \ubb38\uc790\ub97c \uae30\uc785\ud574\uc57c \ud55c\ub2e4.
+#11. message\ub294 \ub300\uc18c\ubb38\uc790\ub97c \uad6c\ubd84\ud55c\ub2e4. \ub300\ubb38\uc790\uc0ac\uc6a9\uc744 \uad8c\uc7a5\ud55c\ub2e4.
+#
+###########################################################################
+message.test=\uc548\ub155! {0},{1}
+
+###########################################################################
+# \ud655\uc778(confirm)
+###########################################################################
+sys.confirm.001=\ucc98\ub9ac\ub97c \uc9c4\ud589 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+
+###########################################################################
+# \uc624\ub958(error)
+###########################################################################
+
+sys.error.001=\ucc98\ub9ac\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.
+sys.error.002=\ucc98\ub9ac\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2f4\ub2f9\uc790\uc5d0\uac8c \ud655\uc778\ubc14\ub78d\ub2c8\ub2e4.
+sys.error.003=\ucc98\ub9ac\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ub418\uc5c8\uc2b5\ub2c8\ub2e4.: {0}
+sys.error.004=\uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! : {0},{1}
+sys.error.005=\uc811\uadfc\uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.
+sys.error.006=\uc801\ud569\ud55c \uc694\uccad\ub370\uc774\uac00 \uc544\ub2d9\ub2c8\ub2e4.
+
+sys.error.101=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1}
+
+sys.error.404=Not Found
+###########################################################################
+# \uacbd\uace0(warn)
+###########################################################################
+sys.warn.001=\ucc98\ub9ac\uc2dc \uc624\ub958\uac00 \ubc1c\uc0dd \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.
+sys.warn.002=\uc911\ubcf5\uc694\uccad\uc744 \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.
+
+###########################################################################
+# \uc548\ub0b4(info)
+###########################################################################
+sys.info.001=\ucc98\ub9ac\uac00 \uc644\ub8cc \ub418\uc5c8\uc2b5\ub2c8\ub2e4.
+
+###########################################################################
+# \uac80\uc99d(valid)
+###########################################################################
+sys.valid.001={0}\ub294(\uc740) \ud544\uc218\uc785\ub825 \ud56d\ubaa9\uc785\ub2c8\ub2e4.
+sys.valid.002={0}\ub294(\uc740) \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.

+ 51 - 0
src/main/resources/message/message-default_en.properties

@@ -0,0 +1,51 @@
+############################################################################
+# Messag \uc81c\uc57d\uc0ac\ud56d
+#
+#01. key = messag\ub85c \uae30\uc220\ub41c\ub2e4.
+#02. Comment \ub294 '#'\uc73c\ub85c \uc2dc\uc791\ub418\uba74 \ub418\ub098, \ubb38\uc790\uc5f4 \uc911\uac04\uc5d0 \uc788\ub294 '#'\uae30\ud638\ub294 Comment\ub85c \uc778\uc2dd\ud558\uc9c0 \uc54a\ub294\ub2e4.
+#03. '=' \ub300\uc2e0 ' '(\uacf5\ubc31)\uc744 \uc0ac\uc6a9\ud574\ub3c4 \ub41c\ub2e4. (\uc989 \uccab\ubc88\uc9f8 \ub098\uc624\ub294 \uacf5\ubc31\uc774 key\uacfc message\ub97c \ub098\ub204\ub294 \uad6c\uc2e4\ub3c4 \ud55c\ub2e4.
+#04. 3\ubc88\uc758 \uc774\uc720\ub85c \uc778\ud558\uc5ec key\uc5d0\ub294 \uc911\uac04\uc5d0 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\ub294\ub2e4.
+#05. \ubc18\uba74, message\uc5d0\ub294 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub41c\ub2e4.
+#06. \ub450 \uc904 \uc774\uc0c1\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \ub77c\uc778\uc758 \ub05d\uc5d0 '\' \ub97c \uc0ac\uc6a9\ud558\uba74 \ub41c\ub2e4.
+#07. message\ub97c \uc0ac\uc6a9\ud560 \ub54c \ud55c\uae00\uc774 \uc9c0\uc6d0\ub41c\ub2e4.  \uadf8\ub7ec\ub098 key\ub294 \ud55c\uae00\uc9c0\uc6d0\uc774 \uc548\ub41c\ub2e4.
+#08. '\' \ubb38\uc790\uac00 \ud544\uc694\ud558\uba74 '\'\uac00 \uc544\ub2c8\ub77c '\\' \uc744 \uc0ac\uc6a9\ud574\uc57c \ud55c\ub2e4. \ub2e8, \ub2e4\uc74c \uc904 ==> & 
+#09. \uc904\uc758 \uccab \uc2dc\uc791 \ube48\uce78\uc740 \uc544\ubb34\ub9ac \ub9ce\ub354\ub77c\ub3c4 \ubb34\uc2dc\ub41c\ub2e4.
+#10. message\uc758 \ub9e8 \ub05d\uc740 \ubc18\ub4dc\uc2dc \ubb38\uc790\ub97c \uae30\uc785\ud574\uc57c \ud55c\ub2e4.
+#11. message\ub294 \ub300\uc18c\ubb38\uc790\ub97c \uad6c\ubd84\ud55c\ub2e4. \ub300\ubb38\uc790\uc0ac\uc6a9\uc744 \uad8c\uc7a5\ud55c\ub2e4.
+#
+###########################################################################
+message.test=Hello! {0},{1}
+
+###########################################################################
+# \ud655\uc778(confirm)
+###########################################################################
+sys.confirm.001=Do you want to proceed with processing?
+
+###########################################################################
+# \uc624\ub958(error)
+###########################################################################
+sys.error.001=An error occurred during processing.
+sys.error.002=An error occurred during processing. Please check with the person in charge.
+sys.error.003=An error occurred during processing. : {0}
+sys.error.004=An error has occurred! : {0},{1}
+sys.error.005=No access permission.
+sys.error.006=Invalid request data
+
+sys.error.101=An sql error has occurred! error code: {0}, error msg: {1}
+
+sys.error.404=Not Found
+###########################################################################
+# \uacbd\uace0(warn)
+###########################################################################
+sys.warn.001=There may be errors in processing.
+sys.warn.002=Duplicate requests are not allowed.
+
+###########################################################################
+# \uc548\ub0b4(info)
+###########################################################################
+sys.info.001=Processing is complete.
+
+###########################################################################
+# \uac80\uc99d(valid)
+###########################################################################
+sys.valid.001={0} is required.

+ 51 - 0
src/main/resources/message/message-default_ko.properties

@@ -0,0 +1,51 @@
+############################################################################
+# Messag \uc81c\uc57d\uc0ac\ud56d
+#
+#01. key = messag\ub85c \uae30\uc220\ub41c\ub2e4.
+#02. Comment \ub294 '#'\uc73c\ub85c \uc2dc\uc791\ub418\uba74 \ub418\ub098, \ubb38\uc790\uc5f4 \uc911\uac04\uc5d0 \uc788\ub294 '#'\uae30\ud638\ub294 Comment\ub85c \uc778\uc2dd\ud558\uc9c0 \uc54a\ub294\ub2e4.
+#03. '=' \ub300\uc2e0 ' '(\uacf5\ubc31)\uc744 \uc0ac\uc6a9\ud574\ub3c4 \ub41c\ub2e4. (\uc989 \uccab\ubc88\uc9f8 \ub098\uc624\ub294 \uacf5\ubc31\uc774 key\uacfc message\ub97c \ub098\ub204\ub294 \uad6c\uc2e4\ub3c4 \ud55c\ub2e4.
+#04. 3\ubc88\uc758 \uc774\uc720\ub85c \uc778\ud558\uc5ec key\uc5d0\ub294 \uc911\uac04\uc5d0 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\ub294\ub2e4.
+#05. \ubc18\uba74, message\uc5d0\ub294 \uacf5\ubc31\uc774 \ud5c8\uc6a9\ub41c\ub2e4.
+#06. \ub450 \uc904 \uc774\uc0c1\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \ub77c\uc778\uc758 \ub05d\uc5d0 '\' \ub97c \uc0ac\uc6a9\ud558\uba74 \ub41c\ub2e4.
+#07. message\ub97c \uc0ac\uc6a9\ud560 \ub54c \ud55c\uae00\uc774 \uc9c0\uc6d0\ub41c\ub2e4.  \uadf8\ub7ec\ub098 key\ub294 \ud55c\uae00\uc9c0\uc6d0\uc774 \uc548\ub41c\ub2e4.
+#08. '\' \ubb38\uc790\uac00 \ud544\uc694\ud558\uba74 '\'\uac00 \uc544\ub2c8\ub77c '\\' \uc744 \uc0ac\uc6a9\ud574\uc57c \ud55c\ub2e4. \ub2e8, \ub2e4\uc74c \uc904 ==> & 
+#09. \uc904\uc758 \uccab \uc2dc\uc791 \ube48\uce78\uc740 \uc544\ubb34\ub9ac \ub9ce\ub354\ub77c\ub3c4 \ubb34\uc2dc\ub41c\ub2e4.
+#10. message\uc758 \ub9e8 \ub05d\uc740 \ubc18\ub4dc\uc2dc \ubb38\uc790\ub97c \uae30\uc785\ud574\uc57c \ud55c\ub2e4.
+#11. message\ub294 \ub300\uc18c\ubb38\uc790\ub97c \uad6c\ubd84\ud55c\ub2e4. \ub300\ubb38\uc790\uc0ac\uc6a9\uc744 \uad8c\uc7a5\ud55c\ub2e4.
+#
+###########################################################################
+message.test=\uc548\ub155! {0},{1}
+
+###########################################################################
+# \ud655\uc778(confirm)
+###########################################################################
+sys.confirm.001=\ucc98\ub9ac\ub97c \uc9c4\ud589 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+
+###########################################################################
+# \uc624\ub958(error)
+###########################################################################
+sys.error.001=\ucc98\ub9ac\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.
+sys.error.002=\ucc98\ub9ac\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2f4\ub2f9\uc790\uc5d0\uac8c \ud655\uc778\ubc14\ub78d\ub2c8\ub2e4.
+sys.error.003=\ucc98\ub9ac\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ub418\uc5c8\uc2b5\ub2c8\ub2e4.: {0}
+sys.error.004=\uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! : {0},{1}
+sys.error.005=\uc811\uadfc\uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.
+sys.error.006=\uc801\ud569\ud55c \uc694\uccad\ub370\uc774\uac00 \uc544\ub2d9\ub2c8\ub2e4.
+
+sys.error.101=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1}
+
+sys.error.404=Not Found
+###########################################################################
+# \uacbd\uace0(warn)
+###########################################################################
+sys.warn.001=\ucc98\ub9ac\uc2dc \uc624\ub958\uac00 \ubc1c\uc0dd \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.
+sys.warn.002=\uc911\ubcf5\uc694\uccad\uc744 \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.
+
+###########################################################################
+# \uc548\ub0b4(info)
+###########################################################################
+sys.info.001=\ucc98\ub9ac\uac00 \uc644\ub8cc \ub418\uc5c8\uc2b5\ub2c8\ub2e4.
+
+###########################################################################
+# \uac80\uc99d(valid)
+###########################################################################
+sys.valid.001={0}\ub294(\uc740) \ud544\uc218\uc785\ub825 \ud56d\ubaa9\uc785\ub2c8\ub2e4.

+ 14 - 0
src/main/resources/mybatis-config.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding= "UTF-8" ?>
+<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > 
+
+<configuration>
+	<settings>
+		<setting name="cacheEnabled" value="false" />
+		<setting name="localCacheScope" value="STATEMENT"/>		
+		<setting name="callSettersOnNulls" value="true"/>
+		<setting name="useGeneratedKeys" value="true" />
+		<setting name="defaultExecutorType" value="REUSE" />
+		<setting name="jdbcTypeForNull" value="NULL" />
+		<setting name="mapUnderscoreToCamelCase" value="true"/>
+	</settings>
+</configuration>

+ 38 - 0
src/test/java/com/lguplus/utcb_base/AppTest.java

@@ -0,0 +1,38 @@
+package kr.co.iya_base;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest 
+    extends TestCase
+{
+    /**
+     * Create the test case
+     *
+     * @param testName name of the test case
+     */
+    public AppTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * @return the suite of tests being tested
+     */
+    public static Test suite()
+    {
+        return new TestSuite( AppTest.class );
+    }
+
+    /**
+     * Rigourous Test :-)
+     */
+    public void testApp()
+    {
+        assertTrue( true );
+    }
+}