From 04a42c76a43d8622ee3d48f9be700029ad3e5d65 Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Mon, 15 Mar 2021 13:57:14 +0200 Subject: [PATCH 01/26] GP-64 Implement DateServlet.java --- .../com/bobocode/servlet/DateServlet.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 2-0-servlet-api/2-0-1-date-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java diff --git a/2-0-servlet-api/2-0-1-date-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java b/2-0-servlet-api/2-0-1-date-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java new file mode 100644 index 0000000..92c2435 --- /dev/null +++ b/2-0-servlet-api/2-0-1-date-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java @@ -0,0 +1,20 @@ +package com.bobocode.servlet; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.time.LocalDate; + +@WebServlet("/date") +public class DateServlet extends HttpServlet { + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + PrintWriter out = response.getWriter(); + LocalDate date = LocalDate.now(); + out.println(date); + } +} From 15f6562c5c2cb2169b3f9e64de6242c04ca0d275 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Mon, 29 Mar 2021 13:29:50 +0300 Subject: [PATCH 02/26] GP-66 complete exercise * implement NoteController.java * implement NoteRestController.javate --- .../bobocode/mvc/api/NoteRestController.java | 19 +++++++++++- .../mvc/controller/NoteController.java | 30 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java index 24d55e5..a10b412 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java @@ -1,10 +1,27 @@ package com.bobocode.mvc.api; +import com.bobocode.mvc.model.Note; import com.bobocode.mvc.storage.Notes; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import java.util.List; + + +@RestController @RequiredArgsConstructor +@RequestMapping("/api/notes") public class NoteRestController { + private final Notes notes; -} + @GetMapping + public List<Note> getNotes() { + return notes.getAll(); + } + + @PostMapping + public void addNote(@RequestBody Note note) { + notes.add(note); + } +} \ No newline at end of file diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java index cf2c131..19482dd 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java @@ -1,13 +1,41 @@ package com.bobocode.mvc.controller; +import com.bobocode.mvc.model.Note; import com.bobocode.mvc.storage.Notes; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; + +@Controller @RequiredArgsConstructor @RequestMapping("/notes") public class NoteController { + private final Notes notes; + @ModelAttribute + private List<Note> noteListAttr() { + return notes.getAll(); + } + + @ModelAttribute + private Note noteAttr() { + return new Note(); + } + + @GetMapping + public String getNotes() { + return "notes"; + } -} + @PostMapping + public String addNote(Note note) { + notes.add(note); + return "redirect:/notes"; + } +} \ No newline at end of file From 87ac5a3cefae8261195a5d71377a376eb28eff78 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Mon, 29 Mar 2021 14:00:11 +0300 Subject: [PATCH 03/26] GP-66 update impl --- .../mvc/controller/NoteController.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java index 19482dd..16695ed 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java @@ -4,32 +4,21 @@ import com.bobocode.mvc.storage.Notes; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; - -import java.util.List; +import org.springframework.web.bind.annotation.RequestParam; @Controller @RequiredArgsConstructor @RequestMapping("/notes") public class NoteController { - private final Notes notes; - @ModelAttribute - private List<Note> noteListAttr() { - return notes.getAll(); - } - - @ModelAttribute - private Note noteAttr() { - return new Note(); - } - @GetMapping - public String getNotes() { + public String getNotes(Model model) { + model.addAttribute("noteList", notes.getAll()); return "notes"; } From 9904be1faf3c008374e2961c2de42ab3d7bb2283 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Mon, 29 Mar 2021 14:45:26 +0300 Subject: [PATCH 04/26] GP-66 improve hello-spring-mvc exercise * add javadoc * add todo section * upgrade tests --- 3-0-spring-mvc/3-0-1-hello-spring-mvc/pom.xml | 11 ----------- .../com/bobocode/mvc/{storage => data}/Notes.java | 2 +- .../src/main/resources/templates/notes.html | 6 +++--- .../src/test/java/NoteControllerTest.java | 3 +-- .../src/test/java/RestControllerTest.java | 14 ++++++-------- 3-0-spring-mvc/pom.xml | 6 ------ pom.xml | 6 ++++++ 7 files changed, 17 insertions(+), 31 deletions(-) rename 3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/{storage => data}/Notes.java (93%) diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/pom.xml b/3-0-spring-mvc/3-0-1-hello-spring-mvc/pom.xml index 37ff45d..d4cefb8 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/pom.xml +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/pom.xml @@ -12,22 +12,12 @@ <artifactId>3-0-1-hello-spring-mvc</artifactId> <dependencies> - <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> - <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <version>1.18.18</version> - <scope>provided</scope> - </dependency> - - <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.4.3</version> </dependency> - <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> @@ -35,7 +25,6 @@ <scope>test</scope> </dependency> - <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/storage/Notes.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/data/Notes.java similarity index 93% rename from 3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/storage/Notes.java rename to 3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/data/Notes.java index c8c205d..0274cc6 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/storage/Notes.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/data/Notes.java @@ -1,4 +1,4 @@ -package com.bobocode.mvc.storage; +package com.bobocode.mvc.data; import com.bobocode.mvc.model.Note; diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/resources/templates/notes.html b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/resources/templates/notes.html index fdb3386..c825110 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/resources/templates/notes.html +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/resources/templates/notes.html @@ -16,11 +16,11 @@ <h1 class="title">My notes</h1> <div class="form_block"> - <form action="#" th:action="@{/notes}" th:method="post" th:object="${note}"> + <form action="#" th:action="@{/notes}" th:method="post"> <label for="title_input">Title</label> - <input id="title_input" type="text" placeholder="Enter title" required th:field="*{title}"> + <input id="title_input" name="title" type="text" placeholder="Enter title" required> <label for="text_input" id="text_label">Your note</label> - <textarea id="text_input" placeholder="Your text" required rows="1" th:field="*{text}"></textarea> + <textarea id="text_input" name="text" placeholder="Your text" required rows="1"></textarea> <input type="submit" value="Add" class="input_submit"> </form> diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/NoteControllerTest.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/NoteControllerTest.java index d67e120..4ccf8fc 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/NoteControllerTest.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/NoteControllerTest.java @@ -1,7 +1,6 @@ import com.bobocode.mvc.HelloSpringMvcApp; import com.bobocode.mvc.model.Note; -import com.bobocode.mvc.storage.Notes; -import org.junit.jupiter.api.BeforeEach; +import com.bobocode.mvc.data.Notes; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/RestControllerTest.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/RestControllerTest.java index 85a3160..6087654 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/RestControllerTest.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/test/java/RestControllerTest.java @@ -1,10 +1,8 @@ import com.bobocode.mvc.HelloSpringMvcApp; +import com.bobocode.mvc.data.Notes; import com.bobocode.mvc.model.Note; -import com.bobocode.mvc.storage.Notes; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -12,11 +10,11 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @AutoConfigureMockMvc @SpringBootTest(classes = HelloSpringMvcApp.class) @@ -29,7 +27,7 @@ public class RestControllerTest { private MockMvc mockMvc; @Test - void getAll() throws Exception { + void getAllNotes() throws Exception { notes.add(new Note("Title 1", "Text 1")); mockMvc.perform(get("/api/notes")) @@ -58,7 +56,7 @@ void addNote() throws Exception { } @Test - void statusIs4xxWhenRequiredFieldsAreEmpty() throws Exception { + void addNoteRespondWithClientErrorWhenFieldsAreEmpty() throws Exception { mockMvc.perform(post("/api/notes") .contentType(MediaType.APPLICATION_JSON) .content(asJsonString(new Note())) diff --git a/3-0-spring-mvc/pom.xml b/3-0-spring-mvc/pom.xml index 19c7f01..e693ab1 100644 --- a/3-0-spring-mvc/pom.xml +++ b/3-0-spring-mvc/pom.xml @@ -9,15 +9,9 @@ </parent> <modelVersion>4.0.0</modelVersion> - <artifactId>3-0-spring-mvc</artifactId> <packaging>pom</packaging> - <properties> - <maven.compiler.source>11</maven.compiler.source> - <maven.compiler.target>11</maven.compiler.target> - </properties> - <modules> <module>3-0-1-hello-spring-mvc</module> </modules> diff --git a/pom.xml b/pom.xml index 80eaf12..0056245 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,12 @@ </properties> <dependencies> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.18</version> + <scope>provided</scope> + </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> From 421ef935da920164e3ca5d5c10c06383f2e79a25 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Mon, 29 Mar 2021 17:01:40 +0300 Subject: [PATCH 05/26] GP-66 minor fixes --- .../src/main/java/com/bobocode/mvc/api/NoteRestController.java | 2 +- .../main/java/com/bobocode/mvc/controller/NoteController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java index 37d6cfe..088d967 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/api/NoteRestController.java @@ -1,7 +1,7 @@ package com.bobocode.mvc.api; -import com.bobocode.mvc.model.Note; import com.bobocode.mvc.data.Notes; +import com.bobocode.mvc.model.Note; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; diff --git a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java index 606867c..40b3736 100644 --- a/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java +++ b/3-0-spring-mvc/3-0-1-hello-spring-mvc/src/main/java/com/bobocode/mvc/controller/NoteController.java @@ -1,7 +1,7 @@ package com.bobocode.mvc.controller; -import com.bobocode.mvc.model.Note; import com.bobocode.mvc.data.Notes; +import com.bobocode.mvc.model.Note; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; From 2f27050cb30695c4bcd076b70c1f3d40109c6fa5 Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Mon, 5 Apr 2021 12:46:35 +0300 Subject: [PATCH 06/26] GP-67 Completed solution --- .../main/java/com/bobocode/config/ApplicationConfig.java | 6 ++++++ .../src/main/java/com/bobocode/dao/FakeAccountDao.java | 2 ++ .../src/main/java/com/bobocode/service/AccountService.java | 1 + 3 files changed, 9 insertions(+) diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java index 52a92d3..dbf1cd3 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java @@ -22,6 +22,12 @@ * todo 4: Don't specify bean name "dataGenerator" explicitly */ +@Configuration +@ComponentScan(basePackages = {"com.bobocode.dao","com.bobocode.service"}) public class ApplicationConfig { + @Bean + public TestDataGenerator dataGenerator() { + return new TestDataGenerator(); + } } diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java index 9543845..9682222 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java @@ -18,9 +18,11 @@ * todo: use explicit (with {@link Autowired} annotation) constructor-based dependency injection for specific bean */ +@Component("accountDao") public class FakeAccountDao implements AccountDao { private List<Account> accounts; + @Autowired public FakeAccountDao(TestDataGenerator testDataGenerator) { this.accounts = Stream.generate(testDataGenerator::generateAccount) .limit(20) diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java index 3fa68b6..9ebd3cd 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java @@ -16,6 +16,7 @@ * todo: use implicit constructor-based dependency injection (don't use {@link org.springframework.beans.factory.annotation.Autowired}) */ +@Service public class AccountService { private final AccountDao accountDao; From 75acdf3fee8320d4ae191706728e2c10539d832c Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Tue, 6 Apr 2021 17:54:41 +0300 Subject: [PATCH 07/26] GP-82 completed solution --- .../src/main/java/com/bobocode/config/RootConfig.java | 3 +++ .../main/java/com/bobocode/config/WebAppInitializer.java | 6 +++--- .../src/main/java/com/bobocode/config/WebConfig.java | 4 +++- .../com/bobocode/web/controller/WelcomeController.java | 7 +++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/RootConfig.java b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/RootConfig.java index 6536634..2c55fbf 100644 --- a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/RootConfig.java +++ b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/RootConfig.java @@ -15,5 +15,8 @@ * todo: enable component scanning for all packages in "com.bobocode" * todo: ignore all web related config and beans (ignore @{@link Controller}, ignore {@link EnableWebMvc}) using exclude filter */ +@Configuration +@ComponentScan(basePackages = "com.bobocode", excludeFilters = + {@Filter(Controller.class), @Filter(EnableWebMvc.class)}) public class RootConfig { } diff --git a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebAppInitializer.java b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebAppInitializer.java index ee23400..e5e154f 100644 --- a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebAppInitializer.java +++ b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebAppInitializer.java @@ -9,16 +9,16 @@ public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { - throw new ExerciseNotCompletedException(); //todo: use {@link RootConfig} as root application config class + return new Class[]{RootConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { - throw new ExerciseNotCompletedException(); //todo: use {@link WebConfig} as ServletConfig class + return new Class[]{WebConfig.class}; } @Override protected String[] getServletMappings() { - throw new ExerciseNotCompletedException(); //todo: provide default servlet mapping ("/") + return new String[]{"/"}; } } diff --git a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebConfig.java b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebConfig.java index 66d3a84..dea1246 100644 --- a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebConfig.java +++ b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/config/WebConfig.java @@ -13,6 +13,8 @@ * todo: enable web mvc using annotation * todo: enable component scanning for package "web" */ - +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.bobocode.web") public class WebConfig { } diff --git a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/web/controller/WelcomeController.java b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/web/controller/WelcomeController.java index 77392cc..12dc892 100644 --- a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/web/controller/WelcomeController.java +++ b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/src/main/java/com/bobocode/web/controller/WelcomeController.java @@ -1,5 +1,9 @@ package com.bobocode.web.controller; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + /** * Welcome controller that consists of one method that handles get request to "/welcome" and respond with a message. * <p> @@ -8,8 +12,11 @@ * todo: tell Spring that {@link WelcomeController#welcome()} method provides response body without view */ +@Controller public class WelcomeController { + @GetMapping("/welcome") + @ResponseBody public String welcome() { return "Welcome to Spring MVC!"; } From 9fec2cf88b9c6f0291bc35b05e98d84c1ae16c51 Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Fri, 16 Apr 2021 11:28:33 +0300 Subject: [PATCH 08/26] GP-83 migrate account-rest-api --- .../3-2-1-account-rest-api/README.MD | 21 +++ .../3-2-1-account-rest-api/pom.xml | 60 ++++++++ .../config/AccountRestApiInitializer.java | 20 +++ .../java/com/bobocode/config/RootConfig.java | 14 ++ .../java/com/bobocode/config/WebConfig.java | 12 ++ .../java/com/bobocode/dao/AccountDao.java | 15 ++ .../bobocode/dao/impl/InMemoryAccountDao.java | 52 +++++++ .../exception/EntityNotFountException.java | 7 + .../web/controller/AccountRestController.java | 21 +++ .../bobocode/AccountRestControllerTest.java | 140 ++++++++++++++++++ 3-0-spring-framework/pom.xml | 1 + 11 files changed, 363 insertions(+) create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/README.MD create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/pom.xml create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/AccountRestApiInitializer.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/AccountDao.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/exception/EntityNotFountException.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java diff --git a/3-0-spring-framework/3-2-1-account-rest-api/README.MD b/3-0-spring-framework/3-2-1-account-rest-api/README.MD new file mode 100644 index 0000000..7c2fc40 --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/README.MD @@ -0,0 +1,21 @@ +# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Account REST API exercise :muscle: +Improve your *Spring MVC* configuration and rest mapping skills +### Task +This webapp provides a **simple REST API for `Account`**. The data is stored using in-memory fake DAO. Your job is to +**configure Spring MVC application** and **implement AccountRestController**. In order to complete the task, please +**follow the instructions in the *todo* section** + +To verify your configuration, run `AccountRestControllerTest.java` :white_check_mark: + + +### Pre-conditions :heavy_exclamation_mark: +You're supposed to be familiar with *Spring MVC* + +### How to start :question: +* Just clone the repository and start implementing the **todo** section, verify your changes by running tests +* If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) +* Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation + +### Related materials :information_source: + * [Spring REST basics tutorial](https://github.com/bobocode-projects/spring-framework-tutorial/tree/master/rest-basics)<img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=20/> + diff --git a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml new file mode 100644 index 0000000..1a7a50a --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>3-0-spring-framework</artifactId> + <groupId>com.bobocode</groupId> + <version>1.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>3-2-1-account-rest-api</artifactId> + <packaging>war</packaging> + + <dependencies> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>5.0.7.RELEASE</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>4.0.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.bobocode</groupId> + <artifactId>spring-framework-exercises-util</artifactId> + <version>1.0-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>2.9.7</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.9.7</version> + </dependency> + </dependencies> + + <build> + <plugins> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>2.6</version> + <configuration> + <failOnMissingWebXml>false</failOnMissingWebXml> + </configuration> + </plugin> + + </plugins> + </build> + + +</project> \ No newline at end of file diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/AccountRestApiInitializer.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/AccountRestApiInitializer.java new file mode 100644 index 0000000..38798ff --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/AccountRestApiInitializer.java @@ -0,0 +1,20 @@ +package com.bobocode.config; + +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; + +public class AccountRestApiInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { + @Override + protected Class<?>[] getRootConfigClasses() { + return new Class[]{RootConfig.class}; + } + + @Override + protected Class<?>[] getServletConfigClasses() { + return new Class[]{WebConfig.class}; + } + + @Override + protected String[] getServletMappings() { + return new String[]{"/"}; + } +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java new file mode 100644 index 0000000..c5d1a6d --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java @@ -0,0 +1,14 @@ +package com.bobocode.config; + +import org.springframework.stereotype.Controller; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * This class provides application root (non-web) configuration. + * <p> + * todo: 1. Mark this class as config + * todo: 2. Enable component scanning for all packages in "com.bobocode" using annotation property "basePackages" + * todo: 3. Exclude web related config and beans (ignore @{@link Controller}, ignore {@link EnableWebMvc}) + */ +public class RootConfig { +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java new file mode 100644 index 0000000..6284d6a --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java @@ -0,0 +1,12 @@ +package com.bobocode.config; + +/** + * This class provides web (servlet) related configuration. + * <p> + * todo: 1. Mark this class as Spring config class + * todo: 2. Enable web mvc using annotation + * todo: 3. Enable component scanning for package "web" using annotation value + */ +public class WebConfig { + +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/AccountDao.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/AccountDao.java new file mode 100644 index 0000000..0ac5d36 --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/AccountDao.java @@ -0,0 +1,15 @@ +package com.bobocode.dao; + +import com.bobocode.model.Account; + +import java.util.List; + +public interface AccountDao { + List<Account> findAll(); + + Account findById(long id); + + Account save(Account account); + + void remove(Account account); +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java new file mode 100644 index 0000000..af5f026 --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java @@ -0,0 +1,52 @@ +package com.bobocode.dao.impl; + +import com.bobocode.dao.AccountDao; +import com.bobocode.exception.EntityNotFountException; +import com.bobocode.model.Account; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * {@link AccountDao} implementation that is based on {@link java.util.HashMap}. + * <p> + * todo: 1. Configure a component with name "accountDao" + */ +public class InMemoryAccountDao implements AccountDao { + private Map<Long, Account> accountMap = new HashMap<>(); + private long idSequence = 1L; + + @Override + public List<Account> findAll() { + return new ArrayList<>(accountMap.values()); + } + + @Override + public Account findById(long id) { + Account account = accountMap.get(id); + if (account == null) { + throw new EntityNotFountException(String.format("Cannot found account by id = %d", id)); + } + return account; + } + + @Override + public Account save(Account account) { + if (account.getId() == null) { + account.setId(idSequence++); + } + accountMap.put(account.getId(), account); + return account; + } + + @Override + public void remove(Account account) { + accountMap.remove(account.getId()); + } + + public void clear() { + accountMap.clear(); + } +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/exception/EntityNotFountException.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/exception/EntityNotFountException.java new file mode 100644 index 0000000..852eb13 --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/exception/EntityNotFountException.java @@ -0,0 +1,7 @@ +package com.bobocode.exception; + +public class EntityNotFountException extends RuntimeException { + public EntityNotFountException(String message) { + super(message); + } +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java new file mode 100644 index 0000000..84fb818 --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java @@ -0,0 +1,21 @@ +package com.bobocode.web.controller; + +import com.bobocode.dao.AccountDao; + +/** + * <p> + * todo: 1. Configure rest controller that handles requests with url "/accounts" + * todo: 2. Inject {@link AccountDao} implementation + * todo: 3. Implement method that handles GET request and returns a list of accounts + * todo: 4. Implement method that handles GET request with id as path variable and returns account by id + * todo: 5. Implement method that handles POST request, receives account as request body, saves account and returns it + * todo: Configure HTTP response status code 201 - CREATED + * todo: 6. Implement method that handles PUT request with id as path variable and receives account as request body. + * todo: It check if account id and path variable are the same and throws {@link IllegalStateException} otherwise. + * todo: Then it saves received account. Configure HTTP response status code 204 - NO CONTENT + * todo: 7. Implement method that handles DELETE request with id as path variable removes an account by id + * todo: Configure HTTP response status code 204 - NO CONTENT + */ +public class AccountRestController { + +} diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java new file mode 100644 index 0000000..c2d7a87 --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java @@ -0,0 +1,140 @@ +package com.bobocode; + +import com.bobocode.config.RootConfig; +import com.bobocode.config.WebConfig; +import com.bobocode.dao.impl.InMemoryAccountDao; +import com.bobocode.model.Account; +import com.bobocode.web.controller.AccountRestController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringJUnitWebConfig(classes = {RootConfig.class, WebConfig.class}) +class AccountRestControllerTest { + @Autowired + private WebApplicationContext applicationContext; + + @Autowired + private InMemoryAccountDao accountDao; + + private MockMvc mockMvc; + + @BeforeEach + void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build(); + accountDao.clear(); + } + + @Test + void testAccountRestControllerAnnotation() { + RestController restController = AccountRestController.class.getAnnotation(RestController.class); + + assertThat(restController, notNullValue()); + } + + @Test + void testAccountRestControllerRequestMapping() { + RequestMapping requestMapping = AccountRestController.class.getAnnotation(RequestMapping.class); + + assertThat(requestMapping, notNullValue()); + assertThat(requestMapping.value(), arrayWithSize(1)); + assertThat(requestMapping.value(), arrayContaining("/accounts")); + } + + @Test + void testHttpStatusCodeOnCreate() throws Exception { + mockMvc.perform( + post("/accounts") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"jboy@gmail.com\"}")) + .andExpect(status().isCreated()); + } + + @Test + void testCreateAccountReturnsAssignedId() throws Exception { + mockMvc.perform( + post("/accounts") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"jboy@gmail.com\"}")) + .andExpect(jsonPath("$.id").value(1L)); + } + + @Test + void testGetAccountsResponseStatusCode() throws Exception { + mockMvc.perform(get("/accounts").accept(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + } + + @Test + void testGetAllAccounts() throws Exception { + Account account1 = create("Johnny", "Boy", "jboy@gmail.com"); + Account account2 = create("Okko", "Bay", "obay@gmail.com"); + accountDao.save(account1); + accountDao.save(account2); + + mockMvc.perform(get("/accounts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[*].email").value(hasItems("jboy@gmail.com", "obay@gmail.com"))); + } + + private Account create(String firstName, String lastName, String email) { + Account account = new Account(); + account.setFirstName(firstName); + account.setLastName(lastName); + account.setEmail(email); + return account; + } + + @Test + void testGetById() throws Exception { + Account account = create("Johnny", "Boy", "jboy@gmail.com"); + accountDao.save(account); + + mockMvc.perform(get(String.format("/accounts/%d", account.getId()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(account.getId())) + .andExpect(jsonPath("$.email").value("jboy@gmail.com")) + .andExpect(jsonPath("$.firstName").value("Johnny")) + .andExpect(jsonPath("$.lastName").value("Boy")); + } + + @Test + void testRemoveAccount() throws Exception { + Account account = create("Johnny", "Boy", "jboy@gmail.com"); + accountDao.save(account); + + mockMvc.perform(delete(String.format("/accounts/%d", account.getId()))) + .andExpect(status().isNoContent()); + } + + @Test + void testUpdateAccount() throws Exception { + Account account = create("Johnny", "Boy", "jboy@gmail.com"); + accountDao.save(account); + + mockMvc.perform(put(String.format("/accounts/%d", account.getId())).contentType(MediaType.APPLICATION_JSON) + .content(String.format("{\"id\":\"%d\", \"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"johnny.boy@gmail.com\"}", account.getId()))) + .andExpect(status().isNoContent()); + } + + +} diff --git a/3-0-spring-framework/pom.xml b/3-0-spring-framework/pom.xml index f018246..345a6bf 100644 --- a/3-0-spring-framework/pom.xml +++ b/3-0-spring-framework/pom.xml @@ -16,6 +16,7 @@ <module>3-0-0-hello-spring-framework</module> <module>3-0-1-hello-spring-mvc</module> <module>3-1-1-dispatcher-servlet-initializer</module> + <module>3-2-1-account-rest-api</module> </modules> </project> \ No newline at end of file From fb53dd50e62dc4d9a9569116a13dff21b41a2bbf Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Fri, 16 Apr 2021 17:38:42 +0300 Subject: [PATCH 09/26] GP-83 update pom and tests --- .../3-2-1-account-rest-api/pom.xml | 13 +- .../bobocode/AccountRestControllerTest.java | 119 +++++++++++------- .../com/bobocode/WebAppConfigurationTest.java | 76 +++++++++++ 3 files changed, 159 insertions(+), 49 deletions(-) create mode 100644 3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/WebAppConfigurationTest.java diff --git a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml index 1a7a50a..07c15b8 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml +++ b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml @@ -16,7 +16,7 @@ <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> - <version>5.0.7.RELEASE</version> + <version>5.2.12.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> @@ -24,6 +24,17 @@ <version>4.0.1</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <version>5.2.12.RELEASE</version> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-all</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> <dependency> <groupId>com.bobocode</groupId> <artifactId>spring-framework-exercises-util</artifactId> diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java index c2d7a87..18f0c46 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java @@ -2,11 +2,11 @@ import com.bobocode.config.RootConfig; import com.bobocode.config.WebConfig; +import com.bobocode.dao.AccountDao; import com.bobocode.dao.impl.InMemoryAccountDao; import com.bobocode.model.Account; import com.bobocode.web.controller.AccountRestController; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; @@ -16,19 +16,17 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.arrayWithSize; +import java.lang.reflect.Constructor; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringJUnitWebConfig(classes = {RootConfig.class, WebConfig.class}) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class AccountRestControllerTest { @Autowired private WebApplicationContext applicationContext; @@ -45,47 +43,38 @@ void setup() { } @Test - void testAccountRestControllerAnnotation() { + @Order(1) + @DisplayName("AccountRestController is marked as @RestController") + void accountRestControllerAnnotation() { RestController restController = AccountRestController.class.getAnnotation(RestController.class); - assertThat(restController, notNullValue()); + assertNotNull(restController); } @Test - void testAccountRestControllerRequestMapping() { + @Order(2) + @DisplayName("AccountRestController is annotated and mapped with @RequestMapping") + void accountRestControllerRequestMapping() { RequestMapping requestMapping = AccountRestController.class.getAnnotation(RequestMapping.class); - assertThat(requestMapping, notNullValue()); - assertThat(requestMapping.value(), arrayWithSize(1)); - assertThat(requestMapping.value(), arrayContaining("/accounts")); + assertNotNull(requestMapping); + assertThat(requestMapping.value().length).isEqualTo(1); + assertThat(requestMapping.value()).contains("/accounts"); } @Test - void testHttpStatusCodeOnCreate() throws Exception { - mockMvc.perform( - post("/accounts") - .contentType(MediaType.APPLICATION_JSON) - .content("{\"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"jboy@gmail.com\"}")) - .andExpect(status().isCreated()); - } - - @Test - void testCreateAccountReturnsAssignedId() throws Exception { - mockMvc.perform( - post("/accounts") - .contentType(MediaType.APPLICATION_JSON) - .content("{\"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"jboy@gmail.com\"}")) - .andExpect(jsonPath("$.id").value(1L)); - } + @Order(3) + @DisplayName("AccountDao is injected using constructor") + void accountDaoInjection() throws NoSuchMethodException { + Constructor<AccountRestController> constructor = AccountRestController.class.getConstructor(); - @Test - void testGetAccountsResponseStatusCode() throws Exception { - mockMvc.perform(get("/accounts").accept(MediaType.APPLICATION_JSON_UTF8)) - .andExpect(status().isOk()); + assertThat(constructor.getParameterTypes()).contains(AccountDao.class); } @Test - void testGetAllAccounts() throws Exception { + @Order(4) + @DisplayName("Getting all accounts is implemented") + void getAllAccounts() throws Exception { Account account1 = create("Johnny", "Boy", "jboy@gmail.com"); Account account2 = create("Okko", "Bay", "obay@gmail.com"); accountDao.save(account1); @@ -96,16 +85,18 @@ void testGetAllAccounts() throws Exception { .andExpect(jsonPath("$.[*].email").value(hasItems("jboy@gmail.com", "obay@gmail.com"))); } - private Account create(String firstName, String lastName, String email) { - Account account = new Account(); - account.setFirstName(firstName); - account.setLastName(lastName); - account.setEmail(email); - return account; + @Test + @Order(5) + @DisplayName("Getting all accounts response status is OK") + void getAccountsResponseStatusCode() throws Exception { + mockMvc.perform(get("/accounts").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); } @Test - void testGetById() throws Exception { + @Order(6) + @DisplayName("Getting account by Id with path variable is implemented") + void getById() throws Exception { Account account = create("Johnny", "Boy", "jboy@gmail.com"); accountDao.save(account); @@ -118,7 +109,39 @@ void testGetById() throws Exception { } @Test - void testRemoveAccount() throws Exception { + @Order(7) + @DisplayName("Creating account returns corresponding HTTP status") + void httpStatusCodeOnCreate() throws Exception { + mockMvc.perform( + post("/accounts") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"jboy@gmail.com\"}")) + .andExpect(status().isCreated()); + } + + @Test + @Order(8) + @DisplayName("Creating account returns assigned Id") + void createAccountReturnsAssignedId() throws Exception { + mockMvc.perform( + post("/accounts") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"jboy@gmail.com\"}")) + .andExpect(jsonPath("$.id").value(1L)); + } + + private Account create(String firstName, String lastName, String email) { + Account account = new Account(); + account.setFirstName(firstName); + account.setLastName(lastName); + account.setEmail(email); + return account; + } + + @Test + @Order(9) + @DisplayName("Removing account is implemented") + void removeAccount() throws Exception { Account account = create("Johnny", "Boy", "jboy@gmail.com"); accountDao.save(account); @@ -127,7 +150,9 @@ void testRemoveAccount() throws Exception { } @Test - void testUpdateAccount() throws Exception { + @Order(10) + @DisplayName("Updating account is implemented") + void updateAccount() throws Exception { Account account = create("Johnny", "Boy", "jboy@gmail.com"); accountDao.save(account); @@ -135,6 +160,4 @@ void testUpdateAccount() throws Exception { .content(String.format("{\"id\":\"%d\", \"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"johnny.boy@gmail.com\"}", account.getId()))) .andExpect(status().isNoContent()); } - - } diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/WebAppConfigurationTest.java b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/WebAppConfigurationTest.java new file mode 100644 index 0000000..6b5640d --- /dev/null +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/WebAppConfigurationTest.java @@ -0,0 +1,76 @@ +package com.bobocode; + +import com.bobocode.config.RootConfig; +import com.bobocode.config.WebConfig; +import com.bobocode.dao.impl.InMemoryAccountDao; +import org.junit.jupiter.api.*; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Controller; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class WebAppConfigurationTest { + + @Test + @Order(1) + @DisplayName("RootConfig class is configured properly") + void rootConfigClassIsConfigured() { + Configuration configuration = RootConfig.class.getAnnotation(Configuration.class); + ComponentScan componentScan = RootConfig.class.getAnnotation(ComponentScan.class); + String[] packages = componentScan.basePackages(); + if (packages.length == 0) { + packages = componentScan.value(); + } + + assertNotNull(configuration); + assertNotNull(componentScan); + assertThat(packages).contains("com.bobocode"); + + Filter[] filters = componentScan.excludeFilters(); + List<Class> filteredClasses = getFilteredClasses(filters); + + assertThat(filters.length).isEqualTo(2); + assertThat(filters[0].type()).isEqualTo(FilterType.ANNOTATION); + assertThat(filters[1].type()).isEqualTo(FilterType.ANNOTATION); + assertThat(filteredClasses.toArray()).containsExactlyInAnyOrder(EnableWebMvc.class, Controller.class); + } + + @Test + @Order(2) + @DisplayName("WebConfig class is configured properly") + void webConfigClassIsConfiguredProperly() { + Configuration configuration = WebConfig.class.getAnnotation(Configuration.class); + ComponentScan componentScan = WebConfig.class.getAnnotation(ComponentScan.class); + EnableWebMvc enableWebMvc = WebConfig.class.getAnnotation(EnableWebMvc.class); + + assertNotNull(configuration); + assertNotNull(componentScan); + assertThat(componentScan.basePackages()).contains("com.bobocode.web"); + assertNotNull(enableWebMvc); + } + + private List<Class> getFilteredClasses(Filter[] filters) { + return Stream.of(filters).flatMap(filter -> Stream.of(filter.value())).collect(Collectors.toList()); + } + + @Test + @Order(3) + @DisplayName("InMemoryAccountDao class is configured properly") + void inMemoryAccountDaoClassIsConfiguredProperly() { + Component component = InMemoryAccountDao.class.getAnnotation(Component.class); + + assertNotNull(component); + assertThat(component.value()).contains("accountDao"); + } +} From 156a0cce0d9c2baa8f621362da1473683d999e6e Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Sat, 17 Apr 2021 13:38:29 +0300 Subject: [PATCH 10/26] GP-83 update pom and dao --- .../3-2-1-account-rest-api/README.MD | 10 ++++----- .../3-2-1-account-rest-api/pom.xml | 14 +++++++++++-- .../java/com/bobocode/config/WebConfig.java | 1 - .../bobocode/dao/impl/InMemoryAccountDao.java | 1 + .../bobocode/AccountRestControllerTest.java | 21 ++++++++++--------- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/3-0-spring-framework/3-2-1-account-rest-api/README.MD b/3-0-spring-framework/3-2-1-account-rest-api/README.MD index 7c2fc40..1f7a720 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/README.MD +++ b/3-0-spring-framework/3-2-1-account-rest-api/README.MD @@ -1,4 +1,4 @@ -# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Account REST API exercise :muscle: +# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Account REST API exercise 💪 Improve your *Spring MVC* configuration and rest mapping skills ### Task This webapp provides a **simple REST API for `Account`**. The data is stored using in-memory fake DAO. Your job is to @@ -8,14 +8,14 @@ This webapp provides a **simple REST API for `Account`**. The data is stored usi To verify your configuration, run `AccountRestControllerTest.java` :white_check_mark: -### Pre-conditions :heavy_exclamation_mark: +### Pre-conditions ❗ You're supposed to be familiar with *Spring MVC* -### How to start :question: +### How to start ❓ * Just clone the repository and start implementing the **todo** section, verify your changes by running tests * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation -### Related materials :information_source: - * [Spring REST basics tutorial](https://github.com/bobocode-projects/spring-framework-tutorial/tree/master/rest-basics)<img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=20/> +### Related materials ℹ + * todo diff --git a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml index 07c15b8..3b9bb30 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml +++ b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml @@ -43,12 +43,22 @@ <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> - <version>2.9.7</version> + <version>2.12.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> - <version>2.9.7</version> + <version>2.12.2</version> + </dependency> + <dependency> + <groupId>com.jayway.jsonpath</groupId> + <artifactId>json-path</artifactId> + <version>2.3.0</version> + </dependency> + <dependency> + <groupId>com.jayway.jsonpath</groupId> + <artifactId>json-path-assert</artifactId> + <version>2.3.0</version> </dependency> </dependencies> diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java index 6284d6a..1197302 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java @@ -8,5 +8,4 @@ * todo: 3. Enable component scanning for package "web" using annotation value */ public class WebConfig { - } diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java index af5f026..5620d5b 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java @@ -48,5 +48,6 @@ public void remove(Account account) { public void clear() { accountMap.clear(); + idSequence = 1L; } } diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java index 18f0c46..098c3a0 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java @@ -25,8 +25,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@SpringJUnitWebConfig(classes = {RootConfig.class, WebConfig.class}) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@SpringJUnitWebConfig(classes = {RootConfig.class, WebConfig.class}) class AccountRestControllerTest { @Autowired private WebApplicationContext applicationContext; @@ -65,8 +65,8 @@ void accountRestControllerRequestMapping() { @Test @Order(3) @DisplayName("AccountDao is injected using constructor") - void accountDaoInjection() throws NoSuchMethodException { - Constructor<AccountRestController> constructor = AccountRestController.class.getConstructor(); + void accountDaoInjection() { + Constructor<?> constructor = AccountRestController.class.getConstructors()[0]; assertThat(constructor.getParameterTypes()).contains(AccountDao.class); } @@ -140,24 +140,25 @@ private Account create(String firstName, String lastName, String email) { @Test @Order(9) - @DisplayName("Removing account is implemented") - void removeAccount() throws Exception { + @DisplayName("Updating account is implemented") + void updateAccount() throws Exception { Account account = create("Johnny", "Boy", "jboy@gmail.com"); accountDao.save(account); - mockMvc.perform(delete(String.format("/accounts/%d", account.getId()))) + mockMvc.perform(put(String.format("/accounts/%d", account.getId())).contentType(MediaType.APPLICATION_JSON) + .content(String.format("{\"id\":\"%d\", \"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"johnny.boy@gmail.com\"}", account.getId()))) .andExpect(status().isNoContent()); } @Test @Order(10) - @DisplayName("Updating account is implemented") - void updateAccount() throws Exception { + @DisplayName("Removing account is implemented") + void removeAccount() throws Exception { Account account = create("Johnny", "Boy", "jboy@gmail.com"); accountDao.save(account); - mockMvc.perform(put(String.format("/accounts/%d", account.getId())).contentType(MediaType.APPLICATION_JSON) - .content(String.format("{\"id\":\"%d\", \"firstName\":\"Johnny\", \"lastName\":\"Boy\", \"email\":\"johnny.boy@gmail.com\"}", account.getId()))) + mockMvc.perform(delete(String.format("/accounts/%d", account.getId()))) .andExpect(status().isNoContent()); } } + From cb7970e7f340b53d03b39ab8fef8493a095d8b37 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Wed, 21 Apr 2021 12:32:59 +0300 Subject: [PATCH 11/26] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 61e1577..c9cd840 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Welcome to the Java Web Course Build strong networking and web development skills that you will need for real-world Enterprise Java development + +## Why +Most people don’t know how to learn Enterprise Java efficiently. So we created an **open-source education system** +that helps them to **master strong skills**, learn **world best practices** and build a **successful career**. 🚀 + +At Bobocode we have extensive experience in both building Enterprise Java applications and organizing efficient learning. +Therefore, this course covers what you need in the most efficient way. We believe that +**the key to efficient learning is practice**. 💪 And as a software engineer, you should **spend as much time as you can in the IDE writing code**. +At the end of the day, this is the only place where you build software... 💻 \ No newline at end of file From cd1e12cb0228740e97953a8c9fccf3aab3692674 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Tue, 27 Apr 2021 12:47:48 +0300 Subject: [PATCH 12/26] GP-67 update exercise while preparing overview * change the order of the tests * update javadocs * simplify impl * add todo --- .../bobocode/config/ApplicationConfig.java | 28 ++++---------- .../java/com/bobocode/dao/AccountDao.java | 2 +- .../java/com/bobocode/dao/FakeAccountDao.java | 12 +++--- .../com/bobocode/service/AccountService.java | 19 +++++----- .../com/bobocode/ApplicationConfigTest.java | 38 ++++++++++++------- .../com/bobocode/ApplicationContextTest.java | 21 ++-------- .../java/com/bobocode/TestDataGenerator.java | 29 ++++++++------ 7 files changed, 66 insertions(+), 83 deletions(-) diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java index 91df5b3..f7ebf38 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/config/ApplicationConfig.java @@ -1,30 +1,16 @@ package com.bobocode.config; import com.bobocode.TestDataGenerator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportResource; -import org.springframework.stereotype.Component; /** - * This class specify application context configuration. Basically, it's all about which instances of which classes - * should be created and registered in the context. An instance that is registered in the context is called 'bean'. + * This class specifies application context configuration. It tells Spring to scan "dao" and "service" packages in order + * to find and create instances for {@link com.bobocode.dao.FakeAccountDao} and + * {@link com.bobocode.service.AccountService}. * <p> - * To tell the container, which bean should be created, you could either specify packages to scan using @{@link ComponentScan}, - * or declare your own beans using @{@link Bean}. When you use @{@link ComponentScan} the container will discover - * specified package and its sub-folders, to find all classes marked @{@link Component}. - * <p> - * If you want to import other configs from Java class or XML file, you could use @{@link Import} - * and @{@link ImportResource} accordingly - * <p> - * todo 1: make this class a Spring configuration class - * todo 2: enable component scanning for dao and service packages - * todo 3: provide explicit configuration for a bean of type {@link TestDataGenerator} with name "dataGenerator" in this class. - * hint: use method creation approach with @Bean annotation; - * todo 4: Don't specify bean name "dataGenerator" explicitly + * It also explicitly configures a bean of {@link TestDataGenerator} called "dataGenerator". This beans will be injected + * into {@link com.bobocode.dao.FakeAccountDao} in order to generate some fake accounts. */ - public class ApplicationConfig { - + // todo: configure application context according to javadoc by following tests in ApplicationConfigTest + // todo: verify final implementation by running ApplicationContextTest } diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/AccountDao.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/AccountDao.java index cbc888b..dcf08ff 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/AccountDao.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/AccountDao.java @@ -5,7 +5,7 @@ import java.util.List; /** - * Defines an API for {@link Account} data access object (DAO). + * Defines a simple API for {@link Account} data access object (DAO). */ public interface AccountDao { List<Account> findAll(); diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java index 9543845..bae7264 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/dao/FakeAccountDao.java @@ -2,8 +2,6 @@ import com.bobocode.TestDataGenerator; import com.bobocode.model.Account; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Stream; @@ -11,13 +9,13 @@ import static java.util.stream.Collectors.toList; /** - * This class should be marked with @{@link Component}, thus Spring container will create an instance - * of {@link FakeAccountDao} class, and will register it the context. + * {@link FakeAccountDao} implements {@link AccountDao} using fake data. Instead of storing and fetching accounts from + * some storage, it just generates fake records using {@link TestDataGenerator}. * <p> - * todo: configure this class as Spring component with bean name "accountDao" - * todo: use explicit (with {@link Autowired} annotation) constructor-based dependency injection for specific bean + * An instance of this class should be created and added to the application context, so it is marked as Spring component. + * Its bean is called "accountDao". And it uses constructor with explicit autowired annotation in order to inject + * {@link TestDataGenerator} instance. */ - public class FakeAccountDao implements AccountDao { private List<Account> accounts; diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java index 3bc0b66..a46dfd2 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/service/AccountService.java @@ -3,16 +3,15 @@ import com.bobocode.dao.AccountDao; import com.bobocode.model.Account; -import java.util.Comparator; -import java.util.List; +import static java.util.Comparator.comparing; /** - * Provides service API for {@link Account}. + * {@link AccountService} is a very simple service that allows to find the richest person based on data provided to + * {@link AccountDao}. * <p> - * todo: configure {@link AccountService} bean implicitly using special annotation for service classes - * todo: use implicit constructor-based dependency injection (don't use {@link org.springframework.beans.factory.annotation.Autowired}) + * Since it's a service that should be added to the application context, it is marked as Spring service. It order to get + * {@link AccountDao} instances, it uses implicit constructor-based injection. */ - public class AccountService { private final AccountDao accountDao; @@ -21,9 +20,9 @@ public AccountService(AccountDao accountDao) { } public Account findRichestAccount() { - List<Account> accounts = accountDao.findAll(); - return accounts.stream() - .max(Comparator.comparing(Account::getBalance)) - .get(); + return accountDao.findAll() + .stream() + .max(comparing(Account::getBalance)) + .orElseThrow(); } } diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationConfigTest.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationConfigTest.java index 2c7a211..21be4b1 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationConfigTest.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationConfigTest.java @@ -5,6 +5,7 @@ import com.bobocode.dao.FakeAccountDao; import com.bobocode.service.AccountService; import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -18,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class ApplicationConfigTest { +class ApplicationConfigTest { @Test @Order(1) @@ -52,6 +53,24 @@ void componentScanPackagesAreSpecified() { @Test @Order(4) + @DisplayName("FakeAccountDao is configured as @Component") + void fakeAccountDaoIsConfiguredAsComponent() { + Component component = FakeAccountDao.class.getAnnotation(Component.class); + + assertNotNull(component); + } + + @Test + @Order(5) + @DisplayName("AccountDao constructor is marked with @Autowired") + void accountDaoConstructorIsMarkedWithAutowired() throws NoSuchMethodException { + Autowired autowired = FakeAccountDao.class.getConstructor(TestDataGenerator.class).getAnnotation(Autowired.class); + + assertNotNull(autowired); + } + + @Test + @Order(6) @DisplayName("DataGenerator bean is configured in method marked with @Bean") void dataGeneratorBeanIsConfiguredExplicitly() { Method[] methods = ApplicationConfig.class.getMethods(); @@ -61,7 +80,7 @@ void dataGeneratorBeanIsConfiguredExplicitly() { } @Test - @Order(5) + @Order(7) @DisplayName("DataGenerator bean name is not specified explicitly") void dataGeneratorBeanNameIsNotSpecifiedExplicitly() { Method[] methods = ApplicationConfig.class.getMethods(); @@ -73,16 +92,7 @@ void dataGeneratorBeanNameIsNotSpecifiedExplicitly() { } @Test - @Order(6) - @DisplayName("FakeAccountDao is configured as @Component") - void fakeAccountDaoIsConfiguredAsComponent() { - Component component = FakeAccountDao.class.getAnnotation(Component.class); - - assertNotNull(component); - } - - @Test - @Order(7) + @Order(8) @DisplayName("AccountService is configured as @Service") void accountServiceIsConfiguredAsService() { Service service = AccountService.class.getAnnotation(Service.class); @@ -91,7 +101,7 @@ void accountServiceIsConfiguredAsService() { } @Test - @Order(8) + @Order(9) @DisplayName("AccountService bean name is not specified explicitly") void accountServiceBeanNameIsNotSpecifiedExplicitly() { Service service = AccountService.class.getAnnotation(Service.class); @@ -100,7 +110,7 @@ void accountServiceBeanNameIsNotSpecifiedExplicitly() { } @Test - @Order(9) + @Order(10) @DisplayName("AccountService doesn't use @Autowired") void accountServiceDoesNotUseAutowired() throws NoSuchMethodException { Annotation[] annotations = AccountService.class.getConstructor(AccountDao.class).getDeclaredAnnotations(); diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationContextTest.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationContextTest.java index 6955a14..e30c3a7 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationContextTest.java +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/test/java/com/bobocode/ApplicationContextTest.java @@ -26,12 +26,6 @@ static class TestConfig { @Autowired private ApplicationContext applicationContext; - @Autowired - private AccountService accountService; - - @Autowired - private AccountDao accountDao; - @Test @Order(1) @DisplayName("DataGenerator has only one bean") @@ -43,7 +37,7 @@ void dataGeneratorHasOnlyOneBean() { @Test @Order(2) - @DisplayName("DataGenerator bean has proper name") + @DisplayName("DataGenerator bean is called \"dataGenerator\"") void testDataGeneratorBeanName() { Map<String, TestDataGenerator> dataGeneratorBeanMap = applicationContext.getBeansOfType(TestDataGenerator.class); @@ -61,22 +55,13 @@ void accountDaoHasOnlyOneBean() { @Test @Order(4) - @DisplayName("AccountDao bean has proper name") + @DisplayName("AccountDao bean is called \"accountDao\"") void accountDaoBeanName() { Map<String, AccountDao> accountDaoBeanMap = applicationContext.getBeansOfType(AccountDao.class); assertThat(accountDaoBeanMap.keySet()).contains("accountDao"); } - @Test - @Order(5) - @DisplayName("AccountDao constructor is marked with @Autowired") - void accountDaoConstructorIsMarkedWithAutowired() throws NoSuchMethodException { - Autowired autowired = FakeAccountDao.class.getConstructor(TestDataGenerator.class).getAnnotation(Autowired.class); - - assertNotNull(autowired); - } - @Test @Order(6) @DisplayName("AccountService has only one bean") @@ -88,7 +73,7 @@ void accountServiceHasOnlyOneBean() { @Test @Order(7) - @DisplayName("AccountService has proper name") + @DisplayName("AccountService bean is called \"accountService\"") void accountServiceBeanName() { Map<String, AccountService> accountServiceMap = applicationContext.getBeansOfType(AccountService.class); diff --git a/java-web-course-util/spring-framework-exercises-util/src/main/java/com/bobocode/TestDataGenerator.java b/java-web-course-util/spring-framework-exercises-util/src/main/java/com/bobocode/TestDataGenerator.java index 50cea04..bee4930 100644 --- a/java-web-course-util/spring-framework-exercises-util/src/main/java/com/bobocode/TestDataGenerator.java +++ b/java-web-course-util/spring-framework-exercises-util/src/main/java/com/bobocode/TestDataGenerator.java @@ -8,26 +8,31 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; public class TestDataGenerator { public Account generateAccount() { Fairy fairy = Fairy.create(); Person person = fairy.person(); - Random random = new Random(); - Account fakeAccount = new Account(); - fakeAccount.setFirstName(person.getFirstName()); - fakeAccount.setLastName(person.getLastName()); - fakeAccount.setEmail(person.getEmail()); - fakeAccount.setBirthday(LocalDate.of( + Account account = convertToAccount(person); + BigDecimal balance = BigDecimal.valueOf(ThreadLocalRandom.current().nextInt(200_000)); + account.setBalance(balance); + account.setCreationTime(LocalDateTime.now()); + + return account; + } + + private Account convertToAccount(Person person) { + Account account = new Account(); + account.setFirstName(person.getFirstName()); + account.setLastName(person.getLastName()); + account.setEmail(person.getEmail()); + account.setBirthday(LocalDate.of( person.getDateOfBirth().getYear(), person.getDateOfBirth().getMonthOfYear(), person.getDateOfBirth().getDayOfMonth())); - fakeAccount.setGender(Gender.valueOf(person.getSex().name())); - fakeAccount.setBalance(BigDecimal.valueOf(random.nextInt(200_000))); - fakeAccount.setCreationTime(LocalDateTime.now()); - - return fakeAccount; + account.setGender(Gender.valueOf(person.getSex().name())); + return account; } } From 2b4e3092dd0753bac84931b4e813c683a47b24b5 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Thu, 29 Apr 2021 09:48:05 +0300 Subject: [PATCH 13/26] GP-67 add AccountServiceDemoApp in order to show how ApplicationContext is created --- .../com/bobocode/AccountServiceDemoApp.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/AccountServiceDemoApp.java diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/AccountServiceDemoApp.java b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/AccountServiceDemoApp.java new file mode 100644 index 0000000..2d70f8d --- /dev/null +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/src/main/java/com/bobocode/AccountServiceDemoApp.java @@ -0,0 +1,34 @@ +package com.bobocode; + +import com.bobocode.config.ApplicationConfig; +import com.bobocode.model.Account; +import com.bobocode.service.AccountService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * This is a demo application. It uses Spring to create an application context based on the configuration you provided + * when implementing this exercise. When everything is done and application context created successfully, + * it will get a "bean" (an object) of a {@link AccountService} from the context. That bean is used to find the richest + * account and show that everything is working as expected. + */ +public class AccountServiceDemoApp { + public static void main(String[] args) { + ApplicationContext context = initializeSpringContext(); + AccountService accountService = context.getBean(AccountService.class); + Account account = accountService.findRichestAccount(); + System.out.println("The riches person is " + account.getFirstName() + " " + account.getLastName()); + } + + /** + * In real applications, you will never initialize Spring context manually. We do this here for the sake of demo, + * so you can see that it's an object that implements {@link ApplicationContext} interface. As you can see, + * {@link ApplicationContext} has multiple implementations. In this exercise, we use the most popular way of + * configuring context – Java config with annotations. + * + * @return an instance of application context + */ + private static ApplicationContext initializeSpringContext() { + return new AnnotationConfigApplicationContext(ApplicationConfig.class); + } +} From 09f42d9710b7d2171d1284b1742667423ea49109 Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Wed, 5 May 2021 14:47:09 +0300 Subject: [PATCH 14/26] GP-83 update pom dependencies --- .../3-0-0-hello-spring-framework/pom.xml | 16 ----------- .../pom.xml | 5 ---- .../3-2-1-account-rest-api/pom.xml | 28 ++----------------- 3-0-spring-framework/pom.xml | 23 +++++++++++++++ pom.xml | 6 ++++ 5 files changed, 31 insertions(+), 47 deletions(-) diff --git a/3-0-spring-framework/3-0-0-hello-spring-framework/pom.xml b/3-0-spring-framework/3-0-0-hello-spring-framework/pom.xml index 0dd36ae..e168059 100644 --- a/3-0-spring-framework/3-0-0-hello-spring-framework/pom.xml +++ b/3-0-spring-framework/3-0-0-hello-spring-framework/pom.xml @@ -17,27 +17,11 @@ <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-test</artifactId> - <version>5.2.12.RELEASE</version> - </dependency> <dependency> <groupId>com.bobocode</groupId> <artifactId>spring-framework-exercises-util</artifactId> <version>1.0-SNAPSHOT</version> </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-all</artifactId> - <version>1.3</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-simple</artifactId> - <version>1.7.24</version> - </dependency> <dependency> <groupId>com.bobocode</groupId> <artifactId>spring-framework-exercises-model</artifactId> diff --git a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/pom.xml b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/pom.xml index 277065d..be4c329 100644 --- a/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/pom.xml +++ b/3-0-spring-framework/3-1-1-dispatcher-servlet-initializer/pom.xml @@ -13,11 +13,6 @@ <packaging>war</packaging> <dependencies> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-webmvc</artifactId> - <version>5.2.12.RELEASE</version> - </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> diff --git a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml index 3b9bb30..7ec0e00 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/pom.xml +++ b/3-0-spring-framework/3-2-1-account-rest-api/pom.xml @@ -13,52 +13,28 @@ <packaging>war</packaging> <dependencies> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-webmvc</artifactId> - <version>5.2.12.RELEASE</version> - </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-test</artifactId> - <version>5.2.12.RELEASE</version> - </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-all</artifactId> - <version>1.3</version> - <scope>test</scope> - </dependency> <dependency> <groupId>com.bobocode</groupId> <artifactId>spring-framework-exercises-util</artifactId> <version>1.0-SNAPSHOT</version> </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-core</artifactId> - <version>2.12.2</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <version>2.12.2</version> - </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.3.0</version> + <scope>test</scope> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path-assert</artifactId> <version>2.3.0</version> + <scope>test</scope> </dependency> </dependencies> diff --git a/3-0-spring-framework/pom.xml b/3-0-spring-framework/pom.xml index 345a6bf..c9a2d9c 100644 --- a/3-0-spring-framework/pom.xml +++ b/3-0-spring-framework/pom.xml @@ -19,4 +19,27 @@ <module>3-2-1-account-rest-api</module> </modules> + <dependencies> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>5.2.12.RELEASE</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <version>5.2.12.RELEASE</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>2.12.2</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.12.2</version> + </dependency> + </dependencies> + </project> \ No newline at end of file diff --git a/pom.xml b/pom.xml index 98218f0..fada7c5 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,12 @@ <version>3.18.1</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-all</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> From cef1e03f3305d556c9e9d6354acf0433057d3a61 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Wed, 12 May 2021 12:16:41 +0300 Subject: [PATCH 15/26] GP-65 complete exercise hello-network-socket --- .../main/java/com/bobocode/net/client/ClientUtil.java | 8 +++++--- .../main/java/com/bobocode/net/server/ServerUtil.java | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/client/ClientUtil.java b/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/client/ClientUtil.java index e2cdac5..a2cfbe5 100644 --- a/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/client/ClientUtil.java +++ b/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/client/ClientUtil.java @@ -1,6 +1,5 @@ package com.bobocode.net.client; -import com.bobocode.util.ExerciseNotCompletedException; import lombok.SneakyThrows; import java.io.*; @@ -24,7 +23,7 @@ private ClientUtil() { */ @SneakyThrows public static Socket openSocket(String host, int port) { - throw new ExerciseNotCompletedException(); // todo: implement according to javadoc and verify by ClientUtilTest + return new Socket(host, port); } /** @@ -62,6 +61,9 @@ public static String readMessage(BufferedReader reader) { */ @SneakyThrows public static void writeToSocket(String message, Socket socket) { - throw new ExerciseNotCompletedException(); // todo: implement according to javadoc and verify by ClientUtilTest + OutputStream outputStream = socket.getOutputStream(); + Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream)); + writer.write(message); + writer.flush(); } } diff --git a/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/server/ServerUtil.java b/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/server/ServerUtil.java index 3778579..1797c5a 100644 --- a/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/server/ServerUtil.java +++ b/1-0-networking-and-http/1-0-0-hello-network-socket/src/main/java/com/bobocode/net/server/ServerUtil.java @@ -1,6 +1,5 @@ package com.bobocode.net.server; -import com.bobocode.util.ExerciseNotCompletedException; import lombok.SneakyThrows; import java.io.BufferedReader; @@ -41,7 +40,7 @@ public static String getLocalHost() { */ @SneakyThrows public static ServerSocket createServerSocket(int port) { - throw new ExerciseNotCompletedException(); // todo: implement according to javadoc and verify by ServerUtilTest + return new ServerSocket(port); } /** @@ -52,7 +51,7 @@ public static ServerSocket createServerSocket(int port) { */ @SneakyThrows public static Socket acceptClientSocket(ServerSocket serverSocket) { - throw new ExerciseNotCompletedException(); // todo: implement according to javadoc and verify by ServerUtilTest + return serverSocket.accept(); } /** @@ -66,7 +65,9 @@ public static Socket acceptClientSocket(ServerSocket serverSocket) { */ @SneakyThrows public static String readMessageFromSocket(Socket socket) { - throw new ExerciseNotCompletedException(); // todo: implement according to javadoc and verify by ServerUtilTest + InputStream inputStream = socket.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + return reader.readLine(); } /** From 53c93c58ad86917f3615e7a36c7e2fc746a5efeb Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Tue, 18 May 2021 12:40:23 +0300 Subject: [PATCH 16/26] GP-83 update dependencies --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index ac49860..04d742e 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ <artifactId>slf4j-simple</artifactId> <version>1.7.24</version> </dependency> - <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> @@ -60,11 +59,6 @@ <version>1.3</version> <scope>test</scope> </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-simple</artifactId> - <version>1.7.24</version> - </dependency> <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> From badda385cfb7cfe188c10f2055823a046a50ace0 Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Tue, 18 May 2021 12:54:27 +0300 Subject: [PATCH 17/26] GP-83 update tests --- .../src/test/java/com/bobocode/AccountRestControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java index 098c3a0..78ee089 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/test/java/com/bobocode/AccountRestControllerTest.java @@ -53,7 +53,7 @@ void accountRestControllerAnnotation() { @Test @Order(2) - @DisplayName("AccountRestController is annotated and mapped with @RequestMapping") + @DisplayName("AccountRestController is specified in @RequestMapping") void accountRestControllerRequestMapping() { RequestMapping requestMapping = AccountRestController.class.getAnnotation(RequestMapping.class); @@ -110,7 +110,7 @@ void getById() throws Exception { @Test @Order(7) - @DisplayName("Creating account returns corresponding HTTP status") + @DisplayName("Creating account returns corresponding HTTP status - 201") void httpStatusCodeOnCreate() throws Exception { mockMvc.perform( post("/accounts") From 78bff110807928b7c010680e49f468412cc27e70 Mon Sep 17 00:00:00 2001 From: Serhii <mathodcoast@gmail.com> Date: Wed, 19 May 2021 12:28:51 +0300 Subject: [PATCH 18/26] GP-83 update completed --- .../java/com/bobocode/config/RootConfig.java | 4 ++ .../java/com/bobocode/config/WebConfig.java | 8 ++++ .../bobocode/dao/impl/InMemoryAccountDao.java | 2 + .../web/controller/AccountRestController.java | 44 +++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java index c5d1a6d..84d2933 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/RootConfig.java @@ -1,5 +1,7 @@ package com.bobocode.config; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -10,5 +12,7 @@ * todo: 2. Enable component scanning for all packages in "com.bobocode" using annotation property "basePackages" * todo: 3. Exclude web related config and beans (ignore @{@link Controller}, ignore {@link EnableWebMvc}) */ +@Configuration +@ComponentScan(basePackages = "com.bobocode", excludeFilters = {@ComponentScan.Filter(EnableWebMvc.class), @ComponentScan.Filter(Controller.class)}) public class RootConfig { } diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java index 1197302..8aa34fa 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/config/WebConfig.java @@ -1,5 +1,9 @@ package com.bobocode.config; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + /** * This class provides web (servlet) related configuration. * <p> @@ -7,5 +11,9 @@ * todo: 2. Enable web mvc using annotation * todo: 3. Enable component scanning for package "web" using annotation value */ +@EnableWebMvc +@Configuration +@ComponentScan(basePackages = "com.bobocode.web") public class WebConfig { + } diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java index 5620d5b..c43089e 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java @@ -3,6 +3,7 @@ import com.bobocode.dao.AccountDao; import com.bobocode.exception.EntityNotFountException; import com.bobocode.model.Account; +import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.HashMap; @@ -14,6 +15,7 @@ * <p> * todo: 1. Configure a component with name "accountDao" */ +@Component("accountDao") public class InMemoryAccountDao implements AccountDao { private Map<Long, Account> accountMap = new HashMap<>(); private long idSequence = 1L; diff --git a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java index 84fb818..afcc2fc 100644 --- a/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java +++ b/3-0-spring-framework/3-2-1-account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java @@ -1,6 +1,12 @@ package com.bobocode.web.controller; import com.bobocode.dao.AccountDao; +import com.bobocode.model.Account; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Objects; /** * <p> @@ -16,6 +22,44 @@ * todo: 7. Implement method that handles DELETE request with id as path variable removes an account by id * todo: Configure HTTP response status code 204 - NO CONTENT */ +@RestController +@RequestMapping("/accounts") public class AccountRestController { + private final AccountDao accountDao; + + public AccountRestController(AccountDao accountDao) { + this.accountDao = accountDao; + } + + @GetMapping + public List<Account> getAll() { + return accountDao.findAll(); + } + + @GetMapping("/{id}") + public Account getOne(@PathVariable long id) { + return accountDao.findById(id); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Account create(@RequestBody Account account) { + return accountDao.save(account); + } + + @PutMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@PathVariable long id, @RequestBody Account account) { + if (!Objects.equals(id, account.getId())) { + throw new IllegalStateException("Id parameter does not match account body value"); + } + accountDao.save(account); + } + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void remove(@PathVariable long id) { + Account account = accountDao.findById(id); + accountDao.remove(account); + } } From 5ba8d30218088ffbe7decb51e6bd0305c1fe480c Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Fri, 21 May 2021 10:47:12 +0300 Subject: [PATCH 19/26] =?UTF-8?q?GP-83=20=E2=80=93=20remove=20duplicate=20?= =?UTF-8?q?dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index 85ec915..9c6bf7a 100644 --- a/pom.xml +++ b/pom.xml @@ -70,11 +70,6 @@ <version>1.3</version> <scope>test</scope> </dependency> - <dependency> - <groupId>org.reflections</groupId> - <artifactId>reflections</artifactId> - <version>0.9.12</version> - </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> From 4eaa0cffdbc3e9477384855dc7cc15faca2ab831 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Fri, 21 May 2021 10:58:19 +0300 Subject: [PATCH 20/26] GP-83 upgrade Spring versions --- 3-0-spring-framework/3-0-1-hello-spring-mvc/pom.xml | 6 +++--- 3-0-spring-framework/pom.xml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/3-0-spring-framework/3-0-1-hello-spring-mvc/pom.xml b/3-0-spring-framework/3-0-1-hello-spring-mvc/pom.xml index 650a31c..7f52952 100644 --- a/3-0-spring-framework/3-0-1-hello-spring-mvc/pom.xml +++ b/3-0-spring-framework/3-0-1-hello-spring-mvc/pom.xml @@ -15,20 +15,20 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> - <version>2.4.3</version> + <version>2.4.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> - <version>2.4.3</version> + <version>2.4.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> - <version>2.4.3</version> + <version>2.4.5</version> </dependency> </dependencies> diff --git a/3-0-spring-framework/pom.xml b/3-0-spring-framework/pom.xml index c9a2d9c..8130a12 100644 --- a/3-0-spring-framework/pom.xml +++ b/3-0-spring-framework/pom.xml @@ -23,12 +23,12 @@ <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> - <version>5.2.12.RELEASE</version> + <version>5.3.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> - <version>5.2.12.RELEASE</version> + <version>5.3.6</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> From fd000a7ad59830dafd0d90632350304f74f363a0 Mon Sep 17 00:00:00 2001 From: Ivan Virchenko <erneman@rambler.ru> Date: Mon, 10 May 2021 17:23:47 +0300 Subject: [PATCH 21/26] implementing exercise in completed branch --- .../java/com/bobocode/config/ResolverConfig.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/config/ResolverConfig.java b/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/config/ResolverConfig.java index 40bb352..39ef7ef 100644 --- a/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/config/ResolverConfig.java +++ b/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/config/ResolverConfig.java @@ -1,4 +1,17 @@ package com.bobocode.config; -public class ResolverConfig { +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.bobocode") +public class ResolverConfig implements WebMvcConfigurer { + @Override + public void configureViewResolvers(ViewResolverRegistry registry) { + registry.jsp("/WEB-INF/views/", ".jsp"); + } } From dfb70009adac515f84e25507369b0b10a47460d6 Mon Sep 17 00:00:00 2001 From: Ivan Virchenko <erneman@rambler.ru> Date: Sat, 15 May 2021 23:30:59 +0300 Subject: [PATCH 22/26] G-55 ViewResolver exercize Completed commit --- .../com/bobocode/controller/HelloJspController.java | 10 ++++++++++ .../main/webapp/WEB-INF/views/{index.jsp => hello.jsp} | 0 .../test/java/com/bobocode/HelloJspControllerTest.java | 7 ++----- 3 files changed, 12 insertions(+), 5 deletions(-) rename 3-0-spring-framework/3-0-2-view-resolver/src/main/webapp/WEB-INF/views/{index.jsp => hello.jsp} (100%) diff --git a/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/controller/HelloJspController.java b/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/controller/HelloJspController.java index 50cdd00..4f3f157 100644 --- a/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/controller/HelloJspController.java +++ b/3-0-spring-framework/3-0-2-view-resolver/src/main/java/com/bobocode/controller/HelloJspController.java @@ -1,4 +1,14 @@ package com.bobocode.controller; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping public class HelloJspController { + @GetMapping + public String hello() { + return "hello"; + } } diff --git a/3-0-spring-framework/3-0-2-view-resolver/src/main/webapp/WEB-INF/views/index.jsp b/3-0-spring-framework/3-0-2-view-resolver/src/main/webapp/WEB-INF/views/hello.jsp similarity index 100% rename from 3-0-spring-framework/3-0-2-view-resolver/src/main/webapp/WEB-INF/views/index.jsp rename to 3-0-spring-framework/3-0-2-view-resolver/src/main/webapp/WEB-INF/views/hello.jsp diff --git a/3-0-spring-framework/3-0-2-view-resolver/src/test/java/com/bobocode/HelloJspControllerTest.java b/3-0-spring-framework/3-0-2-view-resolver/src/test/java/com/bobocode/HelloJspControllerTest.java index e190780..425dc81 100644 --- a/3-0-spring-framework/3-0-2-view-resolver/src/test/java/com/bobocode/HelloJspControllerTest.java +++ b/3-0-spring-framework/3-0-2-view-resolver/src/test/java/com/bobocode/HelloJspControllerTest.java @@ -4,15 +4,12 @@ import com.bobocode.controller.HelloJspController; import lombok.SneakyThrows; import org.junit.jupiter.api.*; -import org.mockito.internal.configuration.ClassPathLoader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import org.springframework.validation.support.BindingAwareModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -20,9 +17,9 @@ import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @WebMvcTest @ContextConfiguration(classes = ResolverConfig.class) From 17cd8cd7cfdc672084288bc63b9df29194a54239 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Thu, 30 Sep 2021 15:51:27 +0300 Subject: [PATCH 23/26] GP-137 update imports in DateServlet.java --- .../src/main/java/com/bobocode/servlet/DateServlet.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/2-0-servlet-api/2-0-1-hello-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java b/2-0-servlet-api/2-0-1-hello-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java index 92c2435..413f25d 100644 --- a/2-0-servlet-api/2-0-1-hello-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java +++ b/2-0-servlet-api/2-0-1-hello-servlet-api/src/main/java/com/bobocode/servlet/DateServlet.java @@ -1,9 +1,10 @@ package com.bobocode.servlet; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDate; From ef176caab28afc91bab1f2ffc071ac4a319054b7 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Fri, 1 Oct 2021 19:23:46 +0300 Subject: [PATCH 24/26] Update lesson-demo --- lesson-demo/pom.xml | 18 ++++++++++++------ .../java/com/bobocode/demo/DemoWebApp.java | 4 +++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lesson-demo/pom.xml b/lesson-demo/pom.xml index b7ea982..d45db22 100644 --- a/lesson-demo/pom.xml +++ b/lesson-demo/pom.xml @@ -18,14 +18,20 @@ <dependencies> <dependency> - <groupId>com.bobocode</groupId> - <artifactId>java-web-course-util</artifactId> - <version>1.0-SNAPSHOT</version> + <groupId>jakarta.servlet</groupId> + <artifactId>jakarta.servlet-api</artifactId> + <version>5.0.0</version> + <scope>provided</scope> </dependency> <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-web</artifactId> - <version>2.5.5</version> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>5.3.10</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.12.5</version> </dependency> </dependencies> diff --git a/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java b/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java index 6e7dc5e..3cc15c9 100644 --- a/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java +++ b/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java @@ -1,7 +1,9 @@ package com.bobocode.demo; +import lombok.SneakyThrows; + public class DemoWebApp { - + @SneakyThrows public static void main(String[] args) { } From 66d2e71437c6ac11b2fa103359b331afd0e59a21 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Sat, 13 Aug 2022 11:18:50 +0300 Subject: [PATCH 25/26] WB-130822 ApplicationContext Task * add ApplicationContext.java * add annotations * add Demo application * add Reflections lib dependency --- lesson-demo/pom.xml | 5 +++ .../java/com/bobocode/demo/DemoWebApp.java | 9 ++++- .../bobocode/demo/annotation/Autowired.java | 8 ++++ .../bobocode/demo/annotation/Component.java | 9 +++++ .../demo/context/ApplicationContext.java | 9 +++++ .../demo/context/ApplicationContextImpl.java | 38 +++++++++++++++++++ .../bobocode/demo/service/MessageService.java | 16 ++++++++ .../bobocode/demo/service/PrinterService.java | 15 ++++++++ 8 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/annotation/Autowired.java create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/annotation/Component.java create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/service/PrinterService.java diff --git a/lesson-demo/pom.xml b/lesson-demo/pom.xml index d45db22..62b645e 100644 --- a/lesson-demo/pom.xml +++ b/lesson-demo/pom.xml @@ -33,6 +33,11 @@ <artifactId>jackson-databind</artifactId> <version>2.12.5</version> </dependency> + <dependency> + <groupId>org.reflections</groupId> + <artifactId>reflections</artifactId> + <version>0.10.2</version> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java b/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java index 3cc15c9..bc39931 100644 --- a/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java +++ b/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java @@ -1,10 +1,17 @@ package com.bobocode.demo; +import com.bobocode.demo.context.ApplicationContextImpl; +import com.bobocode.demo.service.MessageService; +import com.bobocode.demo.service.PrinterService; import lombok.SneakyThrows; public class DemoWebApp { @SneakyThrows public static void main(String[] args) { - + var context = new ApplicationContextImpl("com.bobocode.demo"); + MessageService messageService = context.getBean(MessageService.class); + messageService.setMessage("Hello everyone! It's been a good time"); + PrinterService printerService = context.getBean(PrinterService.class); + printerService.printMessage(); } } diff --git a/lesson-demo/src/main/java/com/bobocode/demo/annotation/Autowired.java b/lesson-demo/src/main/java/com/bobocode/demo/annotation/Autowired.java new file mode 100644 index 0000000..84912b6 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/annotation/Autowired.java @@ -0,0 +1,8 @@ +package com.bobocode.demo.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Autowired { +} diff --git a/lesson-demo/src/main/java/com/bobocode/demo/annotation/Component.java b/lesson-demo/src/main/java/com/bobocode/demo/annotation/Component.java new file mode 100644 index 0000000..7538c9c --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/annotation/Component.java @@ -0,0 +1,9 @@ +package com.bobocode.demo.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Component { + String value() default ""; +} diff --git a/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java new file mode 100644 index 0000000..4cf3e6a --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java @@ -0,0 +1,9 @@ +package com.bobocode.demo.context; + +public interface ApplicationContext { + <T> T getBean(Class<T> type); + + <T> T getBean(Class<T> type, String name); + + <T> T getAllBeans(Class<T> type); +} diff --git a/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java new file mode 100644 index 0000000..fe7bc87 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java @@ -0,0 +1,38 @@ +package com.bobocode.demo.context; + +import com.bobocode.demo.annotation.Component; +import org.reflections.Reflections; + +import java.util.HashMap; +import java.util.Map; + +public class ApplicationContextImpl implements ApplicationContext { + private Map<String, Object> beanMap = new HashMap<>(); + + public ApplicationContextImpl(String basePackage) { + var reflections = new Reflections(basePackage); + var beanClasses = reflections.getTypesAnnotatedWith(Component.class); + beanClasses.forEach(System.out::println); + // todo: 1. Create all bean objects + // todo: 2. Add them to map by name + // todo: 3. Inject bean dependencies + } + + @Override + public <T> T getBean(Class<T> type) { + // todo: find a bean by type + return null; + } + + @Override + public <T> T getBean(Class<T> type, String name) { + // todo: find a bean name and type + return null; + } + + @Override + public <T> T getAllBeans(Class<T> type) { + // todo: find all bean object of this type + return null; + } +} diff --git a/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java b/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java new file mode 100644 index 0000000..78144e4 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java @@ -0,0 +1,16 @@ +package com.bobocode.demo.service; + +import com.bobocode.demo.annotation.Component; + +@Component +public class MessageService { + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/lesson-demo/src/main/java/com/bobocode/demo/service/PrinterService.java b/lesson-demo/src/main/java/com/bobocode/demo/service/PrinterService.java new file mode 100644 index 0000000..91d554a --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/service/PrinterService.java @@ -0,0 +1,15 @@ +package com.bobocode.demo.service; + +import com.bobocode.demo.annotation.Autowired; +import com.bobocode.demo.annotation.Component; + +@Component +public class PrinterService { + @Autowired + private MessageService messageService; + + public void printMessage() { + var message = messageService.getMessage(); + System.out.println("*" + message); + } +} From 8e6365a7aece5ae4b9e1b98e3e9c61a6ab25c625 Mon Sep 17 00:00:00 2001 From: Taras Boychuk <tarass.boychuk@gmail.com> Date: Tue, 16 Aug 2022 13:59:29 +0300 Subject: [PATCH 26/26] WB-130822 ApplicationContext Task * implement ApplicationContext * implement injection logic * implement simple trimming post-processing logic using CGLib proxy --- .../java/com/bobocode/demo/DemoWebApp.java | 3 +- .../com/bobocode/demo/annotation/Trimmed.java | 8 ++ .../demo/context/ApplicationContext.java | 4 +- .../demo/context/ApplicationContextImpl.java | 127 +++++++++++++++--- .../bobocode/demo/service/MessageService.java | 3 +- 5 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 lesson-demo/src/main/java/com/bobocode/demo/annotation/Trimmed.java diff --git a/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java b/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java index bc39931..1982c43 100644 --- a/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java +++ b/lesson-demo/src/main/java/com/bobocode/demo/DemoWebApp.java @@ -10,7 +10,8 @@ public class DemoWebApp { public static void main(String[] args) { var context = new ApplicationContextImpl("com.bobocode.demo"); MessageService messageService = context.getBean(MessageService.class); - messageService.setMessage("Hello everyone! It's been a good time"); + System.out.println(messageService.getClass()); + messageService.setMessage(" Hello everyone! It's been a good time "); PrinterService printerService = context.getBean(PrinterService.class); printerService.printMessage(); } diff --git a/lesson-demo/src/main/java/com/bobocode/demo/annotation/Trimmed.java b/lesson-demo/src/main/java/com/bobocode/demo/annotation/Trimmed.java new file mode 100644 index 0000000..3ea81bc --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/demo/annotation/Trimmed.java @@ -0,0 +1,8 @@ +package com.bobocode.demo.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Trimmed { +} diff --git a/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java index 4cf3e6a..4984323 100644 --- a/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java +++ b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContext.java @@ -1,9 +1,11 @@ package com.bobocode.demo.context; +import java.util.Map; + public interface ApplicationContext { <T> T getBean(Class<T> type); <T> T getBean(Class<T> type, String name); - <T> T getAllBeans(Class<T> type); + <T> Map<String, T> getAllBeans(Class<T> type); } diff --git a/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java index fe7bc87..0a2d560 100644 --- a/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java +++ b/lesson-demo/src/main/java/com/bobocode/demo/context/ApplicationContextImpl.java @@ -1,38 +1,133 @@ package com.bobocode.demo.context; +import com.bobocode.demo.annotation.Autowired; import com.bobocode.demo.annotation.Component; +import com.bobocode.demo.annotation.Trimmed; +import lombok.SneakyThrows; import org.reflections.Reflections; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.MethodInterceptor; import java.util.HashMap; import java.util.Map; +import java.util.Set; + +import static java.util.stream.Collectors.toMap; + +public class ApplicationContextImpl implements ApplicationContext { + private final Map<String, Object> beanMap = new HashMap<>(); -public class ApplicationContextImpl implements ApplicationContext { - private Map<String, Object> beanMap = new HashMap<>(); - public ApplicationContextImpl(String basePackage) { - var reflections = new Reflections(basePackage); - var beanClasses = reflections.getTypesAnnotatedWith(Component.class); - beanClasses.forEach(System.out::println); - // todo: 1. Create all bean objects - // todo: 2. Add them to map by name - // todo: 3. Inject bean dependencies + var componentClasses = scanPackageForComponents(basePackage); + createBeans(componentClasses); + postProcessBeans(); } @Override public <T> T getBean(Class<T> type) { - // todo: find a bean by type - return null; + var beans = getAllBeans(type); + if (beans.size() > 1) { + throw new RuntimeException("No unique bean exception"); + } + return beans.values().stream().findAny().orElseThrow(); } @Override public <T> T getBean(Class<T> type, String name) { - // todo: find a bean name and type - return null; + var bean = beanMap.get(name); + return type.cast(bean); } @Override - public <T> T getAllBeans(Class<T> type) { - // todo: find all bean object of this type - return null; + public <T> Map<String, T> getAllBeans(Class<T> type) { + return beanMap.entrySet() + .stream() + .filter(entry -> type.isAssignableFrom(entry.getValue().getClass())) + .collect(toMap(Map.Entry::getKey, e -> type.cast(e.getValue()))); + } + + private Set<Class<?>> scanPackageForComponents(String basePackage) { + var reflections = new Reflections(basePackage); + return reflections.getTypesAnnotatedWith(Component.class); + } + + @SneakyThrows + private void createBeans(Set<Class<?>> beanClasses) { + for (var beanType : beanClasses) { + var constructor = beanType.getConstructor(); + var bean = constructor.newInstance(); + var beanName = resolveBeanName(beanType); + beanMap.put(beanName, bean); + } + } + + private String resolveBeanName(Class<?> beanType) { + var explicitName = beanType.getAnnotation(Component.class).value(); + return explicitName.isEmpty() ? beanType.getSimpleName() : explicitName; + } + + private void postProcessBeans() { + trimmingPostProcessing(); + autowiringPostProcessing(); + } + + @SneakyThrows + private void autowiringPostProcessing() { + for (var beanEntry : beanMap.entrySet()) { + var bean = beanEntry.getValue(); + var beanType = bean.getClass(); + for (var field : beanType.getDeclaredFields()) { + if (field.isAnnotationPresent(Autowired.class)) { + var dependency = getBean(field.getType()); + field.setAccessible(true); + field.set(bean, dependency); + } + } + } + } + + private void trimmingPostProcessing() { + for (var beanEntry : beanMap.entrySet()) { + var beanName = beanEntry.getKey(); + var bean = beanEntry.getValue(); + if (requiresProxying(beanEntry.getValue())) { + var beanProxy = createProxy(bean); + beanMap.put(beanName, beanProxy); + } + } + } + + private boolean requiresProxying(Object value) { + for (var method : value.getClass().getDeclaredMethods()) { + for (var param : method.getParameters()) { + if (param.isAnnotationPresent(Trimmed.class)) { + return true; + } + } + } + return false; + } + + private Object createProxy(Object bean) { + var beanType = bean.getClass(); + var enhancer = new Enhancer(); + enhancer.setSuperclass(beanType); + enhancer.setInterfaces(beanType.getInterfaces()); + MethodInterceptor trimmingMethodInterceptor = (obj, method, args, methodProxy) -> { + var params = method.getParameters(); + for (int i = 0; i < params.length; i++) { + var param = params[i]; + if (param.isAnnotationPresent(Trimmed.class)) { + if (param.getType().equals(String.class)) { + args[i] = ((String) args[i]).trim(); + } else { + throw new RuntimeException("Only String params can be trimmed"); + } + } + } + return methodProxy.invokeSuper(obj, args); + }; + enhancer.setCallback(trimmingMethodInterceptor); + return enhancer.create(); } } diff --git a/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java b/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java index 78144e4..b075d58 100644 --- a/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java +++ b/lesson-demo/src/main/java/com/bobocode/demo/service/MessageService.java @@ -1,6 +1,7 @@ package com.bobocode.demo.service; import com.bobocode.demo.annotation.Component; +import com.bobocode.demo.annotation.Trimmed; @Component public class MessageService { @@ -10,7 +11,7 @@ public String getMessage() { return message; } - public void setMessage(String message) { + public void setMessage(@Trimmed String message) { this.message = message; } }