diff --git a/app/src/main/java/com/techcourse/Application.java b/app/src/main/java/com/techcourse/Application.java index 500d3f2d8b..6a6ff0086b 100644 --- a/app/src/main/java/com/techcourse/Application.java +++ b/app/src/main/java/com/techcourse/Application.java @@ -1,10 +1,9 @@ package com.techcourse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.stream.Stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Application { diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 768bd33b65..6022115401 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -24,11 +24,9 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { @Override public void onStartup(final ServletContext servletContext) { final HandlerMappings handlerMappings = new HandlerMappings(List.of( - new ManualHandlerMapping(), new AnnotationHandlerMapping("com.techcourse.controller") )); final HandlerAdapters handlerAdapters = new HandlerAdapters(List.of( - new ManualHandlerAdapter(), new AnnotationHandlerAdapter() )); final var dispatcherServlet = new DispatcherServlet(handlerMappings, handlerAdapters); diff --git a/app/src/main/java/com/techcourse/ManualHandlerAdapter.java b/app/src/main/java/com/techcourse/ManualHandlerAdapter.java deleted file mode 100644 index f9f344daab..0000000000 --- a/app/src/main/java/com/techcourse/ManualHandlerAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.techcourse; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; -import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; -import webmvc.org.springframework.web.servlet.view.JspView; - -public class ManualHandlerAdapter implements HandlerAdapter { - - @Override - public ModelAndView handle( - final HttpServletRequest request, - final HttpServletResponse response, - final Object handler - ) throws Exception { - final String result = ((Controller) handler).execute(request, response); - return new ModelAndView(new JspView(result)); - } - - @Override - public boolean isSupported(final Object handler) { - return handler instanceof Controller; - } -} 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 5d615d4939..0000000000 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.techcourse; - -import com.techcourse.controller.LoginController; -import com.techcourse.controller.LoginViewController; -import com.techcourse.controller.LogoutController; -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.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()); - - log.info("Initialized Handler Mapping!"); - controllers.keySet() - .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); - } - - @Override - public Object getHandler(final HttpServletRequest request) { - return getHandler(request.getRequestURI()); - } - - public Controller getHandler(final String requestURI) { - log.debug("Request Mapping Uri : {}", requestURI); - return controllers.get(requestURI); - } -} diff --git a/app/src/main/java/com/techcourse/TomcatStarter.java b/app/src/main/java/com/techcourse/TomcatStarter.java index 4f26f228e3..35ab103914 100644 --- a/app/src/main/java/com/techcourse/TomcatStarter.java +++ b/app/src/main/java/com/techcourse/TomcatStarter.java @@ -1,5 +1,6 @@ package com.techcourse; +import java.io.File; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; @@ -7,8 +8,6 @@ import org.apache.catalina.startup.Tomcat; import org.apache.tomcat.util.scan.StandardJarScanner; -import java.io.File; - public class TomcatStarter { public static final String WEBAPP_DIR_LOCATION = "app/src/main/webapp/"; diff --git a/app/src/main/java/com/techcourse/controller/IndexController.java b/app/src/main/java/com/techcourse/controller/IndexController.java new file mode 100644 index 0000000000..435e7643cd --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/IndexController.java @@ -0,0 +1,16 @@ +package com.techcourse.controller; + +import context.org.springframework.stereotype.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; + +@Controller +public class IndexController { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public ModelAndView index() { + return new ModelAndView(new JspView("/index.jsp")); + } +} diff --git a/app/src/main/java/com/techcourse/controller/LoginController.java b/app/src/main/java/com/techcourse/controller/LoginController.java index 0428fe109e..2bc35b71eb 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -2,33 +2,53 @@ import com.techcourse.domain.User; import com.techcourse.repository.InMemoryUserRepository; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import context.org.springframework.stereotype.Controller; +import jakarta.servlet.http.HttpSession; 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 web.org.springframework.web.bind.annotation.RequestParam; +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 { - if (UserSession.isLoggedIn(req.getSession())) { - return "redirect:/index.jsp"; + @RequestMapping(value = "/login/view", method = RequestMethod.GET) + public ModelAndView loginView(final HttpSession session) { + return UserSession.getUserFrom(session) + .map(user -> { + log.info("logged in {}", user.getAccount()); + return "redirect:/index.jsp"; + }) + .map(it -> new ModelAndView(new JspView(it))) + .orElse(new ModelAndView(new JspView("/login.jsp"))); + } + + @RequestMapping(value = "/login", method = RequestMethod.POST) + public ModelAndView login( + final HttpSession session, + @RequestParam(value = "account") final String account, + @RequestParam("password") final String password + ) { + if (UserSession.isLoggedIn(session)) { + return new ModelAndView(new JspView("redirect:/index.jsp")); } - return InMemoryUserRepository.findByAccount(req.getParameter("account")) + return InMemoryUserRepository.findByAccount(account) .map(user -> { log.info("User : {}", user); - return login(req, user); + return login(user, password, session); }) - .orElse("redirect:/401.jsp"); + .map(it -> new ModelAndView(new JspView(it))) + .orElse(new ModelAndView(new JspView("redirect:/401.jsp"))); } - private String login(final HttpServletRequest request, final User user) { - if (user.checkPassword(request.getParameter("password"))) { - final var session = request.getSession(); + private String login(final User user, final String password, final HttpSession session) { + if (user.checkPassword(password)) { session.setAttribute(UserSession.SESSION_KEY, user); return "redirect:/index.jsp"; } 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..6043b02a1f 100644 --- a/app/src/main/java/com/techcourse/controller/LogoutController.java +++ b/app/src/main/java/com/techcourse/controller/LogoutController.java @@ -1,15 +1,18 @@ package com.techcourse.controller; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import context.org.springframework.stereotype.Controller; +import jakarta.servlet.http.HttpSession; +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 { - final var session = req.getSession(); + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public ModelAndView logout(final HttpSession session) { 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 53f809f744..c5aa609039 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -1,10 +1,10 @@ package com.techcourse.controller; import com.techcourse.domain.User; +import com.techcourse.dto.RegisterRequest; import com.techcourse.repository.InMemoryUserRepository; import context.org.springframework.stereotype.Controller; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import web.org.springframework.web.bind.annotation.RequestBody; import web.org.springframework.web.bind.annotation.RequestMapping; import web.org.springframework.web.bind.annotation.RequestMethod; import webmvc.org.springframework.web.servlet.ModelAndView; @@ -14,18 +14,18 @@ public class RegisterController { @RequestMapping(value = "/register", method = RequestMethod.POST) - public ModelAndView save(HttpServletRequest req, HttpServletResponse res) { + public ModelAndView save(@RequestBody final RegisterRequest registerRequest) { final var user = new User(2, - req.getParameter("account"), - req.getParameter("password"), - req.getParameter("email")); + registerRequest.getAccount(), + registerRequest.getPassword(), + registerRequest.getEmail()); InMemoryUserRepository.save(user); return new ModelAndView(new JspView("redirect:/index.jsp")); } - @RequestMapping(value = "/register", method = RequestMethod.GET) - public ModelAndView show(HttpServletRequest req, HttpServletResponse res) { + @RequestMapping(value = "/register/view", method = RequestMethod.GET) + public ModelAndView show() { return new ModelAndView(new JspView("/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..c384f85621 --- /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.dto.UserRequest; +import com.techcourse.repository.InMemoryUserRepository; +import context.org.springframework.stereotype.Controller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import web.org.springframework.web.bind.annotation.RequestBody; +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(@RequestBody final UserRequest request) { + final String account = request.getAccount(); + 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/controller/UserSession.java b/app/src/main/java/com/techcourse/controller/UserSession.java index dc5fbbc556..ba2ae6013f 100644 --- a/app/src/main/java/com/techcourse/controller/UserSession.java +++ b/app/src/main/java/com/techcourse/controller/UserSession.java @@ -2,7 +2,6 @@ import com.techcourse.domain.User; import jakarta.servlet.http.HttpSession; - import java.util.Optional; public class UserSession { @@ -18,5 +17,6 @@ public static boolean isLoggedIn(final HttpSession session) { return getUserFrom(session).isPresent(); } - private UserSession() {} + private UserSession() { + } } 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/main/java/com/techcourse/dto/RegisterRequest.java b/app/src/main/java/com/techcourse/dto/RegisterRequest.java new file mode 100644 index 0000000000..1e060587f2 --- /dev/null +++ b/app/src/main/java/com/techcourse/dto/RegisterRequest.java @@ -0,0 +1,29 @@ +package com.techcourse.dto; + +public class RegisterRequest { + + private String account; + private String password; + private String email; + + public RegisterRequest() { + } + + public RegisterRequest(final String account, final String password, final String email) { + this.account = account; + this.password = password; + this.email = email; + } + + public String getAccount() { + return account; + } + + public String getPassword() { + return password; + } + + public String getEmail() { + return email; + } +} diff --git a/app/src/main/java/com/techcourse/dto/UserRequest.java b/app/src/main/java/com/techcourse/dto/UserRequest.java new file mode 100644 index 0000000000..c747408027 --- /dev/null +++ b/app/src/main/java/com/techcourse/dto/UserRequest.java @@ -0,0 +1,17 @@ +package com.techcourse.dto; + +public class UserRequest { + + private String account; + + public UserRequest() { + } + + public UserRequest(final String account) { + this.account = account; + } + + public String getAccount() { + return account; + } +} diff --git a/app/src/main/java/com/techcourse/repository/InMemoryUserRepository.java b/app/src/main/java/com/techcourse/repository/InMemoryUserRepository.java index 59bec6d8a8..102d2c82f2 100644 --- a/app/src/main/java/com/techcourse/repository/InMemoryUserRepository.java +++ b/app/src/main/java/com/techcourse/repository/InMemoryUserRepository.java @@ -1,7 +1,6 @@ package com.techcourse.repository; import com.techcourse.domain.User; - import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -23,5 +22,6 @@ public static Optional findByAccount(String account) { return Optional.ofNullable(database.get(account)); } - private InMemoryUserRepository() {} + private InMemoryUserRepository() { + } } diff --git a/app/src/main/java/com/techcourse/support/web/filter/CharacterEncodingFilter.java b/app/src/main/java/com/techcourse/support/web/filter/CharacterEncodingFilter.java index 290fc83b7d..352c3cae70 100644 --- a/app/src/main/java/com/techcourse/support/web/filter/CharacterEncodingFilter.java +++ b/app/src/main/java/com/techcourse/support/web/filter/CharacterEncodingFilter.java @@ -1,8 +1,12 @@ package com.techcourse.support.web.filter; -import jakarta.servlet.*; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; - import java.io.IOException; @WebFilter("/*") diff --git a/app/src/main/java/com/techcourse/support/web/filter/ResourceFilter.java b/app/src/main/java/com/techcourse/support/web/filter/ResourceFilter.java index ddbddd671c..1c5aa8d126 100644 --- a/app/src/main/java/com/techcourse/support/web/filter/ResourceFilter.java +++ b/app/src/main/java/com/techcourse/support/web/filter/ResourceFilter.java @@ -1,15 +1,20 @@ package com.techcourse.support.web.filter; -import jakarta.servlet.*; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @WebFilter("/*") public class ResourceFilter implements Filter { diff --git a/app/src/main/webapp/register.jsp b/app/src/main/webapp/register.jsp index 7e68995a56..8b9d49666b 100644 --- a/app/src/main/webapp/register.jsp +++ b/app/src/main/webapp/register.jsp @@ -64,3 +64,33 @@ <%@ include file="include/footer.jspf" %> + + diff --git a/app/src/test/java/com/techcourse/ManualHandlerAdapterTest.java b/app/src/test/java/com/techcourse/ManualHandlerAdapterTest.java deleted file mode 100644 index 4023c0d4fb..0000000000 --- a/app/src/test/java/com/techcourse/ManualHandlerAdapterTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.techcourse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class ManualHandlerAdapterTest { - - @Test - void 매뉴얼_핸들러_어답터는_Controller를_지원한다() { - // given - final Controller controller = mock(Controller.class); - final ManualHandlerAdapter manualHandlerAdapter = new ManualHandlerAdapter(); - - // expected - assertThat(manualHandlerAdapter.isSupported(controller)).isTrue(); - } -} diff --git a/app/src/test/java/com/techcourse/ManualHandlerMappingTest.java b/app/src/test/java/com/techcourse/ManualHandlerMappingTest.java deleted file mode 100644 index bfd7be33b7..0000000000 --- a/app/src/test/java/com/techcourse/ManualHandlerMappingTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.techcourse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.techcourse.controller.LoginController; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class ManualHandlerMappingTest { - - @Test - void ManualHandlerMapping은_Controller_인스턴스를_반환한다() { - // given - final HttpServletRequest request = mock(HttpServletRequest.class); - final ManualHandlerMapping handlerMapping = new ManualHandlerMapping(); - handlerMapping.initialize(); - when(request.getRequestURI()).thenReturn("/login"); - - // expected - assertThat(handlerMapping.getHandler(request)).isInstanceOf(LoginController.class); - } -} diff --git a/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java b/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java index 5de0905e6f..13093fd3cf 100644 --- a/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java +++ b/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java @@ -4,14 +4,16 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.reflections.Reflections; -public abstract class ReflectionUtils { +public class ReflectionUtils { /** * Obtain an accessible constructor for the given class and parameters. diff --git a/mvc/src/main/java/web/org/springframework/util/HttpRequestBodyConverter.java b/mvc/src/main/java/web/org/springframework/util/HttpRequestBodyConverter.java new file mode 100644 index 0000000000..b014390ed5 --- /dev/null +++ b/mvc/src/main/java/web/org/springframework/util/HttpRequestBodyConverter.java @@ -0,0 +1,25 @@ +package web.org.springframework.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; + +public class HttpRequestBodyConverter { + + private static final ObjectReader reader; + private static final ObjectWriter writer; + + static { + final ObjectMapper mapper = new ObjectMapper(); + reader = mapper.reader(); + writer = mapper.writer(); + } + + public static Object serialize(final String json, final Class target) throws Exception { + return reader.readValue(json, target); + } + + public static String deserialize(final Object object) throws Exception { + return writer.writeValueAsString(object); + } +} diff --git a/mvc/src/main/java/web/org/springframework/util/HttpRequestBodyParser.java b/mvc/src/main/java/web/org/springframework/util/HttpRequestBodyParser.java new file mode 100644 index 0000000000..a635bb3e80 --- /dev/null +++ b/mvc/src/main/java/web/org/springframework/util/HttpRequestBodyParser.java @@ -0,0 +1,14 @@ +package web.org.springframework.util; + +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.stream.Collectors; + +public class HttpRequestBodyParser { + + public static String parse(final HttpServletRequest request) throws IOException { + return request.getReader() + .lines() + .collect(Collectors.joining(System.lineSeparator())); + } +} diff --git a/mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java b/mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java index f1c13fcd53..815cbbf79f 100644 --- a/mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java +++ b/mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java @@ -5,7 +5,6 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.HandlesTypes; - import java.util.ArrayList; import java.util.List; import java.util.Set; diff --git a/mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java index ceefd548e1..338da3ef35 100644 --- a/mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java @@ -1,6 +1,10 @@ package web.org.springframework.web.bind.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) diff --git a/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestBody.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestBody.java new file mode 100644 index 0000000000..c02f2e874f --- /dev/null +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestBody.java @@ -0,0 +1,11 @@ +package web.org.springframework.web.bind.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequestBody { +} diff --git a/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java index 5b3be4cedc..00e07bbb3f 100644 --- a/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java @@ -1,6 +1,10 @@ package web.org.springframework.web.bind.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java index ff8e24553f..852a0b56f5 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java @@ -1,5 +1,7 @@ package webmvc.org.springframework.web.servlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,11 +25,7 @@ public Object getObject(final String attributeName) { return model.get(attributeName); } - public Map getModel() { - return Collections.unmodifiableMap(model); - } - - public View getView() { - return view; + public void render(final HttpServletRequest request, final HttpServletResponse response) throws Exception { + view.render(Collections.unmodifiableMap(model), request, response); } } 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 0356d15f9b..c606be223f 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 @@ -7,6 +7,4 @@ public interface View { void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; - - String getViewName(); } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java index a3cf9751ca..53b278c4f5 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java @@ -7,7 +7,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.view.JspView; public class DispatcherServlet extends HttpServlet { @@ -34,26 +33,10 @@ protected void service(final HttpServletRequest request, final HttpServletRespon final Object handler = handlerMappings.getHandler(request); final HandlerAdapter adapter = handlerAdapters.getAdapter(handler); final ModelAndView result = adapter.handle(request, response, handler); - move(result, request, response); + result.render(request, response); } catch (Throwable e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); } } - - 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); - } } 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 bdd1fde780..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java +++ /dev/null @@ -1,8 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.asis; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public interface Controller { - String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception; -} 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/AnnotationHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java index 3297e2c071..b04779f7a4 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java @@ -2,18 +2,35 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.List; import webmvc.org.springframework.web.servlet.ModelAndView; import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.tobe.resolver.ArgumentResolvers; +import webmvc.org.springframework.web.servlet.mvc.tobe.resolver.HttpServletRequestResolver; +import webmvc.org.springframework.web.servlet.mvc.tobe.resolver.HttpServletResponseResolver; +import webmvc.org.springframework.web.servlet.mvc.tobe.resolver.HttpSessionResolver; +import webmvc.org.springframework.web.servlet.mvc.tobe.resolver.RequestBodyResolver; +import webmvc.org.springframework.web.servlet.mvc.tobe.resolver.RequestParamResolver; public class AnnotationHandlerAdapter implements HandlerAdapter { + private static final ArgumentResolvers argumentResolvers = new ArgumentResolvers( + List.of(new RequestBodyResolver(), + new RequestParamResolver(), + new HttpServletRequestResolver(), + new HttpServletResponseResolver(), + new HttpSessionResolver()) + ); + @Override public ModelAndView handle( final HttpServletRequest request, final HttpServletResponse response, final Object handler ) throws Exception { - return ((HandlerExecution) handler).handle(request, response); + HandlerExecution handlerExecution = (HandlerExecution) handler; + Object[] arguments = argumentResolvers.resolveArgument(handlerExecution.getMethod(), request, response); + return handlerExecution.handle(arguments); } @Override 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/AnnotationHandlerMapping.java index 824ab41928..c01d0bfe35 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/AnnotationHandlerMapping.java @@ -34,7 +34,8 @@ public AnnotationHandlerMapping(final Object... basePackage) { @Override public void initialize() { final Set> controllers = getClassHasAnnotationWith(CONTROLLER_ANNOTATION, basePackage); - final Map, List> classMethods = getMethodHasAnnotationWith(REQUEST_MAPPING_ANNOTATION, controllers); + final Map, List> classMethods = getMethodHasAnnotationWith(REQUEST_MAPPING_ANNOTATION, + controllers); addAllMapping(classMethods); } @@ -59,13 +60,14 @@ private void addMapping(final Class controllerClass, final List class for (RequestMethod httpMethod : httpMethods) { final HandlerKey handlerKey = new HandlerKey(value, httpMethod); final HandlerExecution handlerExecution = new HandlerExecution(controllerClass, method); + log.info("add mapping {}, {}", value, httpMethod); handlerExecutions.put(handlerKey, handlerExecution); } } } @Override - public Object getHandler(final HttpServletRequest request) { + public HandlerExecution getHandler(final HttpServletRequest request) { final String requestPath = request.getRequestURI(); final RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod()); return handlerExecutions.get(new HandlerKey(requestPath, requestMethod)); 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/HandlerExecution.java index 864c90d3d4..2ba55659b8 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/HandlerExecution.java @@ -1,7 +1,5 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import webmvc.org.springframework.web.servlet.ModelAndView; @@ -15,7 +13,11 @@ public HandlerExecution(final Class controllerClass, final Method method) thr this.method = method; } - public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return (ModelAndView) method.invoke(controllerInstance, request, response); + public ModelAndView handle(final Object[] arguments) throws Exception { + return (ModelAndView) method.invoke(controllerInstance, arguments); + } + + public Method getMethod() { + return method; } } 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/HandlerKey.java index 30d3c780ff..97e9479e03 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/HandlerKey.java @@ -1,8 +1,7 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; -import web.org.springframework.web.bind.annotation.RequestMethod; - import java.util.Objects; +import web.org.springframework.web.bind.annotation.RequestMethod; public class HandlerKey { @@ -24,8 +23,12 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof HandlerKey)) return false; + if (this == o) { + return true; + } + if (!(o instanceof HandlerKey)) { + return false; + } HandlerKey that = (HandlerKey) o; return Objects.equals(url, that.url) && requestMethod == that.requestMethod; } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/ArgumentResolvers.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/ArgumentResolvers.java new file mode 100644 index 0000000000..a30a7cf318 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/ArgumentResolvers.java @@ -0,0 +1,36 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; + +public class ArgumentResolvers { + + private final List resolvers; + + public ArgumentResolvers(final List resolvers) { + this.resolvers = resolvers; + } + + public Object[] resolveArgument( + final Method method, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception { + final List resolvedArguments = new ArrayList<>(); + + for (final Parameter parameter : method.getParameters()) { + final Resolver resolver = resolvers.stream() + .filter(it -> it.isSupported(parameter)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("지원하지 않는 파라미터업니다.")); + + resolvedArguments.add(resolver.resolveArgument(parameter, request, response)); + } + + return resolvedArguments.toArray(); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpServletRequestResolver.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpServletRequestResolver.java new file mode 100644 index 0000000000..661260411d --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpServletRequestResolver.java @@ -0,0 +1,22 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Parameter; + +public class HttpServletRequestResolver implements Resolver { + + @Override + public Object resolveArgument( + final Parameter parameter, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception { + return request; + } + + @Override + public boolean isSupported(final Parameter parameter) { + return parameter.getType().equals(HttpServletRequest.class); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpServletResponseResolver.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpServletResponseResolver.java new file mode 100644 index 0000000000..1f6ebc018e --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpServletResponseResolver.java @@ -0,0 +1,22 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Parameter; + +public class HttpServletResponseResolver implements Resolver { + + @Override + public Object resolveArgument( + final Parameter parameter, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception { + return response; + } + + @Override + public boolean isSupported(final Parameter parameter) { + return parameter.getType().equals(HttpServletResponse.class); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpSessionResolver.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpSessionResolver.java new file mode 100644 index 0000000000..1b3430c45b --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/HttpSessionResolver.java @@ -0,0 +1,23 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import java.lang.reflect.Parameter; + +public class HttpSessionResolver implements Resolver { + + @Override + public Object resolveArgument( + final Parameter parameter, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception { + return request.getSession(); + } + + @Override + public boolean isSupported(final Parameter parameter) { + return parameter.getType().equals(HttpSession.class); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/RequestBodyResolver.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/RequestBodyResolver.java new file mode 100644 index 0000000000..c1ff2b04d0 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/RequestBodyResolver.java @@ -0,0 +1,26 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Parameter; +import web.org.springframework.util.HttpRequestBodyConverter; +import web.org.springframework.util.HttpRequestBodyParser; +import web.org.springframework.web.bind.annotation.RequestBody; + +public class RequestBodyResolver implements Resolver { + + @Override + public Object resolveArgument( + final Parameter parameter, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception { + final String json = HttpRequestBodyParser.parse(request); + return HttpRequestBodyConverter.serialize(json, parameter.getType()); + } + + @Override + public boolean isSupported(final Parameter parameter) { + return parameter.isAnnotationPresent(RequestBody.class); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/RequestParamResolver.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/RequestParamResolver.java new file mode 100644 index 0000000000..ccbf4e8267 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/RequestParamResolver.java @@ -0,0 +1,24 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Parameter; +import web.org.springframework.web.bind.annotation.RequestParam; + +public class RequestParamResolver implements Resolver { + + @Override + public Object resolveArgument( + final Parameter parameter, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception { + final RequestParam annotation = parameter.getAnnotation(RequestParam.class); + return request.getParameter(annotation.value()); + } + + @Override + public boolean isSupported(final Parameter parameter) { + return parameter.isAnnotationPresent(RequestParam.class); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/Resolver.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/Resolver.java new file mode 100644 index 0000000000..53ec24ed6f --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/resolver/Resolver.java @@ -0,0 +1,16 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe.resolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Parameter; + +public interface Resolver { + + Object resolveArgument( + final Parameter parameter, + final HttpServletRequest request, + final HttpServletResponse response + ) throws Exception; + + boolean isSupported(final Parameter parameter); +} 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 781a0c5b1c..9c62c4935a 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 @@ -3,21 +3,25 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Map; +import web.org.springframework.http.MediaType; +import web.org.springframework.util.HttpRequestBodyConverter; import webmvc.org.springframework.web.servlet.View; public class JsonView implements View { - private final String viewName; - - public JsonView(final String viewName) { - this.viewName = viewName; - } - @Override - public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) throws Exception { + public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) + throws Exception { + final String deserialize = getDeserializedJson(model); + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + response.getWriter().println(deserialize); } - public String getViewName() { - return viewName; + private String getDeserializedJson(final Map model) throws Exception { + if (model.size() == 1) { + final Object object = model.values().iterator().next(); + return HttpRequestBodyConverter.deserialize(object); + } + return HttpRequestBodyConverter.deserialize(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 e30163545c..a71552bc40 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 @@ -10,7 +10,7 @@ public class JspView implements View { private static final Logger log = LoggerFactory.getLogger(JspView.class); - public static final String REDIRECT_PREFIX = "redirect:"; + private static final String REDIRECT_PREFIX = "redirect:"; private final String viewName; @@ -19,18 +19,19 @@ public JspView(final String viewName) { } @Override - public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - // todo + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { + response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); + return; + } model.keySet().forEach(key -> { log.debug("attribute name : {}, value : {}", key, model.get(key)); request.setAttribute(key, model.get(key)); }); - // todo - } - - public String getViewName() { - return viewName; + final var requestDispatcher = request.getRequestDispatcher(viewName); + requestDispatcher.forward(request, response); } } diff --git a/mvc/src/test/java/web/org/springframework/util/HttpRequestBodyConverterTest.java b/mvc/src/test/java/web/org/springframework/util/HttpRequestBodyConverterTest.java new file mode 100644 index 0000000000..acd98872c8 --- /dev/null +++ b/mvc/src/test/java/web/org/springframework/util/HttpRequestBodyConverterTest.java @@ -0,0 +1,64 @@ +package web.org.springframework.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import java.util.Map; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class HttpRequestBodyConverterTest { + + @Test + void JSON_형식의_데이터를_받아서_객체로_변환한다() throws Exception { + // given + final String json = "{\"username\":\"test1234\",\"password\":\"test1234!\"}"; + + // when + final TestDto serialized = (TestDto) HttpRequestBodyConverter.serialize(json, TestDto.class); + + // then + assertSoftly(softly -> { + softly.assertThat(serialized.getUsername()).isEqualTo("test1234"); + softly.assertThat(serialized.getPassword()).isEqualTo("test1234!"); + }); + } + + @Test + void 모델을_받아_JSON_형태로_변환한다() throws Exception { + // given + final TestDto dto = new TestDto("blackcat", "1234"); + final Map model = Map.of("dto", dto); + + // when + final String deserialize = HttpRequestBodyConverter.deserialize(model); + + // then + assertThat(deserialize).isEqualTo("{\"dto\":{\"username\":\"blackcat\",\"password\":\"1234\"}}"); + } + + static class TestDto { + + private String username; + private String password; + + public TestDto() { + } + + public TestDto(final String username, final String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + } +} 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/AnnotationHandlerMappingTest.java index dcec215a3f..e4c3a532ee 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/AnnotationHandlerMappingTest.java @@ -1,14 +1,15 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - class AnnotationHandlerMappingTest { private AnnotationHandlerMapping handlerMapping; @@ -29,7 +30,7 @@ void get() throws Exception { when(request.getMethod()).thenReturn("GET"); final var handlerExecution = (HandlerExecution) handlerMapping.getHandler(request); - final var modelAndView = handlerExecution.handle(request, response); + final var modelAndView = handlerExecution.handle(List.of(request, response).toArray()); assertThat(modelAndView.getObject("id")).isEqualTo("gugu"); } @@ -44,7 +45,7 @@ void post() throws Exception { when(request.getMethod()).thenReturn("POST"); final var handlerExecution = (HandlerExecution) handlerMapping.getHandler(request); - final var modelAndView = handlerExecution.handle(request, response); + final var modelAndView = handlerExecution.handle(List.of(request, response).toArray()); assertThat(modelAndView.getObject("id")).isEqualTo("gugu"); } 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..627eb8c92e --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JsonViewTest.java @@ -0,0 +1,75 @@ +package webmvc.org.springframework.web.servlet.view; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class JsonViewTest { + + private HttpServletRequest request; + private HttpServletResponse response; + private PrintWriter printWriter; + + @BeforeEach + void setUp() { + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + printWriter = mock(PrintWriter.class); + } + + @Test + void 객체를_받아_값이_한개라면_그대로_응답을_내려준다() throws Exception { + // given + final var jsonView = new JsonView(); + final Map attribute = new HashMap<>(); + when(response.getWriter()).thenReturn(printWriter); + + // when + attribute.put("member", new TestObject("blackcat")); + jsonView.render(attribute, request, response); + + // then + verify(printWriter).println("{\"name\":\"blackcat\"}"); + } + + @Test + void 객체를_받아_값이_2개_이상이라면_JSON_형태로_응답을_내려준다() throws Exception { + // given + final var jsonView = new JsonView(); + final Map attribute = new HashMap<>(); + when(response.getWriter()).thenReturn(printWriter); + + // when + attribute.put("createdAt", "2023-09-25"); + attribute.put("member", new TestObject("blackcat")); + jsonView.render(attribute, request, response); + + // then + verify(printWriter).println("{\"createdAt\":\"2023-09-25\",\"member\":{\"name\":\"blackcat\"}}"); + } + + static class TestObject { + + private String name; + + public TestObject(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} 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..41442d270b --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/view/JspViewTest.java @@ -0,0 +1,72 @@ +package webmvc.org.springframework.web.servlet.view; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class JspViewTest { + + private HttpServletRequest request; + private HttpServletResponse response; + + @BeforeEach + void setUp() { + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + } + + @Test + void Jsp_View_는_응답을_내려준다() throws Exception { + // given + final var jspView = new JspView("/login.jsp"); + final var requestDispatcher = mock(RequestDispatcher.class); + + // when + when(request.getRequestDispatcher("/login.jsp")).thenReturn(requestDispatcher); + jspView.render(new HashMap<>(), request, response); + + // then + verify(requestDispatcher).forward(request, response); + } + + @Test + void Jsp_View_는_요청에_속성을_담아_응답을_내려준다() throws Exception { + // given + final var jspView = new JspView("/login.jsp"); + final var requestDispatcher = mock(RequestDispatcher.class); + final var model = Map.of("username", "blackcat", "password", "1234"); + + // when + when(request.getRequestDispatcher("/login.jsp")).thenReturn(requestDispatcher); + jspView.render(model, request, response); + + // then + verify(requestDispatcher).forward(request, response); + verify(request).setAttribute("username", "blackcat"); + verify(request).setAttribute("password", "1234"); + } + + @Test + void Jsp_View_는_redirect_prefix_가_있을_시_리다이렉션_응답을_내려준다() throws Exception { + // given + final var jspView = new JspView("redirect:/401.jsp"); + + // when + jspView.render(new HashMap<>(), request, response); + + // then + verify(response).sendRedirect("/401.jsp"); + } +}