From 30ef3e5e374d25b5bdbb666a76799f81818f35cd Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Mon, 5 Aug 2024 22:27:10 +0900 Subject: [PATCH 01/15] =?UTF-8?q?[docs]=20step3=EC=9D=98=20todo=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e391b2aa..05bfdf14 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,4 @@ ### [STEP3] JSON View 구현하기 #### TODO -- [ ] JsonView render 구현 후 UserController가 정상동작하는지 테스트 \ No newline at end of file +- [X] JsonView render 구현 후 UserController가 정상동작하는지 테스트 \ No newline at end of file From 36b39f5b3efbc40b18f7c64fa747718c9791dbc1 Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Mon, 5 Aug 2024 23:28:15 +0900 Subject: [PATCH 02/15] [docs] step4 todo --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05bfdf14..43a144ea 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,14 @@ ### [STEP3] JSON View 구현하기 #### TODO -- [X] JsonView render 구현 후 UserController가 정상동작하는지 테스트 \ No newline at end of file +- [X] JsonView render 구현 후 UserController가 정상동작하는지 테스트 + +### [STEP4] 4단계 - Controller 메서드 인자 매핑 +#### TODO +- [ ] method의 parameter types를 통해 parsing할 수 있는 parameter parser + - [ ] request param parser + - [ ] path variable parser + - [ ] HttpSerlvetRequest parser + - [ ] HttpSerlvetResponse parser +- [ ] handlerexecution에서 invoke 시점에 parser로 parsing +- [ ] testusercontroller로 확인 From 758a99c5f611ec9682cce8d970df95045e97f415 Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Mon, 5 Aug 2024 23:30:00 +0900 Subject: [PATCH 03/15] =?UTF-8?q?[refactor]=20servlet/mvc/support=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20web/support=EB=A1=9C=20package=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servlet/mvc/tobe => web}/support/PathPatternParser.java | 2 +- .../servlet/mvc/tobe => web}/support/PathPatternUtil.java | 2 +- .../servlet/mvc/tobe => web}/support/PathPatternUtilTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename mvc/src/main/java/com/interface21/{webmvc/servlet/mvc/tobe => web}/support/PathPatternParser.java (91%) rename mvc/src/main/java/com/interface21/{webmvc/servlet/mvc/tobe => web}/support/PathPatternUtil.java (86%) rename mvc/src/test/java/com/interface21/{webmvc/servlet/mvc/tobe => web}/support/PathPatternUtilTest.java (90%) diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternParser.java b/mvc/src/main/java/com/interface21/web/support/PathPatternParser.java similarity index 91% rename from mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternParser.java rename to mvc/src/main/java/com/interface21/web/support/PathPatternParser.java index f8a204c5..2b0fe5ba 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternParser.java +++ b/mvc/src/main/java/com/interface21/web/support/PathPatternParser.java @@ -1,4 +1,4 @@ -package com.interface21.webmvc.servlet.mvc.tobe.support; +package com.interface21.web.support; import java.util.ArrayList; import java.util.List; diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternUtil.java b/mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java similarity index 86% rename from mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternUtil.java rename to mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java index fcac0170..12141dcf 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternUtil.java +++ b/mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java @@ -1,4 +1,4 @@ -package com.interface21.webmvc.servlet.mvc.tobe.support; +package com.interface21.web.support; import java.util.Map; diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternUtilTest.java b/mvc/src/test/java/com/interface21/web/support/PathPatternUtilTest.java similarity index 90% rename from mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternUtilTest.java rename to mvc/src/test/java/com/interface21/web/support/PathPatternUtilTest.java index 5171f513..7b842737 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/support/PathPatternUtilTest.java +++ b/mvc/src/test/java/com/interface21/web/support/PathPatternUtilTest.java @@ -1,4 +1,4 @@ -package com.interface21.webmvc.servlet.mvc.tobe.support; +package com.interface21.web.support; import org.junit.jupiter.api.Test; From ef782d26b3873434ae7441f56a58e4c93fd07792 Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Tue, 6 Aug 2024 22:03:18 +0900 Subject: [PATCH 04/15] =?UTF-8?q?[feat]=20pathPattern=20util/parser=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/support/PathPatternParser.java | 25 ++++++++++++++----- .../web/support/PathPatternUtil.java | 12 ++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/mvc/src/main/java/com/interface21/web/support/PathPatternParser.java b/mvc/src/main/java/com/interface21/web/support/PathPatternParser.java index 2b0fe5ba..54f7f802 100644 --- a/mvc/src/main/java/com/interface21/web/support/PathPatternParser.java +++ b/mvc/src/main/java/com/interface21/web/support/PathPatternParser.java @@ -1,8 +1,6 @@ package com.interface21.web.support; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; public class PathPatternParser { @@ -16,14 +14,29 @@ public PathPatternParser(String pattern) { } private String buildRegex(String pattern) { - return null; + var matcher = Pattern.compile("\\{([^}]+)}").matcher(pattern); + StringBuffer buffer = new StringBuffer(); + while (matcher.find()) { + variableNames.add(matcher.group(1)); + matcher.appendReplacement(buffer, "([^/]+)"); + } + matcher.appendTail(buffer); + return buffer.toString(); } public boolean matches(String path) { - return false; + return pattern.matcher(path).matches(); } public Map extractUriVariables(String path) { - return null; + var matcher = pattern.matcher(path); + if (!matcher.matches()) { + return Collections.emptyMap(); + } + Map variables = new HashMap<>(); + for (int i = 0; i < variableNames.size(); i++) { + variables.put(variableNames.get(i), matcher.group(i + 1)); + } + return variables; } } diff --git a/mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java b/mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java index 12141dcf..d66b61cf 100644 --- a/mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java +++ b/mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java @@ -5,14 +5,20 @@ public class PathPatternUtil { public static String getUriValue(String pattern, String path, String key) { - return null; + final Map uriVariables = getUriVariables(pattern, path); + return uriVariables.get(key); } public static Map getUriVariables(String pattern, String path) { - return null; + if (!isUrlMatch(pattern, path)) { + return Map.of(); + } + final PathPatternParser parser = new PathPatternParser(pattern); + return parser.extractUriVariables(path); } public static boolean isUrlMatch(String pattern, String path) { - return false; + final PathPatternParser parser = new PathPatternParser(pattern); + return parser.matches(path); } } From 477d6739783015bfdb027669f7f8b039cd032158 Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Mon, 19 Aug 2024 08:12:05 +0900 Subject: [PATCH 05/15] =?UTF-8?q?[feat]=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9D=B8=EC=9E=90=20=EC=9D=B4=EB=A6=84=20=EC=B0=BE=EA=B8=B0?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20build.gradle=EC=97=90=20=EC=BB=B4=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=88=98=EB=A1=9C=20-parameters=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 ++++ mvc/build.gradle | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 0d35baea..51e70713 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,3 +45,7 @@ sourceSets { java.destinationDirectory.set(file('src/main/webapp/WEB-INF/classes')) } } + +compileJava { + options.compilerArgs << '-parameters' +} \ No newline at end of file diff --git a/mvc/build.gradle b/mvc/build.gradle index 142ca323..402de788 100644 --- a/mvc/build.gradle +++ b/mvc/build.gradle @@ -35,3 +35,7 @@ dependencies { test { useJUnitPlatform() } + +tasks.withType(JavaCompile) { + options.compilerArgs.add("-parameters") +} \ No newline at end of file From 320f09d6414f66e9cbf65ca26dd678d22ddb33e3 Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Mon, 19 Aug 2024 08:14:31 +0900 Subject: [PATCH 06/15] =?UTF-8?q?[docs]=20readme=EC=97=90=20todo=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 43a144ea..b2e28d9b 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,8 @@ ### [STEP4] 4단계 - Controller 메서드 인자 매핑 #### TODO - [ ] method의 parameter types를 통해 parsing할 수 있는 parameter parser - - [ ] request param parser - - [ ] path variable parser - - [ ] HttpSerlvetRequest parser - - [ ] HttpSerlvetResponse parser -- [ ] handlerexecution에서 invoke 시점에 parser로 parsing + - [ ] query param parser -> 메소드 파라미터의 type, name 보고 request.getParameter 에서 추출 + - [ ] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 + - [ ] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 +- [ ] handlerExecution에서 invoke 시점에 parser로 parsing - [ ] testusercontroller로 확인 From fa68f4fb3745f093df2ab2674a1379c42bd0e8a4 Mon Sep 17 00:00:00 2001 From: HYEJIN Date: Mon, 19 Aug 2024 08:15:48 +0900 Subject: [PATCH 07/15] [feat] query param parser --- README.md | 2 +- .../web/parameter/ParameterParser.java | 11 +++++ .../web/parameter/QueryParamParser.java | 18 ++++++++ .../web/parameter/QueryParamParserTest.java | 43 +++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 mvc/src/main/java/com/interface21/web/parameter/ParameterParser.java create mode 100644 mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java create mode 100644 mvc/src/test/java/com/interface21/web/parameter/QueryParamParserTest.java diff --git a/README.md b/README.md index b2e28d9b..8c74b603 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ ### [STEP4] 4단계 - Controller 메서드 인자 매핑 #### TODO - [ ] method의 parameter types를 통해 parsing할 수 있는 parameter parser - - [ ] query param parser -> 메소드 파라미터의 type, name 보고 request.getParameter 에서 추출 + - [X] query param parser -> 메소드 파라미터의 type, name 보고 request.getParameter 에서 추출 - [ ] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 - [ ] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 - [ ] handlerExecution에서 invoke 시점에 parser로 parsing diff --git a/mvc/src/main/java/com/interface21/web/parameter/ParameterParser.java b/mvc/src/main/java/com/interface21/web/parameter/ParameterParser.java new file mode 100644 index 00000000..510b7658 --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/parameter/ParameterParser.java @@ -0,0 +1,11 @@ +package com.interface21.web.parameter; + +import jakarta.servlet.http.HttpServletRequest; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +public interface ParameterParser { + Object parse(final Method method, final Parameter parameter, final HttpServletRequest request); + +} diff --git a/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java b/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java new file mode 100644 index 00000000..80c4930f --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java @@ -0,0 +1,18 @@ +package com.interface21.web.parameter; + + +import com.interface21.web.support.TypedParsers; +import jakarta.servlet.http.HttpServletRequest; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +public class QueryParamParser implements ParameterParser { + + @Override + public Object parse(Method method, Parameter parameter, HttpServletRequest request) { + var param = request.getParameter(parameter.getName()); + return TypedParsers.parse(parameter.getType(), param); + } + +} diff --git a/mvc/src/test/java/com/interface21/web/parameter/QueryParamParserTest.java b/mvc/src/test/java/com/interface21/web/parameter/QueryParamParserTest.java new file mode 100644 index 00000000..75750baa --- /dev/null +++ b/mvc/src/test/java/com/interface21/web/parameter/QueryParamParserTest.java @@ -0,0 +1,43 @@ +package com.interface21.web.parameter; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +class QueryParamParserTest { + + private final QueryParamParser parser = new QueryParamParser(); + + @Test + void cannot_parse_nomatch_name() throws NoSuchMethodException { + var parameter = TestQueryParamClass.class.getDeclaredMethod("cannot_parse_nomatch_name", String.class).getParameters()[0]; + final HttpServletRequest request = mock(HttpServletRequest.class); + doReturn("1").when(request).getParameter("wrong"); + + assertThat(parser.parse(null, parameter, request)).isNull(); + } + + + @Test + void parsed_by_param_name() throws NoSuchMethodException { + var parameter = TestQueryParamClass.class.getDeclaredMethod("parsed_by_param_name", long.class).getParameters()[0]; + final HttpServletRequest request = mock(HttpServletRequest.class); + doReturn("1").when(request).getParameter("count"); + + assertThat(parser.parse(null, parameter, request)).isEqualTo(1L); + } +} + +class TestQueryParamClass { + + public void cannot_parse_nomatch_name(String name) { + + } + + public void parsed_by_param_name(long count) { + + } +} \ No newline at end of file From 1f515ccd471ed5e27cebbe792fb65a36d72d7a6b Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:28:56 +0900 Subject: [PATCH 08/15] [feat] path variable parser --- README.md | 2 +- .../web/parameter/PathVariableParser.java | 30 +++++++++ .../web/parameter/PathVariableParserTest.java | 67 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java create mode 100644 mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java diff --git a/README.md b/README.md index 8c74b603..be46e9ba 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ #### TODO - [ ] method의 parameter types를 통해 parsing할 수 있는 parameter parser - [X] query param parser -> 메소드 파라미터의 type, name 보고 request.getParameter 에서 추출 - - [ ] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 + - [X] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 - [ ] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 - [ ] handlerExecution에서 invoke 시점에 parser로 parsing - [ ] testusercontroller로 확인 diff --git a/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java b/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java new file mode 100644 index 00000000..5c399d83 --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java @@ -0,0 +1,30 @@ +package com.interface21.web.parameter; + + +import com.interface21.web.bind.annotation.PathVariable; +import com.interface21.web.bind.annotation.RequestMapping; +import com.interface21.web.support.PathPatternUtil; +import com.interface21.web.support.TypedParsers; +import jakarta.servlet.http.HttpServletRequest; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +public class PathVariableParser implements ParameterParser { + + @Override + public Object parse(Method method, Parameter parameter, HttpServletRequest request) { + var variable = parameter.getAnnotation(PathVariable.class); + var requestMapping = method.getAnnotation(RequestMapping.class); + if (variable == null || requestMapping == null) { + return null; + } + var name = variable.name(); + if (name.isBlank()) { + name = parameter.getName(); + } + var param = PathPatternUtil.getUriValue(requestMapping.value(), request.getRequestURI(), name); + return TypedParsers.parse(parameter.getType(), param); + } + +} diff --git a/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java b/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java new file mode 100644 index 00000000..112bc09c --- /dev/null +++ b/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java @@ -0,0 +1,67 @@ +package com.interface21.web.parameter; + +import com.interface21.web.bind.annotation.PathVariable; +import com.interface21.web.bind.annotation.RequestMapping; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Parameter; +import java.net.http.HttpRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PathVariableParserTest { + + private final PathVariableParser parser = new PathVariableParser(); + + @Test + void cannot_parse_without_pathVariableParameter() throws NoSuchMethodException { + var method = TestPathVariableClass.class.getDeclaredMethod("cannot_parse_without_pathVariableParameter", String.class); + var parameter = method.getParameters()[0]; + + var result = parser.parse(method, parameter, null); + + assertThat(result).isNull(); + } + + @Test + void cannot_parse_without_RequestMapping() throws NoSuchMethodException { + var method = TestPathVariableClass.class.getDeclaredMethod("cannot_parse_without_RequestMapping", String.class); + var parameter = method.getParameters()[0]; + + var result = parser.parse(method, parameter, null); + + assertThat(result).isNull(); + } + + @Test + void can_parse_by_matched() throws NoSuchMethodException { + var request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/1/2"); + var method = TestPathVariableClass.class.getDeclaredMethod("can_parse_by_matched", long.class, int.class); + + var parameter1 = method.getParameters()[0]; + var parameter2 = method.getParameters()[1]; + assertThat(parser.parse(method, parameter1, request)).isEqualTo("1"); + assertThat(parser.parse(method, parameter2, request)).isEqualTo("2"); + } +} + +class TestPathVariableClass { + + public void cannot_parse_without_pathVariableParameter(String id) { + + } + + public void cannot_parse_without_RequestMapping(@PathVariable String id) { + + } + + @RequestMapping(value = "/{a}/{b}") + public void can_parse_by_matched(@PathVariable long a, @PathVariable int b) { + + } +} \ No newline at end of file From 1bf4d48ed0976d10cf939cdb481df31b4a4a8a4d Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:29:19 +0900 Subject: [PATCH 09/15] [feat] typed parser --- .../interface21/web/support/TypedParser.java | 40 +++++++++++++++++++ .../interface21/web/support/TypedParsers.java | 19 +++++++++ 2 files changed, 59 insertions(+) create mode 100644 mvc/src/main/java/com/interface21/web/support/TypedParser.java create mode 100644 mvc/src/main/java/com/interface21/web/support/TypedParsers.java diff --git a/mvc/src/main/java/com/interface21/web/support/TypedParser.java b/mvc/src/main/java/com/interface21/web/support/TypedParser.java new file mode 100644 index 00000000..a00ac6f3 --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/support/TypedParser.java @@ -0,0 +1,40 @@ +package com.interface21.web.support; + + +import java.util.function.Function; + +public class TypedParser { + protected static final TypedParser STRING = new TypedParser(String.class, (s) -> s); + protected static final TypedParser INT = new TypedParser(int.class, Integer::parseInt); + protected static final TypedParser WRAPPER_INTEGER = new TypedParser(Integer.class, Integer::parseInt); + protected static final TypedParser LONG = new TypedParser(long.class, Long::parseLong); + protected static final TypedParser WRAPPER_LONG = new TypedParser(Long.class, Integer::parseInt); + protected static final TypedParser BOOLEAN = new TypedParser(boolean.class, Boolean::parseBoolean); + protected static final TypedParser WRAPPER_BOOLEAN = new TypedParser(Boolean.class, Boolean::parseBoolean); + protected static final TypedParser SHORT = new TypedParser(short.class, Short::parseShort); + protected static final TypedParser WRAPPER_SHORT = new TypedParser(Short.class, Short::parseShort); + protected static final TypedParser FLOAT = new TypedParser(float.class, Float::parseFloat); + protected static final TypedParser WRAPPER_FLOAT = new TypedParser(Float.class, Float::parseFloat); + protected static final TypedParser DOUBLE = new TypedParser(double.class, Double::parseDouble); + protected static final TypedParser WRAPPER_DOUBLE = new TypedParser(Double.class, Double::parseDouble); + protected static final TypedParser CHAR = new TypedParser(char.class, s -> s.charAt(0)); + protected static final TypedParser WRAPPER_CHAR = new TypedParser(Character.class, s -> s.charAt(0)); + + private final Class type; + private final Function parser; + + public TypedParser(Class type, Function parser) { + this.type = type; + this.parser = parser; + } + + protected boolean canParse(Class type) { + return type.equals(this.type); + } + + protected Object parse(String value) { + return parser.apply(value); + } + + +} \ No newline at end of file diff --git a/mvc/src/main/java/com/interface21/web/support/TypedParsers.java b/mvc/src/main/java/com/interface21/web/support/TypedParsers.java new file mode 100644 index 00000000..66c4788e --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/support/TypedParsers.java @@ -0,0 +1,19 @@ +package com.interface21.web.support; + +import java.util.ArrayList; +import java.util.List; + +public class TypedParsers { + private static List parserList = new ArrayList<>(List.of(TypedParser.STRING, TypedParser.INT, TypedParser.WRAPPER_INTEGER, TypedParser.LONG, TypedParser.WRAPPER_LONG, TypedParser.BOOLEAN, TypedParser.WRAPPER_BOOLEAN, TypedParser.SHORT, TypedParser.WRAPPER_SHORT, TypedParser.FLOAT, TypedParser.WRAPPER_FLOAT, TypedParser.DOUBLE, TypedParser.WRAPPER_DOUBLE, TypedParser.CHAR, TypedParser.WRAPPER_CHAR)); + + + public static void add(TypedParser... parsers) { + parserList.addAll(List.of(parsers)); + } + + public static Object parse(final Class type, final String value) { + return parserList.stream().filter(parser -> parser.canParse(type)).findFirst() + .orElseThrow(IllegalArgumentException::new) + .parse(value); + } +} From 6f4c02a5e6d0ac7f8a7005797c2257145e50def9 Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:37:21 +0900 Subject: [PATCH 10/15] [feat] parameterTypedParser --- README.md | 2 +- .../web/parameter/ParameterTypedParser.java | 58 +++++++++++++++++++ .../parameter/ParameterTypedParserTest.java | 52 +++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 mvc/src/main/java/com/interface21/web/parameter/ParameterTypedParser.java create mode 100644 mvc/src/test/java/com/interface21/web/parameter/ParameterTypedParserTest.java diff --git a/README.md b/README.md index be46e9ba..00ae3b5f 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,6 @@ - [ ] method의 parameter types를 통해 parsing할 수 있는 parameter parser - [X] query param parser -> 메소드 파라미터의 type, name 보고 request.getParameter 에서 추출 - [X] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 - - [ ] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 + - [X] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 - [ ] handlerExecution에서 invoke 시점에 parser로 parsing - [ ] testusercontroller로 확인 diff --git a/mvc/src/main/java/com/interface21/web/parameter/ParameterTypedParser.java b/mvc/src/main/java/com/interface21/web/parameter/ParameterTypedParser.java new file mode 100644 index 00000000..c31ab5bb --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/parameter/ParameterTypedParser.java @@ -0,0 +1,58 @@ +package com.interface21.web.parameter; + +import com.interface21.web.support.TypedParsers; +import jakarta.servlet.http.HttpServletRequest; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; + +public class ParameterTypedParser implements ParameterParser { + private final Class type; + + public ParameterTypedParser(Class type) { + this.type = type; + } + + @Override + public Object parse(Method method, Parameter parameter, HttpServletRequest request) { + if (!parameter.getType().equals(type)) { + return null; + } + var constructors = type.getDeclaredConstructors(); + var fullyFieldConstructor = Arrays.stream(constructors).filter(constructor -> constructor.getParameterCount() == type.getDeclaredFields().length).findFirst(); + if (fullyFieldConstructor.isPresent()) { + return parseFully(fullyFieldConstructor.get(), request); + } + var defaultConstructor = Arrays.stream(constructors).filter(constructor -> constructor.getParameterCount() == 0).findFirst(); + return defaultConstructor.map(constructor -> parseDefault(constructor, request)).orElse(null); + } + + private Object parseFully(Constructor constructor, HttpServletRequest request) { + var parameters = constructor.getParameters(); + var args = new Object[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + args[i] = TypedParsers.parse(parameters[i].getType(), request.getParameter(parameters[i].getName())); + } + try { + return constructor.newInstance(args); + } catch (Exception e) { + throw new IllegalArgumentException(); + } + } + + private Object parseDefault(Constructor constructor, HttpServletRequest request) { + try { + var instance = constructor.newInstance(); + for (Field field : type.getDeclaredFields()) { + field.setAccessible(true); + field.set(instance, TypedParsers.parse(field.getType(), request.getParameter(field.getName()))); + } + return instance; + } catch (Exception e) { + throw new IllegalArgumentException(); + } + } +} diff --git a/mvc/src/test/java/com/interface21/web/parameter/ParameterTypedParserTest.java b/mvc/src/test/java/com/interface21/web/parameter/ParameterTypedParserTest.java new file mode 100644 index 00000000..1eba9062 --- /dev/null +++ b/mvc/src/test/java/com/interface21/web/parameter/ParameterTypedParserTest.java @@ -0,0 +1,52 @@ +package com.interface21.web.parameter; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; +import samples.TestUser; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +class ParameterTypedParserTest { + + private final ParameterTypedParser parser = new ParameterTypedParser(TestUser.class); + + + @Test + void cannot_parse_nomatch_type() throws NoSuchMethodException { + var parameter = TestTypedParamClass.class.getDeclaredMethod("cannot_parse_nomatch_type", String.class).getParameters()[0]; + + assertThat(parser.parse(null, parameter, null)).isNull(); + } + + @Test + void parse_by_requestParam() throws NoSuchMethodException { + var parameter = TestTypedParamClass.class.getDeclaredMethod("parse_by_requestParam", TestUser.class).getParameters()[0]; + var request = mock(HttpServletRequest.class); + when(request.getParameter("userId")).thenReturn("woo-yu"); + when(request.getParameter("password")).thenReturn("pass"); + when(request.getParameter("age")).thenReturn("1234"); + + var result = (TestUser) parser.parse(null, parameter, request); + + assertThat(result.getUserId()).isEqualTo("woo-yu"); + assertThat(result.getPassword()).isEqualTo("pass"); + assertThat(result.getAge()).isEqualTo(1234); + + } + + +} + +class TestTypedParamClass { + + public void cannot_parse_nomatch_type(String name) { + + } + + public void parse_by_requestParam(TestUser user) { + + } +} \ No newline at end of file From 6c2ebc98c0c0b6b6fac3862fd381684b651741e2 Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:39:37 +0900 Subject: [PATCH 11/15] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=B3=B8=20parser?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20queryparam,=20pathvariable=20parser=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../web/parameter/ParameterParsers.java | 48 +++++++++++++++++++ .../ControllerAdviceHandlerMapping.java | 7 ++- .../servlet/ControllerHandlerMapping.java | 7 ++- .../webmvc/servlet/DispatcherServlet.java | 8 +++- .../webmvc/servlet/HandlerExecution.java | 7 ++- .../servlet/ControllerHandlerMappingTest.java | 4 +- 7 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 mvc/src/main/java/com/interface21/web/parameter/ParameterParsers.java diff --git a/README.md b/README.md index 00ae3b5f..e7a9111b 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ ### [STEP4] 4단계 - Controller 메서드 인자 매핑 #### TODO -- [ ] method의 parameter types를 통해 parsing할 수 있는 parameter parser +- [X] method의 parameter types를 통해 parsing할 수 있는 parameter parser - [X] query param parser -> 메소드 파라미터의 type, name 보고 request.getParameter 에서 추출 - [X] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 - [X] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 -- [ ] handlerExecution에서 invoke 시점에 parser로 parsing +- [X] handlerExecution에서 invoke 시점에 parser로 parsing - [ ] testusercontroller로 확인 diff --git a/mvc/src/main/java/com/interface21/web/parameter/ParameterParsers.java b/mvc/src/main/java/com/interface21/web/parameter/ParameterParsers.java new file mode 100644 index 00000000..bcca4b5c --- /dev/null +++ b/mvc/src/main/java/com/interface21/web/parameter/ParameterParsers.java @@ -0,0 +1,48 @@ +package com.interface21.web.parameter; + +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.Arrays; +import java.util.List; +import java.util.Objects; + +public class ParameterParsers { + + private final List parsers = new ArrayList<>(Arrays.asList(new PathVariableParser(), new QueryParamParser())); + + public ParameterParsers() { + + } + + public ParameterParsers(ParameterParser... parsers) { + this.parsers.addAll(Arrays.asList(parsers)); + } + + public Object[] parse(final Method method, final HttpServletRequest request, final HttpServletResponse response) { + var parameters = method.getParameters(); + var params = new Object[parameters.length]; + + for (int i = 0; i < parameters.length; i++) { + var parsedParam = parse(method, parameters[i], request, response); + params[i] = parsedParam; + } + return params; + } + + private Object parse(final Method method, final Parameter parameter, final HttpServletRequest request, final HttpServletResponse response) { + if (parameter.getType().equals(HttpServletRequest.class)) { + return request; + } + if (parameter.getType().equals(HttpServletResponse.class)) { + return response; + } + + return parsers.stream().map(parser -> parser.parse(method, parameter, request)).filter(Objects::nonNull).findFirst() + .orElseThrow(IllegalArgumentException::new); + } + +} diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerAdviceHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerAdviceHandlerMapping.java index 542d3ccf..cee4a069 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerAdviceHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerAdviceHandlerMapping.java @@ -2,15 +2,18 @@ import com.interface21.context.stereotype.ControllerAdvice; import com.interface21.web.bind.annotation.ExceptionHandler; +import com.interface21.web.parameter.ParameterParsers; import java.lang.reflect.Method; import java.util.Arrays; public class ControllerAdviceHandlerMapping implements HandlerMapping { + private final ParameterParsers parsers; private final Object[] basePackage; - public ControllerAdviceHandlerMapping(final Object... basePackage) { + public ControllerAdviceHandlerMapping(ParameterParsers parsers, final Object... basePackage) { + this.parsers = parsers; this.basePackage = basePackage; } @@ -28,7 +31,7 @@ private void mappingHandler(final ExceptionHandlers handlers, final Object contr return; } for (Class exceptionClass : handler.value()) { - var execution = new HandlerExecution(controllerInstance, method); + var execution = new HandlerExecution(controllerInstance, method, parsers); handlers.add(exceptionClass, execution); } } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerHandlerMapping.java index 674e1fdd..ab2408b2 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/ControllerHandlerMapping.java @@ -3,15 +3,18 @@ import com.interface21.context.stereotype.Controller; import com.interface21.web.bind.annotation.RequestMapping; import com.interface21.web.bind.annotation.RequestMethod; +import com.interface21.web.parameter.ParameterParsers; import java.lang.reflect.Method; import java.util.Arrays; public class ControllerHandlerMapping implements HandlerMapping { + private final ParameterParsers parsers; private final Object[] basePackage; - public ControllerHandlerMapping(final Object... basePackage) { + public ControllerHandlerMapping(final ParameterParsers parsers, final Object... basePackage) { + this.parsers = parsers; this.basePackage = basePackage; } @@ -29,7 +32,7 @@ private void mappingHandler(final HttpRequestHandlers handlers, final Object con } for (RequestMethod requestMethod : handler.method()) { var key = new HandlerKey(handler.value(), requestMethod); - var execution = new HandlerExecution(controllerInstance, method); + var execution = new HandlerExecution(controllerInstance, method, parsers); handlers.add(key, execution); } } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/DispatcherServlet.java b/mvc/src/main/java/com/interface21/webmvc/servlet/DispatcherServlet.java index ec2e83fd..a25c72cc 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/DispatcherServlet.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/DispatcherServlet.java @@ -1,5 +1,8 @@ package com.interface21.webmvc.servlet; +import com.interface21.web.parameter.ParameterParsers; +import com.interface21.web.parameter.PathVariableParser; +import com.interface21.web.parameter.QueryParamParser; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -18,8 +21,9 @@ public class DispatcherServlet extends HttpServlet { private final HandlerAdapterRegistry handlerAdapters = new HandlerAdapterRegistry(); public DispatcherServlet() { - requestHandlers.addHandlerMapping(new ControllerHandlerMapping(BASE_PACKAGE)); - exceptionHandlers.addHandlerMapping(new ControllerAdviceHandlerMapping(BASE_PACKAGE)); + var parameterParsers = new ParameterParsers(new PathVariableParser(), new QueryParamParser()); + requestHandlers.addHandlerMapping(new ControllerHandlerMapping(parameterParsers, BASE_PACKAGE)); + exceptionHandlers.addHandlerMapping(new ControllerAdviceHandlerMapping(parameterParsers, BASE_PACKAGE)); handlerAdapters.addAdapter(new RequestHandlerAdapter()); } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerExecution.java b/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerExecution.java index 83036789..459cbc8a 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerExecution.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerExecution.java @@ -1,5 +1,6 @@ package com.interface21.webmvc.servlet; +import com.interface21.web.parameter.ParameterParsers; import com.interface21.webmvc.servlet.view.JspView; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -9,14 +10,16 @@ public class HandlerExecution { private final Object controllerInstance; private final Method method; + private final ParameterParsers parsers; - public HandlerExecution(Object controllerInstance, Method method) { + public HandlerExecution(Object controllerInstance, Method method, ParameterParsers parsers) { this.controllerInstance = controllerInstance; this.method = method; + this.parsers = parsers; } public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception { - var result = method.invoke(controllerInstance, request, response); + var result = method.invoke(controllerInstance, parsers.parse(method, request, response)); if (result instanceof ModelAndView) { return (ModelAndView) result; } diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/ControllerHandlerMappingTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/ControllerHandlerMappingTest.java index c41c8c91..2dc642f2 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/ControllerHandlerMappingTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/ControllerHandlerMappingTest.java @@ -1,5 +1,6 @@ package com.interface21.webmvc.servlet; +import com.interface21.web.parameter.ParameterParsers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; @@ -13,10 +14,11 @@ class ControllerHandlerMappingTest { private ControllerHandlerMapping handlerMapping; private HttpRequestHandlers handlers = new HttpRequestHandlers(); + private ParameterParsers parsers = new ParameterParsers(); @BeforeEach void setUp() { - handlerMapping = new ControllerHandlerMapping("samples"); + handlerMapping = new ControllerHandlerMapping(parsers, "samples"); handlerMapping.initialize(handlers); } From d1ebe4099567251515161624637c2d0486ccf0b8 Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:40:46 +0900 Subject: [PATCH 12/15] =?UTF-8?q?[fix]=20HandlerKey=EC=97=90=20path=20?= =?UTF-8?q?=EC=9D=BC=EC=B9=98=EA=B0=80=20=EC=95=84=EB=8B=8C=20uri=20patter?= =?UTF-8?q?n=20match=EB=A1=9C=EB=8F=84=20=EA=B2=80=EC=A6=9D=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/interface21/webmvc/servlet/HandlerKey.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerKey.java b/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerKey.java index 60e83bec..2c96cc20 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerKey.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/HandlerKey.java @@ -1,6 +1,7 @@ package com.interface21.webmvc.servlet; import com.interface21.web.bind.annotation.RequestMethod; +import com.interface21.web.support.PathPatternUtil; import jakarta.servlet.http.HttpServletRequest; import java.util.Objects; @@ -16,7 +17,7 @@ public HandlerKey(final String url, final RequestMethod requestMethod) { } public boolean matchUrl(final HttpServletRequest request) { - return request.getRequestURI().equals(url); + return request.getRequestURI().equals(url) || PathPatternUtil.isUrlMatch(url, request.getRequestURI()); } public boolean matchMethod(final HttpServletRequest request) { From 6bff2227f85e1f8a66f71d0e992ce732c78a3aa9 Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:40:59 +0900 Subject: [PATCH 13/15] =?UTF-8?q?[test]=20TestUserController=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../webmvc/servlet/ComponentScannerTest.java | 5 +- .../servlet/TestUserControllerTest.java | 101 ++++++++++++++++++ .../test/java/samples/TestUserController.java | 8 +- 4 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/TestUserControllerTest.java diff --git a/README.md b/README.md index e7a9111b..7aedb00e 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,4 @@ - [X] path variable parser -> @PathVariable이 있을경우 메소드 파라미터의 type, name 보고 /{}/{} path에 특정한 양식 ({})으로 추출 - [X] parameter typed parser -> 특정 타입을 deserialization. request.getParameter에서 추출 - [X] handlerExecution에서 invoke 시점에 parser로 parsing -- [ ] testusercontroller로 확인 +- [X] testusercontroller로 확인 diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/ComponentScannerTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/ComponentScannerTest.java index 57758e68..bd883434 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/ComponentScannerTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/ComponentScannerTest.java @@ -1,18 +1,15 @@ package com.interface21.webmvc.servlet; import com.interface21.context.stereotype.Controller; -import com.interface21.webmvc.servlet.ComponentScanner; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import samples.TestController; class ComponentScannerTest { @Test void test() { var components = ComponentScanner.scan(Controller.class, "samples"); - Assertions.assertThat(components).hasSize(1); - Assertions.assertThat(components.get(0).methods).hasSize(2); + Assertions.assertThat(components).hasSize(2); } } \ No newline at end of file diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/TestUserControllerTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/TestUserControllerTest.java new file mode 100644 index 00000000..ad4d168d --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/TestUserControllerTest.java @@ -0,0 +1,101 @@ +package com.interface21.webmvc.servlet; + +import com.interface21.web.parameter.ParameterParser; +import com.interface21.web.parameter.ParameterParsers; +import com.interface21.web.parameter.ParameterTypedParser; +import com.interface21.web.support.TypedParser; +import com.interface21.web.support.TypedParsers; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import samples.TestUser; + +import java.lang.reflect.*; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TestUserControllerTest { + + private ControllerHandlerMapping handlerMapping; + private HttpRequestHandlers handlers = new HttpRequestHandlers(); + private ParameterParsers parsers = new ParameterParsers(new ParameterTypedParser(TestUser.class)); + + @BeforeEach + void setUp() { + handlerMapping = new ControllerHandlerMapping(parsers, "samples"); + handlerMapping.initialize(handlers); + } + + @Test + void users_string() throws Exception { + final var request = mock(HttpServletRequest.class); + final var response = mock(HttpServletResponse.class); + + when(request.getParameter("userId")).thenReturn("gugu"); + when(request.getParameter("password")).thenReturn("password"); + when(request.getRequestURI()).thenReturn("/users/string"); + when(request.getMethod()).thenReturn("POST"); + + final var handlerExecution = (HandlerExecution) handlers.getHandler(request); + final var modelAndView = handlerExecution.handle(request, response); + + assertThat(modelAndView.getObject("userId")).isEqualTo("gugu"); + assertThat(modelAndView.getObject("password")).isEqualTo("password"); + } + + + @Test + void users_number() throws Exception { + final var request = mock(HttpServletRequest.class); + final var response = mock(HttpServletResponse.class); + + when(request.getParameter("id")).thenReturn("123"); + when(request.getParameter("age")).thenReturn("22"); + when(request.getRequestURI()).thenReturn("/users/number"); + when(request.getMethod()).thenReturn("POST"); + + final var handlerExecution = (HandlerExecution) handlers.getHandler(request); + final var modelAndView = handlerExecution.handle(request, response); + + assertThat(modelAndView.getObject("id")).isEqualTo(123L); + assertThat(modelAndView.getObject("age")).isEqualTo(22); + } + + @Test + void users_bean() throws Exception { + final var request = mock(HttpServletRequest.class); + final var response = mock(HttpServletResponse.class); + + when(request.getParameter("userId")).thenReturn("woo-yu"); + when(request.getParameter("password")).thenReturn("pass"); + when(request.getParameter("age")).thenReturn("123"); + when(request.getRequestURI()).thenReturn("/users/bean"); + when(request.getMethod()).thenReturn("POST"); + + final var handlerExecution = (HandlerExecution) handlers.getHandler(request); + final var modelAndView = handlerExecution.handle(request, response); + final var result = (TestUser) modelAndView.getObject("testUser"); + + assertThat(result.getUserId()).isEqualTo("woo-yu"); + assertThat(result.getPassword()).isEqualTo("pass"); + assertThat(result.getAge()).isEqualTo(123); + } + + @Test + void users_pathVariable() throws Exception { + final var request = mock(HttpServletRequest.class); + final var response = mock(HttpServletResponse.class); + + when(request.getRequestURI()).thenReturn("/users/123"); + when(request.getMethod()).thenReturn("GET"); + + final var handlerExecution = (HandlerExecution) handlers.getHandler(request); + final var modelAndView = handlerExecution.handle(request, response); + + assertThat(modelAndView.getObject("id")).isEqualTo(123L); + } +} \ No newline at end of file diff --git a/mvc/src/test/java/samples/TestUserController.java b/mvc/src/test/java/samples/TestUserController.java index a669cb42..e4526f7e 100644 --- a/mvc/src/test/java/samples/TestUserController.java +++ b/mvc/src/test/java/samples/TestUserController.java @@ -1,5 +1,6 @@ package samples; +import com.interface21.context.stereotype.Controller; import com.interface21.web.bind.annotation.PathVariable; import com.interface21.web.bind.annotation.RequestMapping; import com.interface21.web.bind.annotation.RequestMethod; @@ -8,11 +9,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Controller public class TestUserController { private static final Logger log = LoggerFactory.getLogger(TestUserController.class); - @RequestMapping(value = "/users", method = RequestMethod.POST) + @RequestMapping(value = "/users/string", method = RequestMethod.POST) public ModelAndView create_string(String userId, String password) { log.debug("userId: {}, password: {}", userId, password); ModelAndView mav = new ModelAndView(new JsonView()); @@ -21,7 +23,7 @@ public ModelAndView create_string(String userId, String password) { return mav; } - @RequestMapping(value = "/users", method = RequestMethod.POST) + @RequestMapping(value = "/users/number", method = RequestMethod.POST) public ModelAndView create_int_long(long id, int age) { log.debug("id: {}, age: {}", id, age); ModelAndView mav = new ModelAndView(new JsonView()); @@ -30,7 +32,7 @@ public ModelAndView create_int_long(long id, int age) { return mav; } - @RequestMapping(value = "/users", method = RequestMethod.POST) + @RequestMapping(value = "/users/bean", method = RequestMethod.POST) public ModelAndView create_javabean(TestUser testUser) { log.debug("testUser: {}", testUser); ModelAndView mav = new ModelAndView(new JsonView()); From 3845f67ed95dffc4258b933718506a350f5b812f Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:42:44 +0900 Subject: [PATCH 14/15] =?UTF-8?q?[fix]=20null=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20TypedParser=EB=A5=BC=20=EA=B1=B0=EC=B9=98=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/interface21/web/parameter/PathVariableParser.java | 3 +++ .../java/com/interface21/web/parameter/QueryParamParser.java | 3 +++ .../com/interface21/web/parameter/PathVariableParserTest.java | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java b/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java index 5c399d83..e80f0e37 100644 --- a/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java +++ b/mvc/src/main/java/com/interface21/web/parameter/PathVariableParser.java @@ -24,6 +24,9 @@ public Object parse(Method method, Parameter parameter, HttpServletRequest reque name = parameter.getName(); } var param = PathPatternUtil.getUriValue(requestMapping.value(), request.getRequestURI(), name); + if (param == null) { + return null; + } return TypedParsers.parse(parameter.getType(), param); } diff --git a/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java b/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java index 80c4930f..4768b448 100644 --- a/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java +++ b/mvc/src/main/java/com/interface21/web/parameter/QueryParamParser.java @@ -12,6 +12,9 @@ public class QueryParamParser implements ParameterParser { @Override public Object parse(Method method, Parameter parameter, HttpServletRequest request) { var param = request.getParameter(parameter.getName()); + if (param == null) { + return null; + } return TypedParsers.parse(parameter.getType(), param); } diff --git a/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java b/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java index 112bc09c..3b850623 100644 --- a/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java +++ b/mvc/src/test/java/com/interface21/web/parameter/PathVariableParserTest.java @@ -45,8 +45,8 @@ void can_parse_by_matched() throws NoSuchMethodException { var parameter1 = method.getParameters()[0]; var parameter2 = method.getParameters()[1]; - assertThat(parser.parse(method, parameter1, request)).isEqualTo("1"); - assertThat(parser.parse(method, parameter2, request)).isEqualTo("2"); + assertThat(parser.parse(method, parameter1, request)).isEqualTo(1L); + assertThat(parser.parse(method, parameter2, request)).isEqualTo(2); } } From 125fbffa7240b9a679ee25aa40820f05a299d6ed Mon Sep 17 00:00:00 2001 From: woo-yu Date: Mon, 19 Aug 2024 12:43:56 +0900 Subject: [PATCH 15/15] =?UTF-8?q?[feat]=20IllegalArugmentException=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/support/DefaultExceptionHandlers.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/camp/nextstep/controller/support/DefaultExceptionHandlers.java b/app/src/main/java/camp/nextstep/controller/support/DefaultExceptionHandlers.java index 3bb99768..b438dc7d 100644 --- a/app/src/main/java/camp/nextstep/controller/support/DefaultExceptionHandlers.java +++ b/app/src/main/java/camp/nextstep/controller/support/DefaultExceptionHandlers.java @@ -10,6 +10,11 @@ @ControllerAdvice public class DefaultExceptionHandlers { + @ExceptionHandler(IllegalArgumentException.class) + public void handle(IllegalArgumentException ex, HttpServletRequest request, HttpServletResponse response) { + response.setStatus(400); + } + @ExceptionHandler(NoHandlerFoundException.class) public void handle(NoHandlerFoundException ex, HttpServletRequest request, HttpServletResponse response) { response.setStatus(404);