diff --git a/pom.xml b/pom.xml index 11ab507..beb9c1e 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ UTF-8 - 1.8 + 17 ${java.version} ${java.version} @@ -210,8 +210,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.8 - 1.8 + ${java.version} + ${java.version} UTF-8 diff --git a/src/main/java/cokr/xit/foundation/ApplicationContainer.java b/src/main/java/cokr/xit/foundation/ApplicationContainer.java index acff280..48684f5 100644 --- a/src/main/java/cokr/xit/foundation/ApplicationContainer.java +++ b/src/main/java/cokr/xit/foundation/ApplicationContainer.java @@ -37,7 +37,7 @@ public class ApplicationContainer implements ApplicationContextAware { private Date startupDate; private boolean secured; - private ApplicationContainer() { + public ApplicationContainer() { try { init(); } catch (Exception e) { diff --git a/src/main/java/cokr/xit/foundation/User.java b/src/main/java/cokr/xit/foundation/User.java index 39e880e..2b37fc4 100644 --- a/src/main/java/cokr/xit/foundation/User.java +++ b/src/main/java/cokr/xit/foundation/User.java @@ -11,8 +11,11 @@ public class User extends AbstractEntity implements Serializable { private String id, + account, name, - password; + password, + institute, + locked; private boolean sealed; /**User가 인증된(로그인한) 사용자인지 반환한다. @@ -25,22 +28,36 @@ public class User extends AbstractEntity implements Serializable { return !UNKNOWN.equals(getId()); } - /**사용자의 아이디를 반환한다. - * @return 사용자의 아이디 + /**사용자 아이디를 반환한다.
+ * 사용자 아이디는 사용자 등록 시 시스템이 생성한다. + * @return 사용자 아이디 */ public String getId() { return id != null ? id : UNKNOWN; } - /**사용자의 아이디를 설정한다. - * @param id 사용자의 아이디 + /**사용자 아이디를 설정한다.
+ * 사용자 아이디는 사용자 등록 시 시스템이 생성한다. + * @param id 사용자 아이디 */ public void setId(String id) { notSealed().id = id; } - public String getUsername() { - return getId(); + /**사용자 계정을 반환한다.
+ * 사용자 계정은 사용자가 입력하는 값으로 {@link #getAccount() 소속 기관 코드}와 조합하여 유일한 값이어야 한다. + * @return 사용자 계정 + */ + public String getAccount() { + return account != null ? account : UNKNOWN; + } + + /**사용자 계정을 설정한다.
+ * 사용자 계정은 사용자가 입력하는 값으로 {@link #getAccount() 소속 기관 코드}와 조합하여 유일한 값이어야 한다. + * @param account 사용자 계정 + */ + public void setAccount(String account) { + this.account = account; } /**사용자의 이름을 반환한다. @@ -71,6 +88,34 @@ public class User extends AbstractEntity implements Serializable { notSealed().password = password; } + /**소속기관을 반환한다. + * @return 소속기관 코드 + */ + public String getInstitute() { + return institute; + } + + /**소속기관을 설정한다. + * @param insititute 소속기관 코드 + */ + public void setInstitute(String insititute) { + this.institute = insititute; + } + + /**사용자 계정의 잠금여부를 반환한다. + * @return 사용자 계정의 잠금여부 + */ + public String getLocked() { + return Assert.ifEmpty(locked, "N"); + } + + /**사용자 계정의 잠금여부를 설정한다. + * @param locked 사용자 계정의 잠금여부 + */ + public void setLocked(String locked) { + this.locked = locked; + } + private User seal() { sealed = true; return this; diff --git a/src/main/java/cokr/xit/foundation/UserInfo.java b/src/main/java/cokr/xit/foundation/UserInfo.java index 8d8f417..c1f85ff 100644 --- a/src/main/java/cokr/xit/foundation/UserInfo.java +++ b/src/main/java/cokr/xit/foundation/UserInfo.java @@ -4,8 +4,6 @@ import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.springframework.stereotype.Component; @@ -18,7 +16,7 @@ public class UserInfo implements Serializable { public static final String NAME = "userInfoProvider", SEC_NAME = "securedUserInfoProvider"; - public static final List ALL_ACTIONS = Stream.of("all-actions").collect(Collectors.toList()); + public static final List ALL_ACTIONS = List.of("all-actions"); /**현재 접속한 사용자의 상세정보를 찾는 객체. * @author mjkhan @@ -100,15 +98,15 @@ public class UserInfo implements Serializable { return getUser().getId(); } - /**사용자의 아이디를 설정한다. - * @param id 사용자의 아이디 - */ - public void setId(String id) { - getUser().setId(id); + public String getUsername() { + return getId(); } - public String getUsername() { - return getUser().getUsername(); + /**사용자의 고유 아이디를 반환한다. + * @return 사용자의 고유 아이디 + */ + public String getAccount() { + return getUser().getAccount(); } /**사용자의 이름을 반환한다. @@ -118,13 +116,6 @@ public class UserInfo implements Serializable { return getUser().getName(); } - /**사용자의 이름을 설정한다. - * @param name 사용자의 이름 - */ - public void setName(String name) { - getUser().setName(name); - } - /**사용자의 비밀번호를 반환한다. * @return 사용자의 비밀번호 */ @@ -132,13 +123,6 @@ public class UserInfo implements Serializable { return getUser().getPassword(); } - /**사용자의 비밀번호를 설정한다. - * @param password 사용자의 비밀번호 - */ - public void setPassword(String password) { - getUser().setPassword(password); - } - /**사용자의 추가정보를 갖는 map을 반환한다. * @return 사용자의 추가정보를 갖는 map */ diff --git a/src/main/java/cokr/xit/foundation/component/AbstractMapper.java b/src/main/java/cokr/xit/foundation/component/AbstractMapper.java index ec93220..a396282 100644 --- a/src/main/java/cokr/xit/foundation/component/AbstractMapper.java +++ b/src/main/java/cokr/xit/foundation/component/AbstractMapper.java @@ -1,7 +1,9 @@ package cokr.xit.foundation.component; import cokr.xit.foundation.ApplicationException; +import cokr.xit.foundation.Assert; import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.data.Convert; import cokr.xit.foundation.data.DataObject; /**매퍼 인터페이스의 베이스 인터페이스.
@@ -80,9 +82,7 @@ import cokr.xit.foundation.data.DataObject; * } * @author mjkhan */ -public interface AbstractMapper extends - cokr.xit.foundation.Assert.Support, - cokr.xit.foundation.data.Convert.Support { +public interface AbstractMapper extends Assert.Support, Convert.Support { /**현재 접근한 사용자의 상세정보를 반환한다. * @return 현재 접근한 사용자의 상세정보 diff --git a/src/main/java/cokr/xit/foundation/test/TestDao.java b/src/main/java/cokr/xit/foundation/test/TestDao.java deleted file mode 100644 index 0d3cc8b..0000000 --- a/src/main/java/cokr/xit/foundation/test/TestDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package cokr.xit.foundation.test; - -import cokr.xit.foundation.component.AbstractDao; - -/**JUnit 테스트 프로그램에서 SQL문을 실행할 때 사용하는 DAO.
- * 주로 테스트 데이터를 지우는데 사용한다.
- * TestDao의 매퍼 파일은 test.xml이다. - * @author mjkhan - */ -//@Repository("testDao") -public class TestDao extends AbstractDao { - /**주어진 INSERT문을 실행한다. - * @param sql INSERT SQL - * @return 저장된 데이터 수 - */ - public int execInsert(String sql) { - return insert("test.insert", params().set("sql", sql)); - } - - /**주어진 UPDATE문을 실행한다. - * @param sql UPDATE SQL - * @return 저장된 데이터 수 - */ - public int execUpdate(String sql) { - return insert("test.update", params().set("sql", sql)); - } - - /**주어진 DELETE문을 실행한다. - * @param sql DELETE SQL - * @return 저장된 데이터 수 - */ - public int execDelete(String sql) { - return insert("test.delete", params().set("sql", sql)); - } - - /**실행된 SQL문을 커밋한다. - */ - public void commit() { - update("test.commit"); - } -} \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/AccessInitializer.java b/src/main/java/cokr/xit/foundation/web/AccessInitializer.java index 887a451..4ffb262 100644 --- a/src/main/java/cokr/xit/foundation/web/AccessInitializer.java +++ b/src/main/java/cokr/xit/foundation/web/AccessInitializer.java @@ -48,6 +48,8 @@ public class AccessInitializer extends RequestInterceptor { @Override public void postHandle(HttpServletRequest hreq, HttpServletResponse hresp, Object handler, ModelAndView mav) throws Exception { + if (mav == null) return; + Map model = mav.getModel(); model.entrySet().stream() .filter(entry -> entry.getValue() instanceof ServiceRequest) diff --git a/src/main/java/cokr/xit/foundation/web/ExceptionController.java b/src/main/java/cokr/xit/foundation/web/ExceptionController.java index 22a4497..b181ef5 100644 --- a/src/main/java/cokr/xit/foundation/web/ExceptionController.java +++ b/src/main/java/cokr/xit/foundation/web/ExceptionController.java @@ -4,6 +4,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -11,7 +12,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; -import cokr.xit.foundation.Access; import cokr.xit.foundation.ApplicationException; /**예외 처리를 위한 정보를 갖는 ModelAndView를 생성하여 반환한다. @@ -25,7 +25,17 @@ public class ExceptionController extends AbstractController { * @return 사용자가 실행을 요청한 uri */ protected String getAction(HttpServletRequest hreq) { - return (String)hreq.getAttribute("javax.servlet.forward.request_uri"); + return currentAccess().getAction(); + } + + protected ModelAndView toErrorPage(HttpServletRequest hreq, Object status, String msgKey) { + return new ModelAndView("error/errorPage") + .addObject("json", ajaxRequest()) + .addObject("path", getAction(hreq)) + .addObject("failed", true) + .addObject("status", status) + .addObject("message", message(msgKey)) + ; } /**pageNotFound 오류의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. @@ -34,11 +44,7 @@ public class ExceptionController extends AbstractController { */ @RequestMapping("/error/pageNotFound.do") public ModelAndView pageNotFound(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", getAction(hreq)) - .addObject("failed", true) - .addObject("status", 404) - .addObject("message", message("pageNotFound")); + return toErrorPage(hreq, 404, "pageNotFound"); } /**세션 만료 시 처리를 위한 정보를 갖는 ModelAndView를 반환한다. @@ -47,12 +53,7 @@ public class ExceptionController extends AbstractController { */ @RequestMapping("/error/sessionExpired.do") public ModelAndView sessionExpired(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", getAction(hreq)) - .addObject("failed", true) - .addObject("status", "sessionExpired") - .addObject("message", message("sessionExpired")) - .addObject("home", true); + return toErrorPage(hreq, "sessionExpired", "sessionExpired"); } /**세션 만료 시 처리를 위한 정보를 갖는 ModelAndView를 반환한다. @@ -61,12 +62,7 @@ public class ExceptionController extends AbstractController { */ @RequestMapping("/error/invalidSession.do") public ModelAndView invalidSession(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", getAction(hreq)) - .addObject("failed", true) - .addObject("status", "invalidSession") - .addObject("message", message("invalidSession")) - .addObject("home", true); + return toErrorPage(hreq, "invalidSession", "invalidSession"); } /**접근 거부 오류의 처리를 위한 정보를 갖는 ModelAndView를 반환한다. @@ -75,11 +71,17 @@ public class ExceptionController extends AbstractController { */ @RequestMapping("/error/accessDenied.do") public ModelAndView accessDenied(HttpServletRequest hreq) { - return new ModelAndView(ajaxRequest() ? "jsonView" : "error/errorPage") - .addObject("path", Access.current().getAction()) - .addObject("failed", true) - .addObject("status", "accessDenied") - .addObject("message", message("accessDenied")); + return toErrorPage(hreq, "accessDenied", "accessDenied"); + } + + public void accessDenied(HttpServletRequest hreq, HttpServletResponse hresp) { + try { + ModelAndView mav = toErrorPage(hreq, "accessDenied", "accessDenied"); + mav.getModel().forEach(hreq::setAttribute); + hreq.getRequestDispatcher("/WEB-INF/jsp/" + mav.getViewName() + ".jsp").forward(hreq, hresp); + } catch (Exception e) { + throw runtimeException(e); + } } /**ApplicationException 처리를 위한 정보를 갖는 ModelAndView를 반환한다. @@ -100,18 +102,13 @@ public class ExceptionController extends AbstractController { public ModelAndView onException(Throwable e) { Throwable cause = rootCause(e); - String action = currentAccess().getAction(), - description = cause.getClass().getSimpleName() + " from " + currentAccess(), - stacktrace = getStackTrace(cause); + String description = cause != null ? ifEmpty(cause.getMessage(), () -> cause.getClass().getSimpleName()) : ""; + String stacktrace = getStackTrace(cause); log().error(description); log().error(stacktrace); - return new ModelAndView(ajaxRequest() || jsonResponse() ? "jsonView" : "error/errorPage") - .addObject("status", 500) - .addObject("failed", true) + return toErrorPage(null, 500, "serverError") .addObject("description", description) - .addObject("path", action) - .addObject("message", cause.getMessage()) .addObject("stacktrace", stacktrace); } diff --git a/src/main/java/cokr/xit/foundation/web/Kookie.java b/src/main/java/cokr/xit/foundation/web/Kookie.java new file mode 100644 index 0000000..59a325b --- /dev/null +++ b/src/main/java/cokr/xit/foundation/web/Kookie.java @@ -0,0 +1,106 @@ +package cokr.xit.foundation.web; + +import java.util.List; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/**쿠키 핸들링 유틸리티로 다음 기능을 지원한다. + *
  • 쿠키값 {@link #get(String) 읽기}
  • + *
  • 쿠키값 {@link #set(String, String) 추가}
  • + *
  • 쿠키값 {@link #remove(String...) 삭제}
  • + *
+ * @author mjkhan + */ +public class Kookie { + private HttpServletRequest hreq; + private HttpServletResponse hresp; + + /**HttpServletRequest를 설정한다. + * @param hreq HttpServletRequest + * @return 현재 Kookie + */ + public Kookie set(HttpServletRequest hreq) { + this.hreq = hreq; + return this; + } + + /**HttpServletResponse를 설정한다. + * @param hresp HttpServletResponse + * @return 현재 Kookie + */ + public Kookie set(HttpServletResponse hresp) { + this.hresp = hresp; + return this; + } + + /**지정하는 이름의 쿠키값을 반환한다. + *
 String kookieValue = new Kookie()
+	 *     .set(hreq)
+	 *     .get("kookieName");
+ * @param name 쿠키 이름 + * @return 쿠키값 + */ + public String get(String name) { + Cookie[] cookies = hreq.getCookies(); + if (cookies == null || cookies.length < 1) return ""; + + for (Cookie cookie: cookies) { + if (cookie.getName().equals(name)) + return cookie.getValue(); + } + return ""; + } + + /**지정하는 이름의 쿠키값을 추가한다. + *
 String kookieValue = new Kookie()
+	 *     .set(hresp)
+	 *     .set("kookieName", "kookieValue");
+ * @param name 쿠키 이름 + * @param value 쿠키값 + * @return 현재 Kookie + */ + public Kookie set(String name, String value) { + Cookie cookie = new Cookie(name, value); + cookie.setMaxAge(Integer.MAX_VALUE); + hresp.addCookie(cookie); + return this; + } + + /**지정하는 이름의 쿠키값을 삭제한다.
+ * 이름을 지정하지 않으면 모든 쿠키값을 삭제한다. + *
 String kookieValue = new Kookie()
+	 *     .set(hreq)
+	 *     .set(hresp)
+	 *     .remove("kookieName0", "kookieName1");
+ * @param names 쿠키 이름 + * @return 현재 Kookie + */ + public Kookie remove(String... names) { + Cookie[] cookies = hreq.getCookies(); + if (cookies == null || cookies.length < 1) return this; + + List cookieNames = List.of(names); + + for (Cookie cookie: cookies) { + if (!cookieNames.isEmpty() + && !cookieNames.contains(cookie.getName())) continue; + + cookie.setValue(""); + cookie.setMaxAge(0); + hresp.addCookie(cookie); + } + return this; + } + + /**현재 Kookie의 HttpServletRequest와 HttpServletResponse를 제거한다.
+ * 현재 Kookie를 객체의 필드로 지정하여 재사용할 때 사용한다. + * @return 현재 Kookie + */ + public Kookie clear() { + hreq = null; + hresp = null; + return this; + } +} \ No newline at end of file