diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 716f38a1d8..4f24da4b03 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -4,7 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.org.springframework.web.WebApplicationInitializer; -import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping.AnnotationHandlerMapping; import webmvc.org.springframework.web.servlet.mvc.tobe.DispatcherServlet; /** @@ -20,7 +20,6 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { @Override public void onStartup(final ServletContext servletContext) { final var dispatcherServlet = new DispatcherServlet( - new ManualHandlerMapping(), new AnnotationHandlerMapping(getClass().getPackage().getName()) ); diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java deleted file mode 100644 index e84b3fc033..0000000000 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.techcourse; - -import com.techcourse.controller.LoginController; -import com.techcourse.controller.LoginViewController; -import com.techcourse.controller.LogoutController; -import com.techcourse.controller.RegisterViewController; -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; -import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController; -import webmvc.org.springframework.web.servlet.mvc.tobe.Handler; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping; - -public class ManualHandlerMapping implements HandlerMapping { - - private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class); - - private static final Map controllers = new HashMap<>(); - - @Override - public void initialize() { - controllers.put("/", new ForwardController("/index.jsp")); - controllers.put("/login", new LoginController()); - controllers.put("/login/view", new LoginViewController()); - controllers.put("/logout", new LogoutController()); - controllers.put("/register/view", new RegisterViewController()); - - log.info("Initialized Handler Mapping!"); - controllers.keySet() - .forEach(path -> log.info("Path : {}, Controller : {}", path, - controllers.get(path).getClass())); - } - - @Override - public Handler getHandler(final HttpServletRequest request) { - return controllers.get(request.getRequestURI()); - } -} diff --git a/app/src/main/java/com/techcourse/controller/LoginController.java b/app/src/main/java/com/techcourse/controller/LoginController.java index 0428fe109e..5c64b47e86 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -2,28 +2,47 @@ import com.techcourse.domain.User; import com.techcourse.repository.InMemoryUserRepository; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; -public class LoginController implements Controller { +@Controller +public class LoginController { private static final Logger log = LoggerFactory.getLogger(LoginController.class); - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(method = RequestMethod.GET, value = "/login/view") + public ModelAndView show(final HttpServletRequest req, final HttpServletResponse res) { + final String viewName = UserSession.getUserFrom(req.getSession()) + .map(user -> { + log.info("logged in {}", user.getAccount()); + return "redirect:/index.jsp"; + }) + .orElse("/login.jsp"); + return new ModelAndView(new JspView(viewName)); + } + + @RequestMapping(method = RequestMethod.POST, value = "/login") + public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) + throws Exception { if (UserSession.isLoggedIn(req.getSession())) { - return "redirect:/index.jsp"; + return new ModelAndView(new JspView("redirect:/index.jsp")); } - return InMemoryUserRepository.findByAccount(req.getParameter("account")) - .map(user -> { - log.info("User : {}", user); - return login(req, user); - }) - .orElse("redirect:/401.jsp"); + final String viewName = InMemoryUserRepository.findByAccount(req.getParameter("account")) + .map(user -> { + log.info("User : {}", user); + return login(req, user); + }) + .orElse("redirect:/401.jsp"); + + return new ModelAndView(new JspView(viewName)); } private String login(final HttpServletRequest request, final User user) { diff --git a/app/src/main/java/com/techcourse/controller/LoginViewController.java b/app/src/main/java/com/techcourse/controller/LoginViewController.java deleted file mode 100644 index 86ec26cdce..0000000000 --- a/app/src/main/java/com/techcourse/controller/LoginViewController.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.techcourse.controller; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; - -public class LoginViewController implements Controller { - - private static final Logger log = LoggerFactory.getLogger(LoginViewController.class); - - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { - return UserSession.getUserFrom(req.getSession()) - .map(user -> { - log.info("logged in {}", user.getAccount()); - return "redirect:/index.jsp"; - }) - .orElse("/login.jsp"); - } -} diff --git a/app/src/main/java/com/techcourse/controller/LogoutController.java b/app/src/main/java/com/techcourse/controller/LogoutController.java index 4642fd9450..2288b4df41 100644 --- a/app/src/main/java/com/techcourse/controller/LogoutController.java +++ b/app/src/main/java/com/techcourse/controller/LogoutController.java @@ -1,15 +1,20 @@ package com.techcourse.controller; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; -public class LogoutController implements Controller { +@Controller +public class LogoutController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(method = RequestMethod.POST, value = "/logout") + public ModelAndView show(final HttpServletRequest req, final HttpServletResponse res) { final var session = req.getSession(); session.removeAttribute(UserSession.SESSION_KEY); - return "redirect:/"; + return new ModelAndView(new JspView("redirect:/")); } } diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index cdfaf7ab6f..5462446548 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -24,7 +24,7 @@ public ModelAndView save(final HttpServletRequest req, final HttpServletResponse return new ModelAndView(new JspView("redirect:/index.jsp")); } - @RequestMapping(value = "/register", method = RequestMethod.GET) + @RequestMapping(value = "/register/view", method = RequestMethod.GET) public ModelAndView show(HttpServletRequest req, HttpServletResponse res) { return new ModelAndView(new JspView("/register.jsp")); } diff --git a/app/src/main/java/com/techcourse/controller/RegisterViewController.java b/app/src/main/java/com/techcourse/controller/RegisterViewController.java deleted file mode 100644 index 136962136d..0000000000 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.techcourse.controller; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; - -public class RegisterViewController implements Controller { - - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { - return "/register.jsp"; - } -} diff --git a/app/src/main/java/com/techcourse/controller/UserController.java b/app/src/main/java/com/techcourse/controller/UserController.java new file mode 100644 index 0000000000..cada0cc189 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/UserController.java @@ -0,0 +1,32 @@ +package com.techcourse.controller; + +import com.techcourse.domain.User; +import com.techcourse.repository.InMemoryUserRepository; +import context.org.springframework.stereotype.Controller; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JsonView; + +@Controller +public class UserController { + + private static final Logger log = LoggerFactory.getLogger(UserController.class); + + @RequestMapping(value = "/api/user", method = RequestMethod.GET) + public ModelAndView show(HttpServletRequest request, HttpServletResponse response) { + final String account = request.getParameter("account"); + log.debug("user id : {}", account); + + final ModelAndView modelAndView = new ModelAndView(new JsonView()); + final User user = InMemoryUserRepository.findByAccount(account) + .orElseThrow(); + + modelAndView.addObject("user", user); + return modelAndView; + } +} diff --git a/app/src/main/java/com/techcourse/domain/User.java b/app/src/main/java/com/techcourse/domain/User.java index beb0919b7e..d0325fbc54 100644 --- a/app/src/main/java/com/techcourse/domain/User.java +++ b/app/src/main/java/com/techcourse/domain/User.java @@ -18,10 +18,22 @@ public boolean checkPassword(String password) { return this.password.equals(password); } + public long getId() { + return id; + } + public String getAccount() { return account; } + public String getPassword() { + return password; + } + + public String getEmail() { + return email; + } + @Override public String toString() { return "User{" + diff --git a/app/src/test/java/com/techcourse/DispatcherServletTest.java b/app/src/test/java/com/techcourse/DispatcherServletTest.java index a6a1c1da0e..6965f003c2 100644 --- a/app/src/test/java/com/techcourse/DispatcherServletTest.java +++ b/app/src/test/java/com/techcourse/DispatcherServletTest.java @@ -20,8 +20,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; import webmvc.org.springframework.web.servlet.mvc.tobe.DispatcherServlet; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping.AnnotationHandlerMapping; class DispatcherServletTest { @@ -30,7 +30,6 @@ class DispatcherServletTest { @BeforeEach void setUp() { dispatcherServlet = new DispatcherServlet( - new ManualHandlerMapping(), new AnnotationHandlerMapping(getClass().getPackage().getName()) ); dispatcherServlet.init(); @@ -54,6 +53,7 @@ void show() throws ServletException, IOException { //given final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); final RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestURI()).thenReturn("/register/view"); when(request.getMethod()).thenReturn("GET"); when(request.getRequestDispatcher(argumentCaptor.capture())) .thenReturn(requestDispatcher); diff --git a/app/src/test/java/com/techcourse/controller/RegisterControllerTest.java b/app/src/test/java/com/techcourse/controller/RegisterControllerTest.java index 094ab544a9..bc78d4a171 100644 --- a/app/src/test/java/com/techcourse/controller/RegisterControllerTest.java +++ b/app/src/test/java/com/techcourse/controller/RegisterControllerTest.java @@ -56,10 +56,12 @@ void save() { final Optional savedUser = InMemoryUserRepository.findByAccount(account); final Optional expectedUser = Optional.of( new User(unvalidatedId, account, password, email)); + final JspView expectedJspView = new JspView("redirect:/index.jsp"); assertAll( - () -> assertThat(actual.getView().getViewName()) - .isEqualTo("redirect:/index.jsp"), + () -> assertThat(actual.getView()) + .usingRecursiveComparison() + .isEqualTo(expectedJspView), () -> assertThat(savedUser) .usingRecursiveComparison() .ignoringFields("value.id") diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java index 0346a3dda8..2254e2da50 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java @@ -2,11 +2,10 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import java.util.Map; public interface View { - void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; - String getViewName(); + void render(Map model, HttpServletRequest request, HttpServletResponse response) + throws Exception; } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java deleted file mode 100644 index 654c3b947a..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java +++ /dev/null @@ -1,19 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.asis; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.mvc.tobe.Handler; -import webmvc.org.springframework.web.servlet.view.JspView; - -public interface Controller extends Handler { - - String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception; - - @Override - default ModelAndView handle(final HttpServletRequest request, - final HttpServletResponse response) throws Exception { - final String viewName = execute(request, response); - return new ModelAndView(new JspView(viewName)); - } -} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java deleted file mode 100644 index cd8f1ef371..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java +++ /dev/null @@ -1,20 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.asis; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.util.Objects; - -public class ForwardController implements Controller { - - private final String path; - - public ForwardController(final String path) { - this.path = Objects.requireNonNull(path); - } - - @Override - public String execute(final HttpServletRequest request, final HttpServletResponse response) { - return path; - } -} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/DispatcherServlet.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/DispatcherServlet.java index e25a01536a..7ed386852a 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/DispatcherServlet.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/DispatcherServlet.java @@ -4,10 +4,12 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.view.JspView; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping.HandlerMapping; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping.HandlerMappings; public class DispatcherServlet extends HttpServlet { @@ -35,7 +37,7 @@ protected void service(final HttpServletRequest request, final HttpServletRespon final var handler = handlerMappings.getHandler(request); final var modelAndView = handler.handle(request, response); move(modelAndView, request, response); - } catch (final Throwable e) { + } catch (final Exception e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); } @@ -43,13 +45,7 @@ protected void service(final HttpServletRequest request, final HttpServletRespon private void move(final ModelAndView modelAndView, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - final String viewName = modelAndView.getView().getViewName(); - if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { - response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); - return; - } - - final var requestDispatcher = request.getRequestDispatcher(viewName); - requestDispatcher.forward(request, response); + final Map model = modelAndView.getModel(); + modelAndView.getView().render(model, request, response); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/default_controller/ForwardController.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/default_controller/ForwardController.java new file mode 100644 index 0000000000..4933070b2c --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/default_controller/ForwardController.java @@ -0,0 +1,21 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.default_controller; + +import context.org.springframework.stereotype.Controller; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; + +@Controller +public class ForwardController { + + private static final String ROOT_DIR = "/index.jsp"; + + @RequestMapping(value = "/", method = RequestMethod.GET) + public ModelAndView show(final HttpServletRequest request, + final HttpServletResponse response) { + return new ModelAndView(new JspView(ROOT_DIR)); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/Handler.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/Handler.java similarity index 82% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/Handler.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/Handler.java index cb238929f0..564954219f 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/Handler.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/Handler.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/NotFoundHandler.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/NotFoundHandler.java similarity index 89% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/NotFoundHandler.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/NotFoundHandler.java index 902a8f6a5e..2f68519111 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/NotFoundHandler.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/NotFoundHandler.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/AnnotationHandlerMapping.java similarity index 82% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/AnnotationHandlerMapping.java index 34a0a6d151..c936c50ff5 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/AnnotationHandlerMapping.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping; import static java.util.Map.entry; @@ -18,9 +18,13 @@ import web.org.springframework.web.bind.annotation.RequestMapping; import web.org.springframework.web.bind.annotation.RequestMethod; import webmvc.org.springframework.web.servlet.exception.RequestMethodNotValidException; +import webmvc.org.springframework.web.servlet.mvc.tobe.default_controller.ForwardController; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler.Handler; -public class AnnotationHandlerMapping implements HandlerMapping{ +public class AnnotationHandlerMapping implements HandlerMapping { + private static final String DEFAULT_CONTROLLER_PACKAGE = + ForwardController.class.getPackage().getName(); private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); private final Object[] basePackage; @@ -29,12 +33,17 @@ public class AnnotationHandlerMapping implements HandlerMapping{ public AnnotationHandlerMapping(final Object... basePackage) { this.basePackage = basePackage; this.handlerExecutions = new HashMap<>(); + initializeByPackages(DEFAULT_CONTROLLER_PACKAGE); } @Override public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); + initializeByPackages(basePackage); + } + + private void initializeByPackages(final Object... basePackage) { final Set> controllerClazz = new Reflections(basePackage) .getTypesAnnotatedWith(Controller.class); final Map handlerExecutions = controllerClazz diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerExecution.java similarity index 86% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerExecution.java index f56da6da8f..ea75102ab2 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerExecution.java @@ -1,10 +1,11 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import webmvc.org.springframework.web.servlet.ModelAndView; import webmvc.org.springframework.web.servlet.exception.HandlerExecutionNotInitializeException; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler.Handler; public class HandlerExecution implements Handler { diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerKey.java similarity index 92% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerKey.java index 1de908117c..1e41dac9c6 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerKey.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping; import java.util.Objects; import web.org.springframework.web.bind.annotation.RequestMethod; diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerMapping.java similarity index 55% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerMapping.java index d96b7f2bff..577f4ed18f 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerMapping.java @@ -1,6 +1,7 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping; import jakarta.servlet.http.HttpServletRequest; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler.Handler; public interface HandlerMapping { diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappings.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerMappings.java similarity index 79% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappings.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerMappings.java index 7dd1557627..2d0b9391b6 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappings.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/HandlerMappings.java @@ -1,10 +1,12 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping; import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler.Handler; +import webmvc.org.springframework.web.servlet.mvc.tobe.handler.NotFoundHandler; public class HandlerMappings { diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java index f763743304..070751ddf6 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java @@ -1,19 +1,29 @@ package webmvc.org.springframework.web.servlet.view; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.View; - import java.util.Map; +import web.org.springframework.http.MediaType; +import webmvc.org.springframework.web.servlet.View; public class JsonView implements View { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + @Override - public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) throws Exception { + public void render(final Map model, final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + final String convertedModel = convertModel(model); + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + response.getWriter().write(convertedModel); } - @Override - public String getViewName() { - return null; + private String convertModel(final Map model) throws JsonProcessingException { + if (model.size() == 1) { + return OBJECT_MAPPER.writeValueAsString(model.values().toArray()[0]); + } + return OBJECT_MAPPER.writeValueAsString(model); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java index b90018b983..a8cc3e6a71 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java @@ -22,18 +22,17 @@ public JspView(final String viewName) { @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - // todo - model.keySet().forEach(key -> { log.debug("attribute name : {}, value : {}", key, model.get(key)); request.setAttribute(key, model.get(key)); }); - // todo - } + if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { + response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); + return; + } - @Override - public String getViewName() { - return viewName; + final var requestDispatcher = request.getRequestDispatcher(viewName); + requestDispatcher.forward(request, response); } } diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/NotFoundHandlerTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/NotFoundHandlerTest.java similarity index 70% rename from mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/NotFoundHandlerTest.java rename to mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/NotFoundHandlerTest.java index 2f79312fa0..5fd59ba63e 100644 --- a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/NotFoundHandlerTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler/NotFoundHandlerTest.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler; import static org.assertj.core.api.Assertions.assertThat; @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; class NotFoundHandlerTest { @@ -20,8 +21,10 @@ void handle() { final ModelAndView modelAndView = notFoundHandler.handle(request, response); - assertThat(modelAndView.getView().getViewName()) - .isEqualTo("redirect:/404.jsp"); + final JspView expectedJspView = new JspView("redirect:/404.jsp"); + assertThat(modelAndView.getView()) + .usingRecursiveComparison() + .isEqualTo(expectedJspView); } } diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/AnnotationHandlerMappingTest.java similarity index 96% rename from mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java rename to mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/AnnotationHandlerMappingTest.java index f9bc8cbbd4..83d42e4a46 100644 --- a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/handler_mapping/AnnotationHandlerMappingTest.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe.handler_mapping; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JsonViewTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JsonViewTest.java new file mode 100644 index 0000000000..8d5b2634da --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JsonViewTest.java @@ -0,0 +1,62 @@ +package webmvc.org.springframework.web.servlet.view; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import web.org.springframework.http.MediaType; + +class JsonViewTest { + + private final HttpServletRequest request = mock(HttpServletRequest.class); + private final HttpServletResponse response = mock(HttpServletResponse.class); + + @Test + @DisplayName("JspView render Test 값이 한개 일 떄") + void renderTest() throws Exception { + final JsonView jsonView = new JsonView(); + final Map model = new HashMap<>(); + final String stringObject = "string"; + final PrintWriter mockWriter = mock(PrintWriter.class); + given(response.getWriter()).willReturn(mockWriter); + model.put("string object", stringObject); + + //when + jsonView.render(model, request, response); + + //then + final String expectedValue = "\"string\""; + verify(mockWriter, times(1)).write(expectedValue); + verify(response, times(1)).setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + } + + @Test + @DisplayName("JspView render Test 값이 두개 이상일 떄") + void renderTest2() throws Exception { + final JsonView jsonView = new JsonView(); + final Map model = new HashMap<>(); + final PrintWriter mockWriter = mock(PrintWriter.class); + given(response.getWriter()).willReturn(mockWriter); + model.put("string object1", "value1"); + model.put("string object2", "value2"); + + //when + jsonView.render(model, request, response); + + //then + final String expectedValue = String.join(System.lineSeparator(), "{" + + "\"string object1\":\"value1\"," + + "\"string object2\":\"value2\"" + + "}"); + verify(mockWriter, times(1)).write(expectedValue); + verify(response, times(1)).setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + } +} diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JspViewTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JspViewTest.java new file mode 100644 index 0000000000..2037bf37f0 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JspViewTest.java @@ -0,0 +1,37 @@ +package webmvc.org.springframework.web.servlet.view; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class JspViewTest { + + private final HttpServletRequest request = mock(HttpServletRequest.class); + private final HttpServletResponse response = mock(HttpServletResponse.class); + + @Test + @DisplayName("JspView render Test") + void renderTest() throws Exception { + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + final RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestDispatcher(argumentCaptor.capture())) + .thenReturn(requestDispatcher); + final String viewName = "/register.jsp"; + final JspView jspView = new JspView(viewName); + + //when + jspView.render(Map.of(), request, response); + + //then + assertThat(argumentCaptor.getValue()) + .isEqualTo(viewName); + } +} diff --git a/study/src/test/java/di/stage3/context/DIContainer.java b/study/src/test/java/di/stage3/context/DIContainer.java index b62feb1ed3..e6e359c669 100644 --- a/study/src/test/java/di/stage3/context/DIContainer.java +++ b/study/src/test/java/di/stage3/context/DIContainer.java @@ -1,6 +1,11 @@ package di.stage3.context; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스 @@ -10,11 +15,61 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + this.beans = createBeans(classes); + setFields(beans); + } + + private void setFields(final Set beans) { + for (final Object bean : beans) { + final Field[] declaredFields = bean.getClass().getDeclaredFields(); + injectBeans(declaredFields, bean); + } + } + + private void injectBeans(final Field[] declaredFields, final Object bean) { + Arrays.stream(declaredFields) + .filter(field -> filterFieldIsNull(field, bean)) + .forEach(field -> injectBean(field, bean)); + } + + private boolean filterFieldIsNull(final Field field, final Object bean) { + try { + field.setAccessible(true); + return Objects.isNull(field.get(bean)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private void injectBean(final Field field, final Object object) { + try { + field.set(object, getBean(field.getType())); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private Set createBeans(final Set> classes) { + return classes.stream() + .map(this::createDefaultObject) + .collect(Collectors.toSet()); + } + + private Object createDefaultObject(final Class clazz) { + try { + final Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException(e); + } } @SuppressWarnings("unchecked") - public T getBean(final Class aClass) { - return null; + public T getBean(final Class clazz) { + return (T) beans.stream() + .filter(object -> clazz.isAssignableFrom(object.getClass())) + .findFirst() + .orElse(null); } } diff --git a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java index 9dab1fd9c4..12073ce3b5 100644 --- a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java +++ b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java @@ -1,10 +1,14 @@ package di.stage4.annotations; import java.util.Set; +import org.reflections.Reflections; public class ClassPathScanner { public static Set> getAllClassesInPackage(final String packageName) { - return null; + final Set> classes = new Reflections(packageName) + .getTypesAnnotatedWith(Repository.class); + classes.addAll(new Reflections(packageName).getTypesAnnotatedWith(Service.class)); + return classes; } } diff --git a/study/src/test/java/di/stage4/annotations/DIContainer.java b/study/src/test/java/di/stage4/annotations/DIContainer.java index 9248ecad7e..0fec7380e6 100644 --- a/study/src/test/java/di/stage4/annotations/DIContainer.java +++ b/study/src/test/java/di/stage4/annotations/DIContainer.java @@ -1,6 +1,11 @@ package di.stage4.annotations; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스 @@ -10,15 +15,67 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + this.beans = createBeans(classes); + setFields(beans); } public static DIContainer createContainerForPackage(final String rootPackageName) { - return null; + final Set> classes = ClassPathScanner.getAllClassesInPackage(rootPackageName); + return new DIContainer(classes); + } + + private void setFields(final Set beans) { + for (final Object bean : beans) { + final Field[] declaredFields = bean.getClass().getDeclaredFields(); + injectBeans(declaredFields, bean); + } + } + + private void injectBeans(final Field[] declaredFields, final Object bean) { + Arrays.stream(declaredFields) + .filter(field -> filterFieldIsNull(field, bean)) + .filter(field -> field.isAnnotationPresent(Inject.class)) + .forEach(field -> injectBean(field, bean)); + } + + private boolean filterFieldIsNull(final Field field, final Object bean) { + try { + field.setAccessible(true); + return Objects.isNull(field.get(bean)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private void injectBean(final Field field, final Object object) { + try { + field.set(object, getBean(field.getType())); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private Set createBeans(final Set> classes) { + return classes.stream() + .map(this::createDefaultObject) + .collect(Collectors.toSet()); + } + + private Object createDefaultObject(final Class clazz) { + try { + final Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException(e); + } } @SuppressWarnings("unchecked") - public T getBean(final Class aClass) { - return null; + public T getBean(final Class clazz) { + return (T) beans.stream() + .filter(object -> clazz.isAssignableFrom(object.getClass())) + .findFirst() + .orElse(null); } }