diff --git a/2024-03/spring-17-mvc/pom.xml b/2024-03/spring-17-mvc/pom.xml
new file mode 100644
index 00000000..c9bb3f66
--- /dev/null
+++ b/2024-03/spring-17-mvc/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-mvc-class-work
+ 1.0
+
+ pom
+
+
+ spring-mvc-exercise
+ spring-mvc-solution-1
+ spring-mvc-solution-2
+ spring-mvc-solution-3
+ spring-mvc-demo
+
+
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/pom.xml b/2024-03/spring-17-mvc/spring-mvc-demo/pom.xml
new file mode 100644
index 00000000..1e7cd783
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-mvc-demo
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.0
+
+
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ 2.2.220
+ 2.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/Main.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..306644c8
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,28 @@
+package ru.otus.spring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+
+import jakarta.annotation.PostConstruct;
+
+@SpringBootApplication
+public class Main {
+
+ // http://localhost:8080/server/system/info
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class);
+ }
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ private PersonRepository repository;
+
+ @PostConstruct
+ public void init() {
+ repository.save(new Person(1, "Pushkin"));
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/config/WebConfig.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/config/WebConfig.java
new file mode 100644
index 00000000..e201fdcf
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/config/WebConfig.java
@@ -0,0 +1,21 @@
+package ru.otus.spring.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import ru.otus.spring.rest.resolvers.SystemInfoMethodArgumentResolver;
+
+import java.util.List;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ @Autowired
+ private SystemInfoMethodArgumentResolver systemInfoMethodArgumentResolver;
+
+ @Override
+ public void addArgumentResolvers(List resolvers) {
+ resolvers.add(systemInfoMethodArgumentResolver);
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/domain/Person.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..ecbb8d5b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,40 @@
+package ru.otus.spring.domain;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class Person {
+
+ @Id
+ private long id;
+ private String name;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public Person(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/domain/SystemInfo.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/domain/SystemInfo.java
new file mode 100644
index 00000000..c16a8a78
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/domain/SystemInfo.java
@@ -0,0 +1,31 @@
+package ru.otus.spring.domain;
+
+public class SystemInfo {
+ private final String osName;
+ private final String timeZone;
+ private final String osArch;
+ private final int processorsCount;
+
+ public SystemInfo(String osName, String timeZone, String osArch, int processorsCount) {
+ this.osName = osName;
+ this.timeZone = timeZone;
+ this.osArch = osArch;
+ this.processorsCount = processorsCount;
+ }
+
+ public String getOsName() {
+ return osName;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public String getOsArch() {
+ return osArch;
+ }
+
+ public int getProcessorsCount() {
+ return processorsCount;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/repostory/PersonRepository.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/repostory/PersonRepository.java
new file mode 100644
index 00000000..c43a877b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/repostory/PersonRepository.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.repostory;
+
+import org.springframework.data.repository.ListCrudRepository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonRepository extends ListCrudRepository {
+
+ List findAll();
+ List findByName(String name);
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/PersonController.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..b195143e
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,64 @@
+package ru.otus.spring.rest;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+import ru.otus.spring.rest.exceptions.NotFoundException;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+public class PersonController {
+
+ private final PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET)
+ public List getAllPersons() {
+ return repository.findAll().stream()
+ .map(PersonDto::toDto)
+ .collect(Collectors.toList());
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET, params = "name")
+ public PersonDto getPersonByNameInRequest(@RequestParam("name") String name) {
+ Person person = repository.findByName(name).stream().findFirst().orElseThrow(NotFoundException::new);
+ return PersonDto.toDto(person);
+ }
+
+ @GetMapping("/persons/{id}")
+ public PersonDto getPersonByIdInPath(@PathVariable("id") long id) {
+ Person person = repository.findById(id).orElseThrow(NotFoundException::new);
+ return PersonDto.toDto(person);
+ }
+
+ @PostMapping("/persons")
+ public PersonDto createNewPerson(@RequestBody PersonDto dto) {
+ Person person = PersonDto.toDomainObject(dto);
+ Person savedPerson = repository.save(person);
+ return PersonDto.toDto(savedPerson);
+ }
+
+ @PatchMapping("/persons/{id}/name")
+ public PersonDto updateNameById(@PathVariable("id") long id, @RequestParam("name") String name) {
+ Person person = repository.findById(id).orElseThrow(NotFoundException::new);
+ person.setName(name);
+ return PersonDto.toDto(repository.save(person));
+ }
+
+ @DeleteMapping("/persons/{id}")
+ public void deleteById(@PathVariable("id") long id) {
+ repository.deleteById(id);
+ }
+
+ @ExceptionHandler(NotFoundException.class)
+ public ResponseEntity handleNotFound(NotFoundException ex) {
+ return ResponseEntity.badRequest().body("Таких тут нет!");
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/SystemInfoController.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/SystemInfoController.java
new file mode 100644
index 00000000..f188a94f
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/SystemInfoController.java
@@ -0,0 +1,14 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import ru.otus.spring.domain.SystemInfo;
+
+@RestController
+public class SystemInfoController {
+
+ @GetMapping("/server/system/info")
+ public SystemInfo getServerSystemInfo(SystemInfo systemInfo) {
+ return systemInfo;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/dto/PersonDto.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
new file mode 100644
index 00000000..a2aeae8f
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Russian Post
+ *
+ * This source code is Russian Post Confidential Proprietary.
+ * This software is protected by copyright. All rights and titles are reserved.
+ * You shall not use, copy, distribute, modify, decompile, disassemble or reverse engineer the software.
+ * Otherwise this violation would be treated by law and would be subject to legal prosecution.
+ * Legal use of the software provides receipt of a license from the right name only.
+ */
+package ru.otus.spring.rest.dto;
+
+import ru.otus.spring.domain.Person;
+
+/**
+ * DTO that represents Person
+ */
+@SuppressWarnings("all")
+public class PersonDto {
+
+ private long id;
+ private String name;
+
+ public PersonDto() {
+ }
+
+ public PersonDto(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public static Person toDomainObject(PersonDto dto) {
+ return new Person(dto.getId(), dto.getName());
+ }
+
+ public static PersonDto toDto(Person person) {
+ return new PersonDto(person.getId(), person.getName());
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
new file mode 100644
index 00000000..5dc475b8
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.spring.rest.exceptions;
+
+public class NotFoundException extends RuntimeException{
+
+ public NotFoundException() {
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/resolvers/SystemInfoMethodArgumentResolver.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/resolvers/SystemInfoMethodArgumentResolver.java
new file mode 100644
index 00000000..51f517a7
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/rest/resolvers/SystemInfoMethodArgumentResolver.java
@@ -0,0 +1,33 @@
+package ru.otus.spring.rest.resolvers;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+import ru.otus.spring.domain.SystemInfo;
+import ru.otus.spring.service.SystemInfoService;
+
+@Component
+public class SystemInfoMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
+ private final SystemInfoService systemInfoService;
+
+ public SystemInfoMethodArgumentResolver(SystemInfoService systemInfoService) {
+ this.systemInfoService = systemInfoService;
+ }
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.getParameterType().equals(SystemInfo.class);
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter,
+ ModelAndViewContainer mavContainer,
+ NativeWebRequest webRequest,
+ WebDataBinderFactory binderFactory) throws Exception {
+ return systemInfoService.getSystemInfo();
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/service/SystemInfoService.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/service/SystemInfoService.java
new file mode 100644
index 00000000..26fb34d2
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/main/java/ru/otus/spring/service/SystemInfoService.java
@@ -0,0 +1,17 @@
+package ru.otus.spring.service;
+
+import org.springframework.stereotype.Service;
+import ru.otus.spring.domain.SystemInfo;
+
+@Service
+public class SystemInfoService {
+
+ public SystemInfo getSystemInfo(){
+ String osName = System.getProperty("os.name");
+ String timeZone = System.getProperty("user.timezone");
+ String osArch = System.getProperty("os.arch");
+ int processorsCount = Runtime.getRuntime().availableProcessors();
+ return new SystemInfo(osName, timeZone, osArch, processorsCount);
+
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..118a671a
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,127 @@
+package ru.otus.spring.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+import ru.otus.spring.service.SystemInfoService;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(PersonController.class)
+class PersonControllerTest {
+
+ public static final String ERROR_STRING = "Таких тут нет!";
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private PersonRepository repository;
+
+ @MockBean
+ private SystemInfoService systemInfoService;
+
+ @Test
+ void shouldReturnCorrectPersonsList() throws Exception {
+ List persons = List.of(new Person(1, "Person1"), new Person(2, "Person2"));
+ given(repository.findAll()).willReturn(persons);
+
+ List expectedResult = persons.stream()
+ .map(PersonDto::toDto).collect(Collectors.toList());
+
+ mvc.perform(get("/persons"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByNameInRequest() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findByName(person.getName())).willReturn(List.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons").param("name", person.getName()))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByIdInPath() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findById(1L)).willReturn(Optional.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnExpectedErrorWhenPersonNotFound() throws Exception {
+ given(repository.findById(1L)).willReturn(Optional.empty());
+
+ mvc.perform(get("/persons").param("name", "Person1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+ }
+
+ @Test
+ void shouldCorrectSaveNewPerson() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.save(any())).willReturn(person);
+ String expectedResult = mapper.writeValueAsString(PersonDto.toDto(person));
+
+ mvc.perform(post("/persons").contentType(APPLICATION_JSON)
+ .content(expectedResult))
+ .andExpect(status().isOk())
+ .andExpect(content().json(expectedResult));
+ }
+
+ @Test
+ void shouldCorrectUpdatePersonName() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findById(1L)).willReturn(Optional.of(person));
+ given(repository.save(any())).willAnswer(invocation -> invocation.getArgument(0));
+
+ Person expectedPerson = new Person(1, "Person2");
+ String expectedResult = mapper.writeValueAsString(PersonDto.toDto(expectedPerson));
+
+ mvc.perform(patch("/persons/{id}/name", 1).param("name", expectedPerson.getName())
+ .content(expectedResult))
+ .andExpect(status().isOk())
+ .andExpect(content().json(expectedResult));
+ }
+
+ @Test
+ void shouldCorrectDeletePerson() throws Exception {
+ mvc.perform(delete("/persons/1"))
+ .andExpect(status().isOk());
+ verify(repository, times(1)).deleteById(1L);
+ }
+
+
+}
\ No newline at end of file
diff --git a/2024-03/spring-17-mvc/spring-mvc-demo/src/test/java/ru/otus/spring/rest/SystemInfoControllerTest.java b/2024-03/spring-17-mvc/spring-mvc-demo/src/test/java/ru/otus/spring/rest/SystemInfoControllerTest.java
new file mode 100644
index 00000000..3c35c11b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-demo/src/test/java/ru/otus/spring/rest/SystemInfoControllerTest.java
@@ -0,0 +1,39 @@
+package ru.otus.spring.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.otus.spring.domain.SystemInfo;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.service.SystemInfoService;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+
+@WebMvcTest(SystemInfoController.class)
+@Import(SystemInfoService.class)
+class SystemInfoControllerTest {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private PersonRepository repository;
+
+ @Autowired
+ private SystemInfoService systemInfoService;
+
+ @Test
+ void shouldReturnCorrectServerSystemInfo() throws Exception {
+ SystemInfo expectedSystemInfo = systemInfoService.getSystemInfo();
+ mvc.perform(get("/server/system/info"))
+ .andExpect(content().json(mapper.writeValueAsString(expectedSystemInfo)));
+ }
+}
\ No newline at end of file
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/pom.xml b/2024-03/spring-17-mvc/spring-mvc-exercise/pom.xml
new file mode 100644
index 00000000..e401dc50
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-mvc-exercise
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.0
+
+
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ 2.2.220
+ 2.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/Main.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..101a633b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,26 @@
+package ru.otus.spring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+
+import jakarta.annotation.PostConstruct;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class);
+ }
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ private PersonRepository repository;
+
+ @PostConstruct
+ public void init() {
+ repository.save(new Person(1, "Pushkin"));
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/domain/Person.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..ecbb8d5b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,40 @@
+package ru.otus.spring.domain;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class Person {
+
+ @Id
+ private long id;
+ private String name;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public Person(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/repostory/PersonRepository.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/repostory/PersonRepository.java
new file mode 100644
index 00000000..c43a877b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/repostory/PersonRepository.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.repostory;
+
+import org.springframework.data.repository.ListCrudRepository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonRepository extends ListCrudRepository {
+
+ List findAll();
+ List findByName(String name);
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/PersonController.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..344a34b7
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,13 @@
+package ru.otus.spring.rest;
+
+import ru.otus.spring.repostory.PersonRepository;
+
+
+public class PersonController {
+
+ private final PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/dto/PersonDto.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
new file mode 100644
index 00000000..a2aeae8f
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Russian Post
+ *
+ * This source code is Russian Post Confidential Proprietary.
+ * This software is protected by copyright. All rights and titles are reserved.
+ * You shall not use, copy, distribute, modify, decompile, disassemble or reverse engineer the software.
+ * Otherwise this violation would be treated by law and would be subject to legal prosecution.
+ * Legal use of the software provides receipt of a license from the right name only.
+ */
+package ru.otus.spring.rest.dto;
+
+import ru.otus.spring.domain.Person;
+
+/**
+ * DTO that represents Person
+ */
+@SuppressWarnings("all")
+public class PersonDto {
+
+ private long id;
+ private String name;
+
+ public PersonDto() {
+ }
+
+ public PersonDto(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public static Person toDomainObject(PersonDto dto) {
+ return new Person(dto.getId(), dto.getName());
+ }
+
+ public static PersonDto toDto(Person person) {
+ return new PersonDto(person.getId(), person.getName());
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/exeptions/NotFoundException.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/exeptions/NotFoundException.java
new file mode 100644
index 00000000..169b67b2
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/main/java/ru/otus/spring/rest/exeptions/NotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.spring.rest.exeptions;
+
+public class NotFoundException extends RuntimeException{
+
+ public NotFoundException() {
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-exercise/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2024-03/spring-17-mvc/spring-mvc-exercise/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..2b7c8154
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-exercise/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,83 @@
+package ru.otus.spring.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(PersonController.class)
+class PersonControllerTest {
+
+ public static final String ERROR_STRING = "Таких тут нет!";
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private PersonRepository repository;
+
+ @Test
+ void shouldReturnCorrectPersonsList() throws Exception {
+ List persons = List.of(new Person(1, "Person1"), new Person(2, "Person2"));
+ given(repository.findAll()).willReturn(persons);
+
+ List expectedResult = persons.stream()
+ .map(PersonDto::toDto).collect(Collectors.toList());
+
+ mvc.perform(get("/persons"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByNameInRequest() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findByName(person.getName())).willReturn(List.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons").param("name", person.getName()))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByIdInPath() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findById(1L)).willReturn(Optional.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnExpectedErrorWhenPersonNotFound() throws Exception {
+ given(repository.findById(1L)).willReturn(Optional.empty());
+
+ mvc.perform(get("/persons").param("name", "Person1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+ }
+}
\ No newline at end of file
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/pom.xml b/2024-03/spring-17-mvc/spring-mvc-solution-1/pom.xml
new file mode 100644
index 00000000..f8179ada
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-mvc-solution-1
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.0
+
+
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ 2.2.220
+ 2.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/Main.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..101a633b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,26 @@
+package ru.otus.spring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+
+import jakarta.annotation.PostConstruct;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class);
+ }
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ private PersonRepository repository;
+
+ @PostConstruct
+ public void init() {
+ repository.save(new Person(1, "Pushkin"));
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/domain/Person.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..ecbb8d5b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,40 @@
+package ru.otus.spring.domain;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class Person {
+
+ @Id
+ private long id;
+ private String name;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public Person(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/repostory/PersonRepository.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/repostory/PersonRepository.java
new file mode 100644
index 00000000..c43a877b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/repostory/PersonRepository.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.repostory;
+
+import org.springframework.data.repository.ListCrudRepository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonRepository extends ListCrudRepository {
+
+ List findAll();
+ List findByName(String name);
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/PersonController.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..c8e47c24
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,27 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+public class PersonController {
+
+ private final PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET)
+ public List getAllPersons() {
+ return repository.findAll().stream()
+ .map(PersonDto::toDto)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/dto/PersonDto.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
new file mode 100644
index 00000000..a2aeae8f
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Russian Post
+ *
+ * This source code is Russian Post Confidential Proprietary.
+ * This software is protected by copyright. All rights and titles are reserved.
+ * You shall not use, copy, distribute, modify, decompile, disassemble or reverse engineer the software.
+ * Otherwise this violation would be treated by law and would be subject to legal prosecution.
+ * Legal use of the software provides receipt of a license from the right name only.
+ */
+package ru.otus.spring.rest.dto;
+
+import ru.otus.spring.domain.Person;
+
+/**
+ * DTO that represents Person
+ */
+@SuppressWarnings("all")
+public class PersonDto {
+
+ private long id;
+ private String name;
+
+ public PersonDto() {
+ }
+
+ public PersonDto(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public static Person toDomainObject(PersonDto dto) {
+ return new Person(dto.getId(), dto.getName());
+ }
+
+ public static PersonDto toDto(Person person) {
+ return new PersonDto(person.getId(), person.getName());
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
new file mode 100644
index 00000000..5dc475b8
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.spring.rest.exceptions;
+
+public class NotFoundException extends RuntimeException{
+
+ public NotFoundException() {
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-1/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..2b7c8154
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-1/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,83 @@
+package ru.otus.spring.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(PersonController.class)
+class PersonControllerTest {
+
+ public static final String ERROR_STRING = "Таких тут нет!";
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private PersonRepository repository;
+
+ @Test
+ void shouldReturnCorrectPersonsList() throws Exception {
+ List persons = List.of(new Person(1, "Person1"), new Person(2, "Person2"));
+ given(repository.findAll()).willReturn(persons);
+
+ List expectedResult = persons.stream()
+ .map(PersonDto::toDto).collect(Collectors.toList());
+
+ mvc.perform(get("/persons"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByNameInRequest() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findByName(person.getName())).willReturn(List.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons").param("name", person.getName()))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByIdInPath() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findById(1L)).willReturn(Optional.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnExpectedErrorWhenPersonNotFound() throws Exception {
+ given(repository.findById(1L)).willReturn(Optional.empty());
+
+ mvc.perform(get("/persons").param("name", "Person1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+ }
+}
\ No newline at end of file
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/pom.xml b/2024-03/spring-17-mvc/spring-mvc-solution-2/pom.xml
new file mode 100644
index 00000000..be5cbbf9
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-mvc-solution-2
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.0
+
+
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ 2.2.220
+ 2.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/Main.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..101a633b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,26 @@
+package ru.otus.spring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+
+import jakarta.annotation.PostConstruct;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class);
+ }
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ private PersonRepository repository;
+
+ @PostConstruct
+ public void init() {
+ repository.save(new Person(1, "Pushkin"));
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/domain/Person.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..ecbb8d5b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,40 @@
+package ru.otus.spring.domain;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class Person {
+
+ @Id
+ private long id;
+ private String name;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public Person(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/repostory/PersonRepository.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/repostory/PersonRepository.java
new file mode 100644
index 00000000..c43a877b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/repostory/PersonRepository.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.repostory;
+
+import org.springframework.data.repository.ListCrudRepository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonRepository extends ListCrudRepository {
+
+ List findAll();
+ List findByName(String name);
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/PersonController.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..2c1b02bc
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,40 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.*;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+import ru.otus.spring.rest.exceptions.NotFoundException;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+public class PersonController {
+
+ private final PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET)
+ public List getAllPersons() {
+ return repository.findAll().stream()
+ .map(PersonDto::toDto)
+ .collect(Collectors.toList());
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET, params = "name")
+ public PersonDto getPersonByNameInRequest(@RequestParam("name") String name) {
+ Person person = repository.findByName(name).stream().findFirst().orElseThrow(NotFoundException::new);
+ return PersonDto.toDto(person);
+ }
+
+ @GetMapping("/persons/{id}")
+ public PersonDto getPersonByIdInPath(@PathVariable("id") long id) {
+ Person person = repository.findById(id).orElseThrow(NotFoundException::new);
+ return PersonDto.toDto(person);
+ }
+
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/dto/PersonDto.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
new file mode 100644
index 00000000..a2aeae8f
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Russian Post
+ *
+ * This source code is Russian Post Confidential Proprietary.
+ * This software is protected by copyright. All rights and titles are reserved.
+ * You shall not use, copy, distribute, modify, decompile, disassemble or reverse engineer the software.
+ * Otherwise this violation would be treated by law and would be subject to legal prosecution.
+ * Legal use of the software provides receipt of a license from the right name only.
+ */
+package ru.otus.spring.rest.dto;
+
+import ru.otus.spring.domain.Person;
+
+/**
+ * DTO that represents Person
+ */
+@SuppressWarnings("all")
+public class PersonDto {
+
+ private long id;
+ private String name;
+
+ public PersonDto() {
+ }
+
+ public PersonDto(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public static Person toDomainObject(PersonDto dto) {
+ return new Person(dto.getId(), dto.getName());
+ }
+
+ public static PersonDto toDto(Person person) {
+ return new PersonDto(person.getId(), person.getName());
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
new file mode 100644
index 00000000..5dc475b8
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.spring.rest.exceptions;
+
+public class NotFoundException extends RuntimeException{
+
+ public NotFoundException() {
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-2/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..6da9bef4
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-2/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,83 @@
+package ru.otus.spring.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(PersonController.class)
+class PersonControllerTest {
+
+ public static final String ERROR_STRING = "Таких тут нет!";
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private PersonRepository repository;
+
+ @Test
+ void shouldReturnCorrectPersonsList() throws Exception {
+ List persons = List.of(new Person(1, "Person1"), new Person(2, "Person2"));
+ given(repository.findAll()).willReturn(persons);
+
+ List expectedResult = persons.stream()
+ .map(PersonDto::toDto).collect(Collectors.toList());
+
+ mvc.perform(get("/persons"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByNameInRequest() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findByName(person.getName())).willReturn(List.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons").param("name", person.getName()))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByIdInPath() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findById(1L)).willReturn(Optional.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnExpectedErrorWhenPersonNotFound() throws Exception {
+ given(repository.findById(1L)).willReturn(Optional.empty());
+
+ mvc.perform(get("/persons").param("name", "Person1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+ }
+}
\ No newline at end of file
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/pom.xml b/2024-03/spring-17-mvc/spring-mvc-solution-3/pom.xml
new file mode 100644
index 00000000..32fc8920
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-mvc-solution-3
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.0
+
+
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ 2.2.220
+ 2.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/requests.http b/2024-03/spring-17-mvc/spring-mvc-solution-3/requests.http
new file mode 100644
index 00000000..958a57b2
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/requests.http
@@ -0,0 +1,10 @@
+POST http://localhost:8080/person
+Content-Type: application/json
+
+{
+ "id": "2",
+ "name": "Pushkin"
+}
+
+###
+GET http://localhost:8080/persons/all
\ No newline at end of file
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/Main.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..101a633b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,26 @@
+package ru.otus.spring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+
+import jakarta.annotation.PostConstruct;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class);
+ }
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ private PersonRepository repository;
+
+ @PostConstruct
+ public void init() {
+ repository.save(new Person(1, "Pushkin"));
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/domain/Person.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..ecbb8d5b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,40 @@
+package ru.otus.spring.domain;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class Person {
+
+ @Id
+ private long id;
+ private String name;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public Person(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/repostory/PersonRepository.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/repostory/PersonRepository.java
new file mode 100644
index 00000000..c43a877b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/repostory/PersonRepository.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.repostory;
+
+import org.springframework.data.repository.ListCrudRepository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonRepository extends ListCrudRepository {
+
+ List findAll();
+ List findByName(String name);
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/PersonController.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..f7ee302b
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,45 @@
+package ru.otus.spring.rest;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+import ru.otus.spring.rest.exceptions.NotFoundException;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+public class PersonController {
+
+ private final PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET)
+ public List getAllPersons() {
+ return repository.findAll().stream()
+ .map(PersonDto::toDto)
+ .collect(Collectors.toList());
+ }
+
+ @RequestMapping(value = "/persons", method = RequestMethod.GET, params = "name")
+ public PersonDto getPersonByNameInRequest(@RequestParam("name") String name) {
+ Person person = repository.findByName(name).stream().findFirst().orElseThrow(NotFoundException::new);
+ return PersonDto.toDto(person);
+ }
+
+ @GetMapping("/persons/{id}")
+ public PersonDto getPersonByIdInPath(@PathVariable("id") long id) {
+ Person person = repository.findById(id).orElseThrow(NotFoundException::new);
+ return PersonDto.toDto(person);
+ }
+
+ @ExceptionHandler(NotFoundException.class)
+ public ResponseEntity handleNotFound(NotFoundException ex) {
+ return ResponseEntity.badRequest().body("Таких тут нет!");
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/dto/PersonDto.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
new file mode 100644
index 00000000..a2aeae8f
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/dto/PersonDto.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Russian Post
+ *
+ * This source code is Russian Post Confidential Proprietary.
+ * This software is protected by copyright. All rights and titles are reserved.
+ * You shall not use, copy, distribute, modify, decompile, disassemble or reverse engineer the software.
+ * Otherwise this violation would be treated by law and would be subject to legal prosecution.
+ * Legal use of the software provides receipt of a license from the right name only.
+ */
+package ru.otus.spring.rest.dto;
+
+import ru.otus.spring.domain.Person;
+
+/**
+ * DTO that represents Person
+ */
+@SuppressWarnings("all")
+public class PersonDto {
+
+ private long id;
+ private String name;
+
+ public PersonDto() {
+ }
+
+ public PersonDto(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public static Person toDomainObject(PersonDto dto) {
+ return new Person(dto.getId(), dto.getName());
+ }
+
+ public static PersonDto toDto(Person person) {
+ return new PersonDto(person.getId(), person.getName());
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
new file mode 100644
index 00000000..5dc475b8
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/main/java/ru/otus/spring/rest/exceptions/NotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.spring.rest.exceptions;
+
+public class NotFoundException extends RuntimeException{
+
+ public NotFoundException() {
+ }
+}
diff --git a/2024-03/spring-17-mvc/spring-mvc-solution-3/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..6da9bef4
--- /dev/null
+++ b/2024-03/spring-17-mvc/spring-mvc-solution-3/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,83 @@
+package ru.otus.spring.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repostory.PersonRepository;
+import ru.otus.spring.rest.dto.PersonDto;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(PersonController.class)
+class PersonControllerTest {
+
+ public static final String ERROR_STRING = "Таких тут нет!";
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private PersonRepository repository;
+
+ @Test
+ void shouldReturnCorrectPersonsList() throws Exception {
+ List persons = List.of(new Person(1, "Person1"), new Person(2, "Person2"));
+ given(repository.findAll()).willReturn(persons);
+
+ List expectedResult = persons.stream()
+ .map(PersonDto::toDto).collect(Collectors.toList());
+
+ mvc.perform(get("/persons"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByNameInRequest() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findByName(person.getName())).willReturn(List.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons").param("name", person.getName()))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnCorrectPersonByIdInPath() throws Exception {
+ Person person = new Person(1, "Person1");
+ given(repository.findById(1L)).willReturn(Optional.of(person));
+ PersonDto expectedResult = PersonDto.toDto(person);
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isOk())
+ .andExpect(content().json(mapper.writeValueAsString(expectedResult)));
+ }
+
+ @Test
+ void shouldReturnExpectedErrorWhenPersonNotFound() throws Exception {
+ given(repository.findById(1L)).willReturn(Optional.empty());
+
+ mvc.perform(get("/persons").param("name", "Person1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+
+ mvc.perform(get("/persons/1"))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string(ERROR_STRING));
+ }
+}
\ No newline at end of file