최초 커밋
commit
0ca8e2fb20
@ -0,0 +1,114 @@
|
||||
<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>cokr.xit.interfaces</groupId>
|
||||
<artifactId>xit-filejob</artifactId>
|
||||
<version>23.04.01-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>xit-filejob</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>mvn2s</id>
|
||||
<url>https://repo1.maven.org/maven2/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>egovframe</id>
|
||||
<url>http://maven.egovframe.kr:8080/maven/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>egovframe2</id>
|
||||
<url>http://www.egovframe.go.kr/maven/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>maven-public</id>
|
||||
<url>http://xit.xit-nexus.com:8081/repository/maven-public/</url>
|
||||
</repository>
|
||||
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cokr.xit.base</groupId>
|
||||
<artifactId>xit-file</artifactId>
|
||||
<version>23.04.01-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.egovframe.rte</groupId>
|
||||
<artifactId>org.egovframe.rte.bat.core</artifactId>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<defaultGoal>install</defaultGoal>
|
||||
<directory>${basedir}/target</directory>
|
||||
<finalName>${artifactId}-${version}</finalName>
|
||||
|
||||
<resources>
|
||||
<resource><directory>${basedir}/src/main/resources</directory></resource>
|
||||
</resources>
|
||||
<testResources>
|
||||
<testResource><directory>${basedir}/src/test/resources</directory></testResource>
|
||||
<testResource><directory>${basedir}/src/main/resources</directory></testResource>
|
||||
</testResources>
|
||||
</build>
|
||||
|
||||
<!-- Nexus deploy -->
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>maven-snapshot</id>
|
||||
<url>http://xit.xit-nexus.com:8081/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
|
||||
<repository>
|
||||
<id>maven-release</id>
|
||||
<url>http://xit.xit-nexus.com:8081/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
<!-- Nexus deploy -->
|
||||
</project>
|
@ -0,0 +1,147 @@
|
||||
package cokr.xit.interfaces.filejob;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import cokr.xit.foundation.AbstractComponent;
|
||||
import cokr.xit.foundation.Assert;
|
||||
import cokr.xit.foundation.data.JSON;
|
||||
|
||||
/**파일 작업을 위한 설정 정보.
|
||||
* <p>설정 정보는 conf/file-job.conf 파일의 내용을 읽어 적재한다.
|
||||
*
|
||||
* @author mjkhan
|
||||
*/
|
||||
public class JobConf extends AbstractComponent {
|
||||
private static JobConf conf;
|
||||
|
||||
public static JobConf get() {
|
||||
if (conf == null)
|
||||
try {
|
||||
ClassPathResource res = new ClassPathResource("conf/file-job.conf");
|
||||
conf = new JSON().parse(res.getInputStream(), JobConf.class);
|
||||
} catch (Exception e) {
|
||||
throw runtimeException(e);
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
private Map<String, String>
|
||||
dirs,
|
||||
defaults,
|
||||
dirCodes;
|
||||
private List<Map<String, String>> jobs;
|
||||
private Map<String, Map<String, String>> jobMap;
|
||||
|
||||
private JobConf() {}
|
||||
|
||||
/**지정한 type에 해당하는 최상위 디렉토리 경로를 반환한다.
|
||||
* @param type 디렉토리 유형
|
||||
* <ul><li>"receive": 수신 파일</li>
|
||||
* <li>"send": 전송 파일</li>
|
||||
* <li>"working": 작업 디렉토리</li>
|
||||
* <li>"success": 작업이 성공한 파일의 디렉토리</li>
|
||||
* <li>"fail": 작업이 실패한 파일의 디렉토리</li>
|
||||
* </ul>
|
||||
* @return 지정한 type에 해당하는 최상위 디렉토리 경로
|
||||
*/
|
||||
public String getDir(String type) {
|
||||
return ifEmpty(dirs != null ? dirs.get(type) : null, "");
|
||||
}
|
||||
|
||||
/**유형별 최상위 디렉토리 경로를 설정한다.
|
||||
* @param dirs 설정 파일의 "dirs" 필드에 지정된 값
|
||||
*/
|
||||
public void setDirs(Map<String, String> dirs) {
|
||||
this.dirs = dirs;
|
||||
}
|
||||
|
||||
/**지정한 key에 해당하는 디폴트 값을 반환한다.
|
||||
* @param key 디폴트 값의 키
|
||||
* @return key에 해당하는 디폴트 값
|
||||
*/
|
||||
public String getDefault(String key) {
|
||||
return ifEmpty(defaults != null ? defaults.get(key) : null, "");
|
||||
}
|
||||
|
||||
/**디폴트 값을 설정한다.<br />
|
||||
* @param defaults 설정 파일의 "defaults" 필드에 지정된 값
|
||||
*/
|
||||
public void setDefaults(Map<String, String> defaults) {
|
||||
this.defaults = defaults;
|
||||
}
|
||||
|
||||
/**지정한 key에 해당하는 디렉토리 코드를 반환한다.
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public String getDirCode(String key) {
|
||||
return ifEmpty(dirCodes != null ? dirCodes.get(key) : null, "");
|
||||
}
|
||||
|
||||
public void setDirCodes(Map<String, String> dirCodes) {
|
||||
this.dirCodes = dirCodes;
|
||||
}
|
||||
|
||||
public Map<String, String> getJob(String key) {
|
||||
if (jobMap == null) {
|
||||
if (jobs == null)
|
||||
jobMap = Collections.emptyMap();
|
||||
else
|
||||
jobMap = jobs.stream()
|
||||
.collect(Collectors.toMap(job -> job.get("name"), job -> job));
|
||||
}
|
||||
return Assert.ifEmpty(jobMap.get(key), Collections::emptyMap);
|
||||
}
|
||||
|
||||
public void setJobs(List<Map<String, String>> jobs) {
|
||||
this.jobs = jobs;
|
||||
}
|
||||
|
||||
public String getJobConf(String jobName, String key) {
|
||||
Map<String, String> job = getJob(jobName);
|
||||
return Assert.ifEmpty(job.get(key), () -> getDefault(key));
|
||||
}
|
||||
|
||||
String path(String... str) {
|
||||
return String.join(File.separator, str);
|
||||
}
|
||||
|
||||
public String getDir(String jobName, String dir) {
|
||||
String org = getJob(jobName).get("dirCode");
|
||||
String code = getDirCode(org);
|
||||
return path(getDir(dir), code);
|
||||
}
|
||||
|
||||
public String getWorkingDir(String jobName, String dir) {
|
||||
String org = getJob(jobName).get("dirCode");
|
||||
String code = getDirCode(org);
|
||||
return path(getDir("working"), dir, code);
|
||||
/*
|
||||
return path(getDir(jobName, "working"), dir);
|
||||
*/
|
||||
}
|
||||
|
||||
public String getSuccessDir(String jobName) {
|
||||
String org = getJob(jobName).get("dirCode");
|
||||
String code = getDirCode(org);
|
||||
return path(getDir("success"), "receive", code);
|
||||
/*
|
||||
return path(getDir(jobName, "success"), "receive");
|
||||
*/
|
||||
}
|
||||
|
||||
public String getFailDir(String jobName) {
|
||||
String org = getJob(jobName).get("dirCode");
|
||||
String code = getDirCode(org);
|
||||
return path(getDir("fail"), "receive", code);
|
||||
/*
|
||||
return path(getDir(jobName, "fail"), "receive");
|
||||
*/
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
/**파일의 수신과 전송을 위한 작업의 설정을 지원하는 클래스를 정의한다.
|
||||
*/
|
||||
package cokr.xit.interfaces.filejob;
|
@ -0,0 +1,251 @@
|
||||
package cokr.xit.interfaces.filejob.service.bean;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import cokr.xit.foundation.AbstractComponent;
|
||||
import cokr.xit.interfaces.filejob.JobConf;
|
||||
|
||||
/**파일 작업을 수행하는 클래스가 상속하는 베이스 클래스
|
||||
* @author mjkhan
|
||||
*/
|
||||
public abstract class FileJobBean extends AbstractComponent {
|
||||
private boolean busy;
|
||||
|
||||
/**현재 Bean이 수행하는 작업의 이름을 반환한다.<br />
|
||||
* 작업 이름은 {@link JobConf}가 현재 Bean의 작업 설정을 찾는데 사용한다.
|
||||
* @return 작업 이름
|
||||
*/
|
||||
public abstract String jobName();
|
||||
|
||||
/**파일 작업 설정을 반환한다.
|
||||
* @return 파일 작업 설정
|
||||
*/
|
||||
protected JobConf config() {
|
||||
return JobConf.get();
|
||||
}
|
||||
|
||||
/**현재 Bean이 작업 중인지 반환한다.<br />
|
||||
* {@link FileJobServiceBean}이 사용한다.
|
||||
* @return 작업 여부
|
||||
* <ul><li>작업 중이면 true</li>
|
||||
* <li>그렇지 않으면 false</li>
|
||||
* </ul>
|
||||
*/
|
||||
public boolean isBusy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
/**현재 Bean의 작업 여부를 설정한다.<br />
|
||||
* {@link FileJobServiceBean}이 사용한다.
|
||||
* @param busy 작업 여부
|
||||
* <ul><li>작업 중이면 true</li>
|
||||
* <li>그렇지 않으면 false</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void setBusy(boolean busy) {
|
||||
this.busy = busy;
|
||||
}
|
||||
|
||||
/**파일 하나의 작업 결과
|
||||
* @author mjkhan
|
||||
*/
|
||||
public static class FileStatus {
|
||||
private boolean success = true;
|
||||
private Path path;
|
||||
private HashMap<String, Object> info;
|
||||
private Throwable cause;
|
||||
|
||||
/**작업이 성공했는지 반환한다.
|
||||
* @return 작업 성공 여부
|
||||
* <ul><li>성공했으면 true(디폴트)</li>
|
||||
* <li>그렇지 않으면 false</li>
|
||||
* </ul>
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**작업의 성공 여부를 설정한다.
|
||||
* @param success 작업 성공 여부
|
||||
* <ul><li>성공했으면 true(디폴트)</li>
|
||||
* <li>그렇지 않으면 false</li>
|
||||
* </ul>
|
||||
* @return FileStatus
|
||||
*/
|
||||
public FileStatus setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**작업 파일의 경로를 반환한다.
|
||||
* @return 작업 파일 경로
|
||||
*/
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**작업 파일의 경로를 설정한다.
|
||||
* @param path 작업 파일 경로
|
||||
* @return FileStatus
|
||||
*/
|
||||
public FileStatus setPath(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**키로 설정한 추가 정보를 반환한다.
|
||||
* @param <T> 추가 정보 유형
|
||||
* @param key 추가 정보 키
|
||||
* @return 추가 정보
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
if (isEmpty(info)) return null;
|
||||
|
||||
Object obj = info.get(key);
|
||||
return obj != null ? (T)obj : null;
|
||||
}
|
||||
|
||||
/**추가 정보를 설정한다.
|
||||
* @param key 추가 정보 키
|
||||
* @param obj 추가 정보
|
||||
* @return FileStatus
|
||||
*/
|
||||
public FileStatus set(String key, Object obj) {
|
||||
if (info == null)
|
||||
info = new HashMap<>();
|
||||
info.put(key, obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**작업 중 발생한 예외를 반환한다.
|
||||
* @return 작업 중 발생한 예외
|
||||
*/
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
/**작업 중 발생한 예외를 설정한다.<br />
|
||||
* 예외가 설정되면 {@link #setSuccess(boolean) 성공하지 않은 것으로 간주}한다.
|
||||
* @param cause 작업 중 발생한 예외
|
||||
* @return FileStatus
|
||||
*/
|
||||
public FileStatus setCause(Throwable cause) {
|
||||
this.cause = rootCause(cause);
|
||||
return setSuccess(this.cause == null);
|
||||
}
|
||||
}
|
||||
|
||||
/**수신 파일의 최상위 디렉토리 경로를 반환한다.
|
||||
* @return 수신 파일의 최상위 디렉토리 경로
|
||||
*/
|
||||
protected String receiveDir() {
|
||||
return config().getDir(jobName(), "receive");
|
||||
}
|
||||
|
||||
/**수신 파일을 작업하기 위해 이동할 디렉토리 경로를 반환한다.
|
||||
* @return 수신 파일을 작업하기 위해 이동할 디렉토리 경로
|
||||
*/
|
||||
protected String receiveWorkingDir() {
|
||||
return config().getWorkingDir(jobName(), "receive");
|
||||
}
|
||||
|
||||
/**전송 파일의 최상위 디렉토리 경로를 반환한다.
|
||||
* @return 전송 파일의 최상위 디렉토리 경로
|
||||
*/
|
||||
protected String sendDir() {
|
||||
return config().getDir(jobName(), "send");
|
||||
}
|
||||
|
||||
/**전송할 파일을 작업하는 디렉토리의 경로를 반환한다.
|
||||
* @return 전송할 파일을 작업하는 디렉토리 경로
|
||||
*/
|
||||
protected String sendWorkingDir() {
|
||||
return config().getWorkingDir(jobName(), "send");
|
||||
}
|
||||
|
||||
/**처리한 수신 파일을 이동할 디렉토리의 경로를 반환한다.
|
||||
* @return 처리한 수신 파일을 이동할 디렉토리의 경로
|
||||
*/
|
||||
protected String successDir() {
|
||||
return config().getSuccessDir(jobName());
|
||||
}
|
||||
|
||||
/**처리하지 못한 수신 파일을 이동할 디렉토리의 경로를 반환한다.
|
||||
* @return 처리하지 못한 수신 파일을 이동할 디렉토리의 경로
|
||||
*/
|
||||
protected String failDir() {
|
||||
return config().getFailDir(jobName());
|
||||
}
|
||||
|
||||
/**파일 작업 설정값을 반환한다.
|
||||
* @param key 설정값의 키
|
||||
* @return 설정값
|
||||
*/
|
||||
protected String config(String key) {
|
||||
return config().getJobConf(jobName(), key);
|
||||
}
|
||||
|
||||
/**지정한 디렉토리의 파일들의 경로를 반환한다.
|
||||
* @param dir 디렉토리 경로
|
||||
* @param filter 필터. 지정하지 않으면 모든 파일을 대상
|
||||
* @return 파일 경로 목록
|
||||
*/
|
||||
protected List<Path> getFilePaths(String dir, Predicate<Path> filter) {
|
||||
try (Stream<Path> walk = Files.list(Paths.get(dir))) {
|
||||
return walk.filter(ifEmpty(filter, path -> true)).collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
throw runtimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**지정하는 경로의 파일들을 이동한다.
|
||||
* @param paths 파일 경로 목록
|
||||
* @param destDir 이동할 디렉토리
|
||||
*/
|
||||
protected void move(List<Path> paths, String destDir) {
|
||||
if (isEmpty(paths)) return;
|
||||
|
||||
mkdirs(destDir);
|
||||
|
||||
paths.forEach(src -> {
|
||||
try {
|
||||
Path dest = Paths.get(destDir + File.separator + src.getFileName());
|
||||
Files.move(src, dest);
|
||||
log().debug(src.toAbsolutePath() + " -> " + dest.toAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
throw runtimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**지정한 경로의 디렉토리를 만든다.
|
||||
* @param dirPath 디렉토리 경로
|
||||
*/
|
||||
protected void mkdirs(String dirPath) {
|
||||
File dir = new File(dirPath);
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
/**수신 디렉토리의 파일들을 작업 디렉토리로 이동하고, 경로를 반환한다.
|
||||
* @return 수신 작업 디렉토리의 파일 경로 목록
|
||||
*/
|
||||
protected List<Path> getReceivedFilePaths() {
|
||||
String receiveDir = receiveDir(),
|
||||
workingDir = receiveWorkingDir();
|
||||
|
||||
List<Path> paths = getFilePaths(receiveDir, Files::isRegularFile);
|
||||
move(paths, workingDir);
|
||||
|
||||
return getFilePaths(workingDir, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package cokr.xit.interfaces.filejob.service.bean;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import cokr.xit.foundation.component.AbstractServiceBean;
|
||||
|
||||
/**수신 파일의 처리, 파일의 생성/전송을 수행하는 서비스 구현체가 상속받는 베이스 클래스
|
||||
* @author mjkhan
|
||||
*/
|
||||
public class FileJobServiceBean extends AbstractServiceBean {
|
||||
/**주어진 작업을 수행한다. fileJob이 작업 중이면 무시한다.
|
||||
* @param fileJob 파일 작업 Bean
|
||||
* @param task 파일 작업 Bean의 메소드
|
||||
*/
|
||||
protected void execute(FileJobBean fileJob, Runnable task) {
|
||||
if (fileJob.isBusy()) return;
|
||||
|
||||
fileJob.setBusy(true);
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
fileJob.setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**주어진 작업을 수행하고 결과를 반환한다. fileJob이 작업 중이면 무시하고 null을 반환한다.
|
||||
* @param fileJob 파일 작업 Bean
|
||||
* @param task 파일 작업 Bean의 메소드
|
||||
* @return 작업 결과
|
||||
*/
|
||||
protected <T> T execute(FileJobBean fileJob, Supplier<T> task) {
|
||||
if (fileJob.isBusy()) return null;
|
||||
|
||||
fileJob.setBusy(true);
|
||||
try {
|
||||
return task.get();
|
||||
} finally {
|
||||
fileJob.setBusy(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
/**파일의 수신과 전송을 위한 작업의 구현을 지원하는 클래스를 정의한다.
|
||||
*/
|
||||
package cokr.xit.interfaces.filejob.service.bean;
|
Loading…
Reference in New Issue