commit 0ca8e2fb20545fb926154627b985a264317f8af0 Author: mjkhan21 Date: Wed May 24 15:28:26 2023 +0900 최초 커밋 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e02725e --- /dev/null +++ b/pom.xml @@ -0,0 +1,114 @@ + + 4.0.0 + + cokr.xit.interfaces + xit-filejob + 23.04.01-SNAPSHOT + jar + + xit-filejob + http://maven.apache.org + + + UTF-8 + + 1.8 + ${java.version} + ${java.version} + + + + + + mvn2s + https://repo1.maven.org/maven2/ + + true + + + true + + + + egovframe + http://maven.egovframe.kr:8080/maven/ + + true + + + false + + + + egovframe2 + http://www.egovframe.go.kr/maven/ + + true + + + false + + + + + maven-public + http://xit.xit-nexus.com:8081/repository/maven-public/ + + + + + + + cokr.xit.base + xit-file + 23.04.01-SNAPSHOT + + + + org.egovframe.rte + org.egovframe.rte.bat.core + 4.1.0 + + + + org.quartz-scheduler + quartz + 2.3.2 + + + + org.mariadb.jdbc + mariadb-java-client + 2.7.2 + + + + + install + ${basedir}/target + ${artifactId}-${version} + + + ${basedir}/src/main/resources + + + ${basedir}/src/test/resources + ${basedir}/src/main/resources + + + + + + + maven-snapshot + http://xit.xit-nexus.com:8081/repository/maven-snapshots/ + + + + maven-release + http://xit.xit-nexus.com:8081/repository/maven-releases/ + + + + \ No newline at end of file diff --git a/src/main/java/cokr/xit/interfaces/filejob/JobConf.java b/src/main/java/cokr/xit/interfaces/filejob/JobConf.java new file mode 100644 index 0000000..3314077 --- /dev/null +++ b/src/main/java/cokr/xit/interfaces/filejob/JobConf.java @@ -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; + +/**파일 작업을 위한 설정 정보. + *

설정 정보는 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 + dirs, + defaults, + dirCodes; + private List> jobs; + private Map> jobMap; + + private JobConf() {} + + /**지정한 type에 해당하는 최상위 디렉토리 경로를 반환한다. + * @param type 디렉토리 유형 + *

+ * @return 지정한 type에 해당하는 최상위 디렉토리 경로 + */ + public String getDir(String type) { + return ifEmpty(dirs != null ? dirs.get(type) : null, ""); + } + + /**유형별 최상위 디렉토리 경로를 설정한다. + * @param dirs 설정 파일의 "dirs" 필드에 지정된 값 + */ + public void setDirs(Map dirs) { + this.dirs = dirs; + } + + /**지정한 key에 해당하는 디폴트 값을 반환한다. + * @param key 디폴트 값의 키 + * @return key에 해당하는 디폴트 값 + */ + public String getDefault(String key) { + return ifEmpty(defaults != null ? defaults.get(key) : null, ""); + } + + /**디폴트 값을 설정한다.
+ * @param defaults 설정 파일의 "defaults" 필드에 지정된 값 + */ + public void setDefaults(Map 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 dirCodes) { + this.dirCodes = dirCodes; + } + + public Map 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> jobs) { + this.jobs = jobs; + } + + public String getJobConf(String jobName, String key) { + Map 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"); +*/ + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/interfaces/filejob/package-info.java b/src/main/java/cokr/xit/interfaces/filejob/package-info.java new file mode 100644 index 0000000..ac406c7 --- /dev/null +++ b/src/main/java/cokr/xit/interfaces/filejob/package-info.java @@ -0,0 +1,3 @@ +/**파일의 수신과 전송을 위한 작업의 설정을 지원하는 클래스를 정의한다. + */ +package cokr.xit.interfaces.filejob; \ No newline at end of file diff --git a/src/main/java/cokr/xit/interfaces/filejob/service/bean/FileJobBean.java b/src/main/java/cokr/xit/interfaces/filejob/service/bean/FileJobBean.java new file mode 100644 index 0000000..c7a4024 --- /dev/null +++ b/src/main/java/cokr/xit/interfaces/filejob/service/bean/FileJobBean.java @@ -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이 수행하는 작업의 이름을 반환한다.
+ * 작업 이름은 {@link JobConf}가 현재 Bean의 작업 설정을 찾는데 사용한다. + * @return 작업 이름 + */ + public abstract String jobName(); + + /**파일 작업 설정을 반환한다. + * @return 파일 작업 설정 + */ + protected JobConf config() { + return JobConf.get(); + } + + /**현재 Bean이 작업 중인지 반환한다.
+ * {@link FileJobServiceBean}이 사용한다. + * @return 작업 여부 + *
  • 작업 중이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isBusy() { + return busy; + } + + /**현재 Bean의 작업 여부를 설정한다.
+ * {@link FileJobServiceBean}이 사용한다. + * @param busy 작업 여부 + *
  • 작업 중이면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public void setBusy(boolean busy) { + this.busy = busy; + } + + /**파일 하나의 작업 결과 + * @author mjkhan + */ + public static class FileStatus { + private boolean success = true; + private Path path; + private HashMap info; + private Throwable cause; + + /**작업이 성공했는지 반환한다. + * @return 작업 성공 여부 + *
  • 성공했으면 true(디폴트)
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean isSuccess() { + return success; + } + + /**작업의 성공 여부를 설정한다. + * @param success 작업 성공 여부 + *
  • 성공했으면 true(디폴트)
  • + *
  • 그렇지 않으면 false
  • + *
+ * @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 추가 정보 유형 + * @param key 추가 정보 키 + * @return 추가 정보 + */ + @SuppressWarnings("unchecked") + public 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; + } + + /**작업 중 발생한 예외를 설정한다.
+ * 예외가 설정되면 {@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 getFilePaths(String dir, Predicate filter) { + try (Stream 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 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 getReceivedFilePaths() { + String receiveDir = receiveDir(), + workingDir = receiveWorkingDir(); + + List paths = getFilePaths(receiveDir, Files::isRegularFile); + move(paths, workingDir); + + return getFilePaths(workingDir, null); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/interfaces/filejob/service/bean/FileJobServiceBean.java b/src/main/java/cokr/xit/interfaces/filejob/service/bean/FileJobServiceBean.java new file mode 100644 index 0000000..dbdfc4f --- /dev/null +++ b/src/main/java/cokr/xit/interfaces/filejob/service/bean/FileJobServiceBean.java @@ -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 execute(FileJobBean fileJob, Supplier task) { + if (fileJob.isBusy()) return null; + + fileJob.setBusy(true); + try { + return task.get(); + } finally { + fileJob.setBusy(false); + } + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/interfaces/filejob/service/bean/package-info.java b/src/main/java/cokr/xit/interfaces/filejob/service/bean/package-info.java new file mode 100644 index 0000000..0cabe37 --- /dev/null +++ b/src/main/java/cokr/xit/interfaces/filejob/service/bean/package-info.java @@ -0,0 +1,3 @@ +/**파일의 수신과 전송을 위한 작업의 구현을 지원하는 클래스를 정의한다. + */ +package cokr.xit.interfaces.filejob.service.bean; \ No newline at end of file