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;
     }
 }