From 10868327841ed996836bd1dab7a2eb1b94ff78c5 Mon Sep 17 00:00:00 2001 From: HattoriHenzo Date: Mon, 29 Jun 2026 01:19:08 -0400 Subject: [PATCH 1/2] Add the implementation of Onion-Architecture --- onion-architecture/README.md | 292 ++++++++++++++ onion-architecture/application/pom.xml | 66 +++ .../onion/application/dto/PersonResponse.java | 37 ++ .../application/dto/SavePersonCommand.java | 37 ++ .../application/usecase/GetPersonUseCase.java | 75 ++++ .../usecase/SavePersonUseCase.java | 67 ++++ .../usecase/GetPersonUseCaseTest.java | 198 +++++++++ .../usecase/SavePersonUseCaseTest.java | 261 ++++++++++++ onion-architecture/domain/pom.xml | 47 +++ .../domain/exception/DomainException.java | 34 ++ .../iluwatar/onion/domain/model/Category.java | 50 +++ .../iluwatar/onion/domain/model/Person.java | 114 ++++++ .../domain/repository/PersonRepository.java | 41 ++ .../onion/domain/model/CategoryTest.java | 76 ++++ .../onion/domain/model/PersonTest.java | 221 ++++++++++ onion-architecture/etc/onion-architecture.png | Bin 0 -> 258593 bytes .../etc/onion-architecture.puml | 237 +++++++++++ ...onion-architecture.postman_collection.json | 83 ++++ onion-architecture/infrastructure/pom.xml | 87 ++++ .../onion/infrastructure/Application.java | 38 ++ .../config/ApplicationConfig.java | 47 +++ .../persistence/JpaCategoryEntity.java | 55 +++ .../persistence/JpaPersonEntity.java | 87 ++++ .../persistence/PersonRepositoryAdapter.java | 106 +++++ .../SpringDataPersonRepository.java | 37 ++ .../infrastructure/web/PersonController.java | 73 ++++ .../src/main/resources/application.properties | 19 + .../src/main/resources/data.sql | 4 + .../PersonRepositoryAdapterTest.java | 356 +++++++++++++++++ .../web/PersonControllerTest.java | 376 ++++++++++++++++++ onion-architecture/pom.xml | 49 +++ pom.xml | 8 +- 32 files changed, 3277 insertions(+), 1 deletion(-) create mode 100644 onion-architecture/README.md create mode 100644 onion-architecture/application/pom.xml create mode 100644 onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java create mode 100644 onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java create mode 100644 onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java create mode 100644 onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java create mode 100644 onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java create mode 100644 onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java create mode 100644 onion-architecture/domain/pom.xml create mode 100644 onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java create mode 100644 onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java create mode 100644 onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java create mode 100644 onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java create mode 100644 onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java create mode 100644 onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java create mode 100644 onion-architecture/etc/onion-architecture.png create mode 100644 onion-architecture/etc/onion-architecture.puml create mode 100644 onion-architecture/etc/postman/onion-architecture.postman_collection.json create mode 100644 onion-architecture/infrastructure/pom.xml create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java create mode 100644 onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java create mode 100644 onion-architecture/infrastructure/src/main/resources/application.properties create mode 100644 onion-architecture/infrastructure/src/main/resources/data.sql create mode 100644 onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java create mode 100644 onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java create mode 100644 onion-architecture/pom.xml diff --git a/onion-architecture/README.md b/onion-architecture/README.md new file mode 100644 index 000000000000..1a7fc394638d --- /dev/null +++ b/onion-architecture/README.md @@ -0,0 +1,292 @@ +--- +title: "Onion Architecture in Java: A Layered Approach to Building Maintainable and Testable Applications" +shortTitle: Onion Architecture +description: "Learn how the Onion Architecture pattern promotes maintainability, testability, and separation of concerns in Java applications. Explore examples, benefits, and best practices." +category: Architectural +language: en +tag: + - Decoupling + - Enterprise patterns + - Integration + - Microservices + - Scalability + - Security +--- +## Intent of Microservices API Gateway Design Pattern + +In this project, the implementation demonstrates **Onion Architecture** with clear dependency direction: infrastructure and application depend on domain, but domain is independent. + +The central intent is to keep business rules in `domain`, orchestrate use cases in `application`, and isolate delivery/persistence/API details in `infrastructure`. + +Current implementation highlights: + +* `domain`: `Person`, `Category`, `PersonRepository`, `DomainException` +* `application`: `SavePersonUseCase`, `GetPersonUseCase`, DTO records +* `infrastructure`: Spring Boot REST controller, JPA entities, repository adapter, bean wiring + +## Also known as + +* Ports and Adapters Architecture +* Hexagonal-style layering (conceptually related) +* Dependency-rule-first architecture + +## Detailed Explanation of Onion Architecture Pattern with Real-World Examples + +Real-world example + +> Imagine a people-management service where business validation must stay consistent no matter how data is stored or exposed. In this codebase, `Person` and `Category` enforce invariants (e.g., age >= 18, required email/category), use cases coordinate behavior, and infrastructure adapts HTTP + JPA concerns. This allows the persistence or web layer to evolve without changing core domain rules. + +In plain words + +> The project keeps business logic in the center and treats frameworks as replaceable details around it. + +Wikipedia says + +> Onion Architecture is a software architecture pattern that emphasizes separation of concerns and dependency inversion by organizing code in concentric layers, with the domain model at the center. + +Sequence diagram + +![Onion Architecture class diagram](./etc/onion-architecture.png) + +Request flow in this implementation: + +1. Client calls REST endpoint in `PersonController` (`/api/persons`, `/api/persons/{id}`) +2. Controller delegates to `SavePersonUseCase` or `GetPersonUseCase` +3. Use case interacts with `PersonRepository` abstraction from `domain` +4. `PersonRepositoryAdapter` maps domain <-> JPA and delegates to `SpringDataPersonRepository` +5. Response DTO (`PersonResponse`) is returned to the client + +## Programmatic Example of Onion Architecture in Java + +This repository exposes a simple person API backed by use cases and domain models. + +Controller (infrastructure layer): + +```java +@RestController +@RequestMapping("/api") +public class PersonController { + + @GetMapping("/persons/{id}") + public ResponseEntity getPerson(@PathVariable Long id) { + var person = getPersonUseCase.execute(id); + return ResponseEntity.ok(person); + } + + @GetMapping("/persons") + public ResponseEntity> getAllPersons() { + var persons = getPersonUseCase.executeAll(); + return ResponseEntity.ok(persons); + } + + @PostMapping("/persons") + public ResponseEntity savePerson(@RequestBody SavePersonCommand command) { + try { + var savedPerson = savePersonUseCase.execute(command); + return ResponseEntity.status(HttpStatus.OK).body(savedPerson); + } catch (DomainException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } +} +``` + +Use case (application layer): + +```java +public class SavePersonUseCase { + + private final PersonRepository repository; + + public PersonResponse execute(SavePersonCommand command) { + var category = new Category(command.categoryId(), command.categoryType()); + var person = new Person( + null, + command.firstName(), + command.lastName(), + command.age(), + command.phoneNumber(), + command.email(), + category); + + var savedPerson = repository.save(person); + return new PersonResponse( + savedPerson.getId(), + savedPerson.getFirstName(), + savedPerson.getLastName(), + savedPerson.getAge(), + savedPerson.getPhoneNumber(), + savedPerson.getEmail(), + savedPerson.getCategory().getId(), + savedPerson.getCategory().getType() + ); + } +} +``` + +Domain validation (domain layer): + +```java +public class Person { + public Person(Long id, String firstName, String lastName, int age, + String phoneNumber, String email, Category category) { + validateNames(firstName, lastName); + validateAge(age); + validatePhone(phoneNumber); + validateEmail(email); + validateCategory(category); + // assign fields... + } +} +``` + +Repository adapter (infrastructure -> domain port): + +```java +@Repository +public class PersonRepositoryAdapter implements PersonRepository { + + private final SpringDataPersonRepository repository; + + @Override + public Optional findById(Long id) { + return repository.findById(id).map(this::mapToDomain); + } + + @Override + public Person save(Person person) { + JpaPersonEntity savedEntity = repository.save(mapToEntity(person)); + return mapToDomain(savedEntity); + } +} +``` + +- **Maven 3.6.0** or higher + +### Build Steps + +1. **Navigate to the onion-architecture module directory:** + ```bash + cd java-design-patterns/onion-architecture + ``` + +2. **Build all modules:** + ```bash + mvn clean package + ``` + This will compile the `domain`, `application`, and `infrastructure` modules and package them into a Spring Boot executable JAR. + +3. **Run the Spring Boot application:** + ```bash + mvn -pl infrastructure spring-boot:run + ``` + Alternatively, after building, run the JAR directly: + ```bash + java -jar infrastructure/target/infrastructure-1.26.0-SNAPSHOT.jar + ``` + +### Accessing the API + +The application exposes REST endpoints at `http://localhost:8080/api`: +There is a Postman collection available in the `etc/postman` folder for testing the API. + +- **Get all persons:** + ```bash + GET http://localhost:8080/api/persons + ``` + +- **Get person by ID:** + ```bash + GET http://localhost:8080/api/persons/{id} + ``` + +- **Create a new person:** + ```bash + POST http://localhost:8080/api/persons + Content-Type: application/json + + { + "firstName": "John", + "lastName": "Doe", + "age": 30, + "phoneNumber": "555-1234", + "email": "john.doe@example.com", + "address": "123 Main St", + "categoryId": 1, + "categoryType": "individual" + } + ``` + +### Run Tests + +To execute unit tests across all modules: + +```bash +mvn clean test +``` + +To run tests for a specific module: + +```bash +mvn -pl domain test +mvn -pl application test +mvn -pl infrastructure test +``` + +### Database + +The application uses an **H2 in-memory database** for demonstration purposes. Configuration is in `infrastructure/src/main/resources/application.properties`: + +- **JDBC URL:** `jdbc:h2:mem:testdb` +- **Username:** `sa` +- **Password:** `password` + +Sample data is initialized from `infrastructure/src/main/resources/data.sql` on application startup. + +## When to Use the Onion Architecture Pattern in Java + +* When domain rules must be stable and independent from frameworks. +* When you want use cases to be testable without HTTP or database setup. +* When infrastructure details (web, JPA, database) should be replaceable. +* When dependency direction must be enforced from outer layers toward the domain core. + +## Onion Architecture Pattern Java Tutorials + +* [Clean Architecture with Spring Boot (Baeldung)](https://www.baeldung.com/spring-boot-clean-architecture) +* [Hexagonal Architecture Explained (Cockburn)](https://alistair.cockburn.us/hexagonal-architecture) +* [Spring Data JPA Reference](https://docs.spring.io/spring-data/jpa/reference/) + +## Benefits and Trade-offs of Microservices API Gateway Pattern + +Benefits: + +* Business validations are centralized in domain constructors (`Person`, `Category`). +* Use cases stay independent from Spring, JPA, and transport concerns. +* Repository abstraction (`PersonRepository`) keeps application logic persistence-agnostic. +* Testability is strong across layers (domain, use case, adapter, controller tests). + +Trade-offs: + +* Additional mapping code between domain models, DTOs, and JPA entities. +* More classes and modules than a simple CRUD-by-controller approach. +* Requires discipline to avoid leaking infrastructure concerns into domain/application. + +## Real-World Applications of Microservices API Gateway Pattern in Java + +* People/contact management services with strict data validation. +* Internal platforms where multiple delivery mechanisms (REST, batch, messaging) can share the same core domain. +* Systems that need incremental infrastructure evolution while preserving business logic. + +## Related Java Design Patterns + +* [Repository](https://martinfowler.com/eaaCatalog/repository.html) - `PersonRepository` defines the domain-facing persistence contract. +* [Adapter](https://refactoring.guru/design-patterns/adapter) - `PersonRepositoryAdapter` bridges domain model and Spring Data JPA. +* [Dependency Injection](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html) - `ApplicationConfig` wires use case beans with repository implementations. + +## References and Credits + +* Project modules: `domain`, `application`, `infrastructure` +* Java 21 + Maven multi-module setup +* Spring Boot 3.3 (`spring-boot-starter-web`, `spring-boot-starter-data-jpa`, H2) +* Layer-focused tests in each module validating domain invariants and use case behavior + diff --git a/onion-architecture/application/pom.xml b/onion-architecture/application/pom.xml new file mode 100644 index 000000000000..eccdaae2869a --- /dev/null +++ b/onion-architecture/application/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + com.iluwatar + onion-architecture + 1.26.0-SNAPSHOT + + application + Application + + + + com.iluwatar + domain + ${project.version} + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + org.mockito + mockito-core + test + + + + org.mockito + mockito-junit-jupiter + test + + + diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java new file mode 100644 index 000000000000..b26d68b84459 --- /dev/null +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java @@ -0,0 +1,37 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.application.dto; + +public record PersonResponse(Long id, + String firstName, + String lastName, + int age, + String phoneNumber, + String email, + Long categoryId, + String categoryType) { +} diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java new file mode 100644 index 000000000000..cafec064747f --- /dev/null +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java @@ -0,0 +1,37 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.application.dto; + +public record SavePersonCommand(String firstName, + String lastName, + int age, + String phoneNumber, + String email, + String address, + Long categoryId, + String categoryType) { +} diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java new file mode 100644 index 000000000000..99e1d25533ec --- /dev/null +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java @@ -0,0 +1,75 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.application.usecase; + +import com.iluwatar.onion.application.dto.PersonResponse; +import com.iluwatar.onion.domain.repository.PersonRepository; + +import java.util.Collection; +import java.util.List; + +public class GetPersonUseCase { + + private final PersonRepository repository; + + public GetPersonUseCase(PersonRepository repository) { + this.repository = repository; + } + + public PersonResponse execute(Long id) { + var person = repository.findById(id) + .orElseThrow(() -> new RuntimeException("Person not found with id: " + id)); + + return new PersonResponse( + person.getId(), + person.getFirstName(), + person.getLastName(), + person.getAge(), + person.getPhoneNumber(), + person.getEmail(), + person.getCategory().getId(), + person.getCategory().getType() + ); + } + + public List executeAll() { + return repository.findAll() + .stream() + .flatMap(Collection::stream) + .map(person -> new PersonResponse( + person.getId(), + person.getFirstName(), + person.getLastName(), + person.getAge(), + person.getPhoneNumber(), + person.getEmail(), + person.getCategory().getId(), + person.getCategory().getType() + )) + .toList(); + } +} diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java new file mode 100644 index 000000000000..2d72d378de0f --- /dev/null +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java @@ -0,0 +1,67 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.application.usecase; + +import com.iluwatar.onion.application.dto.PersonResponse; +import com.iluwatar.onion.application.dto.SavePersonCommand; +import com.iluwatar.onion.domain.model.Category; +import com.iluwatar.onion.domain.model.Person; +import com.iluwatar.onion.domain.repository.PersonRepository; + +public class SavePersonUseCase { + + private final PersonRepository repository; + + public SavePersonUseCase(PersonRepository repository) { + this.repository = repository; + } + + public PersonResponse execute(SavePersonCommand command) { + var category = new Category(command.categoryId(), command.categoryType()); + var person = new Person( + null, + command.firstName(), + command.lastName(), + command.age(), + command.phoneNumber(), + command.email(), + category); + + var savedPerson = repository.save(person); + + return new PersonResponse( + savedPerson.getId(), + savedPerson.getFirstName(), + savedPerson.getLastName(), + savedPerson.getAge(), + savedPerson.getPhoneNumber(), + savedPerson.getEmail(), + savedPerson.getCategory().getId(), + savedPerson.getCategory().getType() + ); + } +} diff --git a/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java new file mode 100644 index 000000000000..e4c39d238bde --- /dev/null +++ b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java @@ -0,0 +1,198 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.application.usecase; + +import com.iluwatar.onion.application.dto.PersonResponse; +import com.iluwatar.onion.domain.model.Category; +import com.iluwatar.onion.domain.model.Person; +import com.iluwatar.onion.domain.repository.PersonRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class GetPersonUseCaseTest { + + @Mock + private PersonRepository personRepository; + + private GetPersonUseCase getPersonUseCase; + + @BeforeEach + void setUp() { + getPersonUseCase = new GetPersonUseCase(personRepository); + } + + @Nested + @DisplayName("Execute by ID - Happy Path") + class ExecuteByIdHappyPath { + + @Test + @DisplayName("Should return PersonResponse when person exists") + void shouldReturnPersonResponseWhenPersonExists() { + // Arrange + var person = new Person( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + new Category(1L, "Professional") + ); + + when(personRepository.findById(1L)).thenReturn(Optional.of(person)); + + // Act + var response = getPersonUseCase.execute(1L); + + // Assert + assertNotNull(response); + assertEquals(1L, response.id()); + assertEquals("John", response.firstName()); + assertEquals("Doe", response.lastName()); + assertEquals(25, response.age()); + assertEquals("+1234567890", response.phoneNumber()); + assertEquals("john.doe@example.com", response.email()); + assertEquals(1L, response.categoryId()); + assertEquals("Professional", response.categoryType()); + + verify(personRepository, times(1)).findById(1L); + } + } + + @Nested + @DisplayName("Execute by ID - Error Cases") + class ExecuteByIdErrorCases { + + @Test + @DisplayName("Should throw RuntimeException when person not found") + void shouldThrowExceptionWhenPersonNotFound() { + // Arrange + when(personRepository.findById(999L)).thenReturn(Optional.empty()); + + // Act & Assert + var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(999L)); + assertTrue(exception.getMessage().contains("Person not found with id: 999")); + + verify(personRepository, times(1)).findById(999L); + } + + @Test + @DisplayName("Should propagate exception when repository throws exception") + void shouldPropagateExceptionWhenRepositoryFails() { + // Arrange + when(personRepository.findById(anyLong())) + .thenThrow(new RuntimeException("Database error")); + + // Act & Assert + var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(1L)); + assertTrue(exception.getMessage().contains("Database error")); + + verify(personRepository, times(1)).findById(1L); + } + } + + @Nested + @DisplayName("Execute All - Happy Path") + class ExecuteAllHappyPath { + + @Test + @DisplayName("Should return list of PersonResponses") + void shouldReturnListOfPersonResponses() { + // Arrange + var person1 = new Person( + 1L, + "John", + "Doe", + 30, + "+9876543255", + "john.doe@example.com", + new Category(2L, "Personal") + ); + + var person2 = new Person( + 2L, + "Jane", + "Smith", + 25, + "+9876543210", + "jane.smith@example.com", + new Category(2L, "Personal") + ); + + when(personRepository.findAll()) + .thenReturn(Optional.of(List.of(person1, person2))); + + // Act + var responses = getPersonUseCase.executeAll(); + + // Assert + assertNotNull(responses); + assertEquals(2, responses.size()); + + PersonResponse response1 = responses.get(0); + assertEquals(1L, response1.id()); + assertEquals("John", response1.firstName()); + assertEquals("Doe", response1.lastName()); + + PersonResponse response2 = responses.get(1); + assertEquals(2L, response2.id()); + assertEquals("Jane", response2.firstName()); + assertEquals("Smith", response2.lastName()); + + verify(personRepository, times(1)).findAll(); + } + + @Test + @DisplayName("Should return empty list when no persons found") + void shouldReturnEmptyListWhenNoPersonsFound() { + // Arrange + when(personRepository.findAll()).thenReturn(Optional.of(List.of())); + + // Act + List responses = getPersonUseCase.executeAll(); + + // Assert + assertNotNull(responses); + assertTrue(responses.isEmpty()); + + verify(personRepository, times(1)).findAll(); + } + } +} + diff --git a/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java new file mode 100644 index 000000000000..751f1445d73f --- /dev/null +++ b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java @@ -0,0 +1,261 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.application.usecase; + +import com.iluwatar.onion.application.dto.SavePersonCommand; +import com.iluwatar.onion.domain.exception.DomainException; +import com.iluwatar.onion.domain.model.Category; +import com.iluwatar.onion.domain.model.Person; +import com.iluwatar.onion.domain.repository.PersonRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class SavePersonUseCaseTest { + + @Mock + private PersonRepository personRepository; + + @Captor + private ArgumentCaptor personCaptor; + + private SavePersonUseCase savePersonUseCase; + + @BeforeEach + void setUp() { + savePersonUseCase = new SavePersonUseCase(personRepository); + } + + @Nested + @DisplayName("Execute - Happy Path") + class ExecuteHappyPath { + + @Test + @DisplayName("Should save person and return PersonResponse") + void shouldSavePersonAndReturnResponse() { + // Arrange + var command = new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + "123 Main St", + 1L, + "Professional" + ); + + var category = new Category(1L, "Professional"); + var savedPerson = new Person( + 100L, // ID generated by database + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + category + ); + + when(personRepository.save(any(Person.class))).thenReturn(savedPerson); + + // Act + var response = savePersonUseCase.execute(command); + + // Assert + assertNotNull(response); + assertEquals(100L, response.id()); + assertEquals("John", response.firstName()); + assertEquals("Doe", response.lastName()); + assertEquals(25, response.age()); + assertEquals("+1234567890", response.phoneNumber()); + assertEquals("john.doe@example.com", response.email()); + assertEquals(1L, response.categoryId()); + assertEquals("Professional", response.categoryType()); + + // Verify repository interaction + verify(personRepository, times(1)).save(any(Person.class)); + } + + @Test + @DisplayName("Should pass correct Person object to repository") + void shouldPassCorrectPersonToRepository() { + // Arrange + var command = new SavePersonCommand( + "Jane", + "Smith", + 30, + "+9876543210", + "jane.smith@example.com", + "456 Oak Ave", + 2L, + "Personal" + ); + + var category = new Category(2L, "Personal"); + var savedPerson = new Person( + 200L, + "Jane", + "Smith", + 30, + "+9876543210", + "jane.smith@example.com", + category + ); + + when(personRepository.save(any(Person.class))).thenReturn(savedPerson); + + // Act + savePersonUseCase.execute(command); + + // Assert - Verify what was passed to repository + verify(personRepository).save(personCaptor.capture()); + var capturedPerson = personCaptor.getValue(); + + assertNull(capturedPerson.getId()); // New person should have null ID + assertEquals("Jane", capturedPerson.getFirstName()); + assertEquals("Smith", capturedPerson.getLastName()); + assertEquals(30, capturedPerson.getAge()); + assertEquals("+9876543210", capturedPerson.getPhoneNumber()); + assertEquals("jane.smith@example.com", capturedPerson.getEmail()); + assertEquals("Personal", capturedPerson.getCategory().getType()); + } + } + + @Nested + @DisplayName("Execute - Validation Failures") + class ExecuteValidationFailures { + + @Test + @DisplayName("Should throw DomainException when age is less than 18") + void shouldThrowExceptionWhenAgeIsInvalid() { + // Arrange + var command = new SavePersonCommand( + "Young", + "Person", + 17, // Invalid age + "+1234567890", + "young@example.com", + "123 Main St", + 1L, + "Student" + ); + + // Act & Assert + var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Age cannot be less than 18")); + + // Verify repository was never called + verify(personRepository, never()).save(any(Person.class)); + } + + @Test + @DisplayName("Should throw DomainException when email is empty") + void shouldThrowExceptionWhenEmailIsEmpty() { + // Arrange + var command = new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "", // Empty email + "123 Main St", + 1L, + "Professional" + ); + + // Act & Assert + var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Email cannot be null or empty")); + + verify(personRepository, never()).save(any(Person.class)); + } + + @Test + @DisplayName("Should throw DomainException when category type is invalid") + void shouldThrowExceptionWhenCategoryTypeIsInvalid() { + // Arrange + var command = new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john@example.com", + "123 Main St", + 1L, + "" // Empty category type + ); + + // Act & Assert + var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Type is null or empty")); + + verify(personRepository, never()).save(any(Person.class)); + } + } + + @Nested + @DisplayName("Execute - Repository Exceptions") + class ExecuteRepositoryExceptions { + + @Test + @DisplayName("Should propagate exception when repository fails") + void shouldPropagateExceptionWhenRepositoryFails() { + // Arrange + var command = new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + "123 Main St", + 1L, + "Professional" + ); + + when(personRepository.save(any(Person.class))) + .thenThrow(new RuntimeException("Database connection failed")); + + // Act & Assert + var exception = assertThrows(RuntimeException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Database connection failed")); + + verify(personRepository, times(1)).save(any(Person.class)); + } + } +} + diff --git a/onion-architecture/domain/pom.xml b/onion-architecture/domain/pom.xml new file mode 100644 index 000000000000..d1efc50def12 --- /dev/null +++ b/onion-architecture/domain/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + com.iluwatar + onion-architecture + 1.26.0-SNAPSHOT + + domain + Domain + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java new file mode 100644 index 000000000000..af7da5647abc --- /dev/null +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java @@ -0,0 +1,34 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.domain.exception; + +public class DomainException extends RuntimeException { + + public DomainException(String message) { + super(message); + } +} diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java new file mode 100644 index 000000000000..60adc5b5e1f6 --- /dev/null +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java @@ -0,0 +1,50 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.domain.model; + +import com.iluwatar.onion.domain.exception.DomainException; + +public class Category { + + private final Long id; + private final String type; + public Category(Long id, String type) { + if (type == null || type.isEmpty()) { + throw new DomainException("Type is null or empty. Category type is required."); + } + this.id = id; + this.type = type; + } + + public Long getId() { + return id; + } + + public String getType() { + return type; + } +} diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java new file mode 100644 index 000000000000..f0608c1f9716 --- /dev/null +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java @@ -0,0 +1,114 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.domain.model; + +import com.iluwatar.onion.domain.exception.DomainException; + +public class Person { + + private final Long id; + private final String firstName; + private final String lastName; + private final int age; + private final String phoneNumber; + private final String email; + private final Category category; + + public Person(Long id, String firstName, String lastName, int age, String phoneNumber, String email, Category category) { + validateNames(firstName, lastName); + validateAge(age); + validatePhone(phoneNumber); + validateEmail(email); + validateCategory(category); + + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.phoneNumber = phoneNumber; + this.email = email; + this.category = category; + } + + private void validateNames(String firstName, String lastName) { + if (firstName == null || lastName == null) { + throw new DomainException("First name and last name cannot be null."); + } + } + + private void validateAge(int age) { + if (age < 18) { + throw new DomainException("Age cannot be less than 18."); + } + } + + private void validatePhone(String phone) { + if (phone == null || phone.isEmpty()) { + throw new DomainException("Phone number cannot be null or empty."); + } + } + + private void validateEmail(String email) { + if (email == null || email.isEmpty()) { + throw new DomainException("Email cannot be null or empty."); + } + } + + private void validateCategory(Category category) { + if (category == null || category.getType().isEmpty()) { + throw new DomainException("Category cannot be null or empty."); + } + } + + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public int getAge() { + return age; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public String getEmail() { + return email; + } + + public Category getCategory() { + return category; + } +} diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java new file mode 100644 index 000000000000..e755f4a76438 --- /dev/null +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java @@ -0,0 +1,41 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.domain.repository; + +import com.iluwatar.onion.domain.model.Person; + +import java.util.List; +import java.util.Optional; + +public interface PersonRepository { + Optional findById(Long id); + Optional findByFirstName(String firstName); + Optional findByLastName(String lastName); + Optional> findAll(); + Person save(Person person); + boolean deleteById(Long id); +} diff --git a/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java new file mode 100644 index 000000000000..74622a5b6bb3 --- /dev/null +++ b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java @@ -0,0 +1,76 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.domain.model; + +import com.iluwatar.onion.domain.exception.DomainException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CategoryTest { + + @Test + @DisplayName("Should create category with valid data") + void shouldCreateCategoryWithValidData() { + // Arrange & Act + var category = new Category(1L, "Professional"); + + // Assert + assertNotNull(category); + assertEquals(1L, category.getId()); + assertEquals("Professional", category.getType()); + } + + @Test + @DisplayName("Should create category with null id") + void shouldCreateCategoryWithNullId() { + // Arrange & Act + var category = new Category(null, "Personal"); + + // Assert + assertNull(category.getId()); + assertEquals("Personal", category.getType()); + } + + @Test + @DisplayName("Should throw exception when type is null") + void shouldThrowExceptionWhenTypeIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Category(1L, null)); + assertTrue(exception.getMessage().contains("Type is null or empty")); + } + + @Test + @DisplayName("Should throw exception when type is empty") + void shouldThrowExceptionWhenTypeIsEmpty() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Category(1L, "")); + assertTrue(exception.getMessage().contains("Type is null or empty")); + } +} + diff --git a/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java new file mode 100644 index 000000000000..b4baa03314e1 --- /dev/null +++ b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java @@ -0,0 +1,221 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.domain.model; + +import com.iluwatar.onion.domain.exception.DomainException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PersonTest { + + private final Category validCategory = new Category(1L, "Professional"); + + @Nested + @DisplayName("Person Creation - Valid Cases") + class ValidPersonCreation { + + @Test + @DisplayName("Should create person with valid data") + void shouldCreatePersonWithValidData() { + // Arrange & Act + var person = new Person( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + validCategory + ); + + // Assert + assertNotNull(person); + assertEquals(1L, person.getId()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(25, person.getAge()); + assertEquals("+1234567890", person.getPhoneNumber()); + assertEquals("john.doe@example.com", person.getEmail()); + assertEquals(validCategory, person.getCategory()); + } + + @Test + @DisplayName("Should create person with minimum valid age (18)") + void shouldCreatePersonWithMinimumValidAge() { + // Arrange & Act + var person = new Person( + null, + "Jane", + "Smith", + 18, // minimum valid age + "+9876543210", + "jane@example.com", + validCategory + ); + + // Assert + assertEquals(18, person.getAge()); + } + } + + @Nested + @DisplayName("Person Creation - Invalid Cases") + class InvalidPersonCreation { + + @Test + @DisplayName("Should throw exception when first name is null") + void shouldThrowExceptionWhenFirstNameIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + null, + "Doe", + 25, + "+1234567890", + "john@example.com", + validCategory + )); + assertTrue(exception.getMessage().contains("First name and last name cannot be null")); + } + + @Test + @DisplayName("Should throw exception when last name is null") + void shouldThrowExceptionWhenLastNameIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + null, + 25, + "+1234567890", + "john@example.com", + validCategory + )); + assertTrue(exception.getMessage().contains("First name and last name cannot be null")); + } + + @Test + @DisplayName("Should throw exception when age is less than 18") + void shouldThrowExceptionWhenAgeIsLessThan18() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + "Doe", + 17, + "+1234567890", + "john@example.com", + validCategory + )); + assertTrue(exception.getMessage().contains("Age cannot be less than 18")); + } + + @Test + @DisplayName("Should throw exception when phone number is null") + void shouldThrowExceptionWhenPhoneIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + "Doe", + 25, + null, + "john@example.com", + validCategory + )); + assertTrue(exception.getMessage().contains("Phone number cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when phone number is empty") + void shouldThrowExceptionWhenPhoneIsEmpty() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + "Doe", + 25, + "", + "john@example.com", + validCategory + )); + assertTrue(exception.getMessage().contains("Phone number cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when email is null") + void shouldThrowExceptionWhenEmailIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + "Doe", + 25, + "+1234567890", + null, + validCategory + )); + assertTrue(exception.getMessage().contains("Email cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when email is empty") + void shouldThrowExceptionWhenEmailIsEmpty() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "", + validCategory + )); + assertTrue(exception.getMessage().contains("Email cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when category is null") + void shouldThrowExceptionWhenCategoryIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Person( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john@example.com", + null + )); + assertTrue(exception.getMessage().contains("Category cannot be null or empty")); + } + } +} + diff --git a/onion-architecture/etc/onion-architecture.png b/onion-architecture/etc/onion-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..82f34e3d7fd18dee5bc526226f74c1a633d5fe3e GIT binary patch literal 258593 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�a|MrvloacCFYpZ<1dwz#)`?93 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ƙ@3WcVA6#V+HQr zn>_!B;j$j;o<_vSWJ8X47|J}tTeruzg3eu33BSvCUbie!>VkdPQWiFtN)$eLL+@D7 z(RTw$syJP5wn6Gq5|OlduGmAyt@5dsPL2sd_D40XFXpJL!^CS7o$?5*H2ef0Kg(^b zUI8k~pj<()slf*$$wHRJ{jeV#+pw(4_6+`o4eLK-=l8w)_n&6UgTex;^792Y$UoU2 z6&Ve@p$QSxY(AmLQ9(etri!{CkCj~gy|1k5&}XyZR=vlbQ$Zv(Y0!WfCS>uWzUAxA z;nc2`t#Ns^1#7t($--pEtXK${4o@UY&xIP7RM%=?Si6&&{eO>6YH>&6J>KjkjQtp6 zSKV1+K}$r<8nCvMM9uPrd#;_y(0`ozG9IYJn5Gl_^+<=mDRPN=1kd-5Sh_V*p-BhT zH%O8c@UxWNiXu%768Z0dSnMxks{zWr_s<)dJBHLXLBEM0F95TiUO0`V1DkM`Z}t7q zC8tx>#$unf|2WKInE1jF-R@c|*yy;K%UPT9l$UZDV`s&@47~#`q@;_|5}jvpadI-M z9NoKfJ+vkx2WXmT=JPw- zaQ8Y>hlFR;+z;rhC8CHhU3?h!tS64?S+Y!R=6(QH5q|w)$w?4tJ_A$&1e-)~dS8@A zcuUUwbbldRI4Bl$4V0gV;P2CU;(GA{aRmzFHr{Su)yLG*L0f~^^8NdK(1k-7z|TSv z)cjGa2vU$MA!g-jEU}=7x*GdCnI}h^3Zh4p0lMXu8@(&wr&MA7n<+xPQ9v&0u!+}! zCN06v!NGuy9Q3P|qsgC|(gHU`FkK%&+0#brjx)YcOd1o||M&zVjvPHt#`GC1=BaZXf?FM_2-CJ8nG!dG7PJ7KFK03s%!+8~aKs01UhazmbyudmDH}Ps zAG-C|IW%K5qn#93spSl#%MA(>D;{LnDG*^?Np2xq2e-?6;aCLlw(TnGw!#bmR)L8>uINJY}1=9Mk_NR(uz z9gG`AQGiGe3SMf)vT8Xm7Jjziew;2hq9FAY0CN<7+agA~qz7__x3khrQ5SCaOZw>& zDC=jXqzMusf&+$iySZ#J#n*q++#eAI0~lS(x_`Pc z;FxdZjL!@162kXA(NQw*;My1##Mr9@lC3Ut;fGMW3mZCS71aM$BqdbV zFK3Fus)6Wnfb38V7Zc9p0!kf(4Q-i;>SWoE%&=v8Y?S}#gO^bqhJvX*Y>=L3-|w)T z*GvzQOg}NHR;IQz-LuB&2pt$;R4zw63`GoTN7!qLnk|=|K)qFDAvt@RyQrW_6;StZIK@o$|0L3fwnI?VeR6Bqbo2C8JyPYw#wuU~S>;yL>QBcPx?3Q%B72+2S7AW+I z0x4k+wq6dz`FzvY3ci{^2VtncH0ffiRLU4J8Y8hgCn^kSe$Wo6VKCvmc^}mVuh_a| zT?IvrVCqVUE9`~Hmn&tY*}mXEN+Iz9k3$}5;Gy53qf0(Gy)kDiNQ;Qm&!Gu1;-P!O zk(mGr3^gcbXkv@sf-@k~J>T zq1U6dQx_@)G)k(NnL8N!+r?P|&5I9YZL2>|ttE__m>+~S%FErj%)2iQ%=$&UF3Kjh znP?995u=lT7>o#R_qtQr&2I)znvV0FP?;~fO3tiM*p6?Qe#g+21f zlXkjNsP7okY+77pBb{stD)J@4GobP`g0f;PkwQAwCw0!47lEkqX6zM@;*q&*W@+UT z2DMm>0VBKM?)(Ep#-tp?OeSX_aS?o?XD`nrA55EE$-OzSf3L9cW=kszzbHt6?>;vd15 zpX|ma+2RV4w3lv4;(-k1SjC9tJqZ<`9VBQUg6Il?=uWU9L0#**O}5crd1b;WVBt#B z`>Hogod|G5FnIU50M(A<&a>IA^#jJ<%2!?y3mGi_0LK2|5>g;Kv_|K`UJe7(4j}y%BqiLe> zpr#A99Pf{bs+k^qnGGs&$Jvp@TEg+lD5Om{j26_-+wg~U8#p`%udr$Zhmf3@CqB2> z(AQbo*LfWwL`j=6+`reJ>T)$R{3%Yc7FKo|AVf%;g_Ec(474d0O;{lR#fTT+j)zFceDkkp}{*8Z)r z6@k^TjWVcx%=D2@H`vJ^GcS5)fS8g~EefQiG5!Q!`uw$6Porw!>JMHm91&o#*y(Xh;3bE3b}RVtz!V}b-J`SY{UgE|Rgu?(Hme;zDEk1wx# z#u(z?JK|LqoKwo!{Ly^+)n&%h+Mk?vlX-7h|~z2vyF3Z zuu9xNV_cW~|5>MeR(bN$Z~5^BcH2|LS8B1UyfPEh0K*-SGjw8AYkw;ThU5xtra}EM zai&}P%46-kDbFb3?`hi`PKrtyO{_B(pLa78>pj@BMOM}mkSn@z1ZWN;awGdYA|6C6 zzclTN2grxCKU5dclyC8cYbX{*0T-|GMQL0Z53i-;Sg#M17cSEDB7}y$YI{_;hu13-ik_i#ZWI!UQ1du?Lo^_5s6*T zpk%wTvSTiFrPd`UlX{2sOGEF}S+z|cx#iu-)aVvPdOVE4Pw0`pvNw9V#NGMM(->Nw zn8i!=qBHCaZRtmgB{^l)NdotBjEGp$WNlo(zpYc(If3s|FL@g(AsnaAbG&%M(hYBB z?dS#LMko%rak;8#nJ<@`&9g2p?Bfqfrdx0wPKw%HN)!L~-Bd2sK=L+MyAHl6%QlqQ zW8Vu|-tnlsyY4T(ToRAs4n_8y1mP=~e;O3gg|&9vSpv1S-^JS%P;n*@01*@UKPx+O z+9>B+mhYColR><$obdJZVJjS1a!4?`uf|(Rp~Ho2JJnSwqo_vo{9XexGP;lDl~1|p z=V@tl&~!FSZGHlV;X%Cu=L!==rM;YF4DEw44QaFO>*w$u3mVY}Ip(G&Ttaz`) z);878)h7~6+lpPT1o9PeHgDVlC9;3^js9>6i}2Jh3RD!=&_FJ&K8gQ6jMTdO=0o1R z7Ky3-&d($H|hkt9F8zGqp?XJm!Fqcop-+5|B%1g)apoS8Fw(Y6l;lN7_%Y6^rnZo~va zX3VZ2oRq-K^IX* z8mtT@WgY}oMgOZB#38@)*Vhyb%s`uz)v6F<`j9cJG{v*QQ*sr`^6fG!YMI^%F7=9p zk#6Xp)VJO|8pE4|hsowkyw$aJm!ScL6F0}S^sA`z{N>YPx2EWvQz#XA zVu=lssrQ6$E!GtWmW>2rsDyWR#s+jpqTl{rKjSxfO@liXVf1ZssJ-!}aAl5wVBDkY z=CgU?KtQrClSw?%L-M{MD07k%eg}#ftSpMan>tE`>8pDb92+OWad=RLZ!CNQL4FUy z{|%}rNW}|6O#E< zZ_jX`PS4SYlsieS&0Dh1@Aq~kPVd8sdd$D`jKWi|vzV(Dn|b$Zy|QU!Nh#yUd}wIB za?IjnjZ%2Rg@U`+N>1e`ycel?|CW4l{w*SBbo>r7N`#r->`bPdRu2; zBUgO7TevYz_rV+M^YRtWauuU_;_7*~3nn5L*7+;7(umh0Jg4BZY6~ZT2D~%TDMitr`<87|qqGgdYi)v7O z4JU&5^zb+)>ZGS^J&lNq!&3?0Pdl#OT^JUWE7F zajB!uj|}xO(a7XhD1DFJh3H`&8$Do)F9(sb-{=C9+;#?`V!wU@jgtkF- zp=Q1}33J8ShFpy$hDTZ2GxREhgb4#~wpG+<;zAT1JUC(}$uRBzau8KtZk!d^|HIP1 zmpVX^CMFPdzD$U>W14OFqtk+Z@YX=pkQ{4nG7dcigovzj$5Fxh&AuOls!0NVs(1N& z3$S&Dh}qBZeCZ(tpeAnI%-d@*1r=rHc0<~W$b0RN%<0sZ_BnT}SldSWr;||wGnZ3) zsIK=zI&!nJG+KL^HYbeTC0RM?V=g$<=*Ouqpcbx(xa+YYe!ML0M(aVr7%Ao^(&k)E zk8T+SI8Wu}_v*BlfZPY4GI7I;Mpm^5O3nVn;NJHpLuXke#p&E~t3@FO_pauE2u7Cq zJee9~2XE=)kbwOBVaB7881QOu}%|$yU`VovvZn>GHKg-ZMw`$h%3% zxRKnJlVniDe<3@8=aw+~bfHyt(jPcTv>8MgQ<0XlTt;vV92}k8?y=Qu#n>M}7j?=9Lzs1Y%^{4v#!!!=W~(iwivZRCI)%aC#lv5J z{y}6s_D|>EKek6b(nsUVp8WiC76rQk_iO95&>Q*pYg1FQ%k8)7#%V6nMR z!3-NtB}-4yVCI}AU@u6G@chlTtt%61N0ONXd$XJy5{MS8wD$}gsLh?!bMe7f(2zi@ z%w6*p9<9!NB>Z|UgU5{0il&Wu4Jlce<@u1 zh1KBd12dcI+DpB^xB`S<3gDh4{y&39BKkqW*o(v8bOX8yf#Akv(KhDM! zN_bT(Y-ajBdtLUoQN|Ia-`?u1J!03$=+mpGhjluxcFfB>_-#_Oz}`l-xZYGNNYG?a zK-Ly^IrvMjo*La0OAp_)1YK4fp*>~IH|oQ)i7kxsNOg{3DS{4HksqX4j5yS`I~r=W zq$+rqBkhVhj|N|y3k`DSSZ z(d0Lzj(yl`2^Y*4lGp6aSJfE80Z{gS0p6@PWMIT^`^eO2{Z!Hmau@6=?|S0uTt(ns`7bdI!Bi+M^n1b%T0XD?B3?`JAO3%6~*w8=(Y(f z>dD!aW$Rd6?Le1c!BUQ^j!DOf)KAKO*bRojKtd zWY(S0*;P~6&F{}oa=3PY5jYa})Xhu|{(zybak9VX#{a1`SKPUxWqeleNL%JM-TmQT z7_=SJBpZXOSjo4l6h%|x^iL1u?q6SxSK42Xxc-Q|vDs9eamMFMM|`jp-;L;!yraFW zw;XqrGIrI*CLP7e1IDIu3j5oRl<{qfeh7NtlBW$SCmIgB=Lw}elA5Kl54}h4LU57p z_!aw{R^l{`-(AX_%`-~6z%&hRY@!9+7$;2}qiF~!ntYW#zEqqO7&25aM4G$)(62}t2!NdWBr?I zq1rZ!GfQ^g3iYm;Z8~|o<%wUnDoDUA{@8c5ldb19oxfEd-Df|VHPbBsvK^A{?IP;yL<5^Nn zyS(pyQ3isIUl{dXtcW^{nqyM&n*ry47ei+O!#b9Bs>7Bql1oYb9fawKBXF9wZDoTL zewG&6Z|Gm_$?RD?rBfMXhZG9|@flSP&5JnAtvA6(eIksrO&k=JwW^e@ePc+Pg00^D zv1YaWh_$tI^eY`QuZZL^2MGnyi}UIhl5`8)tLHH8-r6p%s}lyxhetX%K8VMda5O5dv^Nz+lPhY=HVPfo*g{* z8PTuKityjF$S<01)0YzB5TuWk;2HGk71`DNrj#LF&q=n%&TL+GB`BF@XZ$gaPS(a| zY!u^+yH$!nKwFgCjMN!wfcvMe{<{;G2@A3?qJ?(ajebdSIRJA|2#gX)kP9X<*9p7|BDPG z>+_w_v91&KEroi-o}Bex7*6d(&$V_HL%bjy8whJ~fYiQX61Sp2M?v?>3pFADJ^5>gWc(zMiAntF;?0 z!QpNF9P?%gU~%5O#|Of8$LuP-{nyi(YU<+xHwob)5(0k{7o~iGG0z{)Csg(Wvq~=Z z5?=Uo2^;diEvlHD)JS&jzHpV$Yjwxg-bcp6*nD5JbmOUew*$}QrPTL%eMrgXvpdu$ zx8t12#$>;w$+tW28KT*?=r4%BBY^jBfj8WTZe=`6h-~lY)Bo5{hjkTU9A6pbkJ@!?FULB;`){P+v8Yj~=-+3E zrbQ6n{KnV|{Cm^ng!SX{hH!AK2qS5(MIN>yf==t2tG;g6Wtn|2u9%frO>_sk9(X>TLn^aEVvGK7Bq5s#)$ecjjxN`iEpH*cZf37WTmgWNpKrNlNXuBg&nb!LfhGh3L|2pJaP2CKm$o#zX1oqltUp^TO#VsvF$Juay53T6KFYFS@YAdh3Zq zrW)!=jC`D;(ctQ1?E;0!*5`rEwJBmnYkqTWk)L>(6-oIHSX>A$oojhz^0u+|n54g0 zc1R)#>_T6to^r-{*er_6;-r@u&u2Y)sfsNggbQ>r;Lu2dai+cbtP7Ftw;xq0u|G}f zrBhB(F(u-C%XdYS>wh_qOKH~7!}IESULGo4z;V{}4wM(y&Wqfp|ws&0}3EOIECe z3u{tX$_KliSLv@ls1?M8eXt&+{5?SFG&O7+Brv*BqUJDv`hg3yl z^I}+}M~xx}r?~5Deq9*UNGX3xS76;OmSUO9wP+hH@9Fr1TIK}d&^R-D@bvWihbaab z;5eQjqgF6X{Ss7W(6MP#d6al#^$1;7ZHX!8+tKF3K`fV7RWd|&kTSwk^to%vdHp*6 zG`o=Z@gcr-)D#Xh+Zrs=dq^*9S0(X}b$$tY5=E&a}_k=b~u!q>A4 z*u@h54bU-m!*5YJ_a5x%pUALjLk{Ix7rmg?NO8Q3uUZpN3+s%P+|`gKb_xC(C_1)} ztSZ%{lR?`|71G>U#?Z{?DAEliVw$DH_pOrPC=9?bc!hs|Se{Lzc|KBinmf)bTb3SZD;31lvv-^6_ZLSY=V1V(o;6tEu zB#gcD;?F%F>JFY#VP#BoYa_?SzEUl;-Qc^GSAyEPm(h~DznxCP$-gfYD=_JwMd}Cl zx_73_w(Y9lLeqwE3?4T%SnRjU6i}6lIC3OLOq4#KVOr3nlBL%!n+Fihr8x_JAT3i)M4x$W{KJI)h{1hgbfr2KSI0zf|2NT`>fgv z!+7a)#5=()lUmRpu^@!T1<>J+QfZ?GFDK@doliuzMH(z)Ka%P=ZKRH4*@Y7HilkTP z_}F~JktudLW_gYQkL1-0!s##7?tK5)opbY_1tOv~jL+SoMR=-Q`=W(x|g4KZ&7 zP@U*5V@0+5*p0%1hS-}ylHC+1)fwJ;k+{Bxlz_KQ{}X}aEf2eD2gyX6n?*E^&L0kU z5KSed+2wW0^hfD*h)*Zs7|{g+F2Sm@s<|yC>;Kslyt3p>is3C!vt*l}Sfa^GksoXu zQPV@Q37OI-giikrg&61P3!KR`A!R8kPB2!T-3D_$Wz;U*P9JHTxOf^(+eBeWf{NW_ zio!2fPPUwGJ4(V6p2c%*M4ojCemr?H*3A%`l+pMdr?pi|LoyN3R4#x-y@f7}RjL|- zG7Jx?^trp5d(Dz@i0?NtV>N29=?Hhf=66rB_ovx)oD6z#;)6$@bw$!sjO65s4_IMB z(0Ax(XwHQ<*HZ0_b3B~*j>8is(9)z716>!RK7dL5_lw(#gJHGdm+7PDrIhyrE+BM_ z0HL#*bQqotdea*BZ~#MmxRta+la=u@{;&6m6Koc)dP5lA8h5Qg40@?(rj(;1KQT{? zPf)fNLM$37(A<|N4uu%u;y$`@L%TfZf=to%dX0)_JI~~^Fu4T#m+~@SD0+o``m2tO zj_m@ZrZTiBTltV5Q4Ef4AhbaM9u7SZ!=uEkAB|rz!b*AJP0(q#C?t6nGw;|pEn(C; z;PSXYzCUk$T0&^UESmfXS9{qIe*LRLuK3E2TBg_zXiaTauKXcdJH5!x+~s%;uMPEZ zOJ4}Ih#cMh(aFxNu5JDX1NuuxI;v(GC<||_uj7`{NvjjC8@k~d zjnS886q&gq1es-Dp8`zf7!Ngjb@Muu{olREZ)JM7-M~Fm0F{q2ekJy5cB_NMg{W$% z<{Ud^f)gjIM5;2GMuy|%O%4kZQ$xdM9X4n>VpQtl;cQK!MEkr-@m^+cPMf~KBWo`S z^Do_^?r_5pkB&QOdWWm$u+lRIO|v5qLN|bimN&%|yT5WTdblAFo-wG?5+#m!2_09i zUw=YN^k1@@g-RMSEB{s0&Cg?grF=*F2|@Ge7yS3l>?mtW?qci*UPq4`cNo8dSm)i) z(^BTXoMu#HFg9j|$*$Ga_hJkcko`M5M@KX-8HofW>!Rl}DBDm5z7 zp5(0<5PxHwc)B=ebc#E&)tgW=sbYtxaltM|0QF^ib)Q91QVva`O&c46;s5HK65v0jhzb zcQ{%ZIaPidXig*Nqwr;=#3&@{df%Z7r@W`tIQP zqxhKvPJOb#Xz;f&7S3RUB5ioJoXf7@Mav7;4w;N!P7&)*GAik%&&;xy}pcz`>;V=E%=nhV&c)eprg1g4z$~-E@K`qtUBa za}#QVv+a$y0LQzd_3CxB3ZZJ~6J=L01fRc&7aP|Fr@YFQEV6yG@}Iw}M5!#Csx)Vx z^*^OLxxDJiyyn8{5@zRNn$rC>)4v!;|ARvdv*&O!@^le0p0Gq#1Ha; zCtjHQ6?(-tq+Gxyg8KK0 zVzgv6!=7boA&sV{CJFv&q~d~8&5B0&Qa6@K&XUWDnat2|zEViSz#$gI9uXSR`Zv>= zn3U|;+pN9_U8NG4`ZR807p9;Kz~7-r`}Lmi70h(?%XQM&Faf_`(JB|7#XK2^fmrhF zslR?>7h;fdHA4>e$0-Jz4k2PsxryI~;&|n#%yC`0*X}a>MlcA;rN2;kg zG%y$GYKU05+8B)L*rN^shbwinrgeF<2BqJ5BdCu^$_FC0FUOh7s!jEB#qcDm!bX%E zB5xmf)A4JQM_QN<`p%2E24QEC|)y_x8feuiX# z3W0fv{6&5c6PEzXGAn;49~eRZV&2iSmm|K6C0pXNZzD9wDpz~T5<6;90nMma2~EL2 zOO1>s@0ukG;0CKZXD2eyy6$aA7`@@2*gSO~UQv@j8rVOtD4kg5z+(<2G&#-e#^dv( zXJ98DLLQI3a%YD|B$rL<#lVIcNE`VIB=aMOKUHa_I8r*jYJOM4OS1l5trGg(dybox zx6hzmYY)Hu`SpGF`X3o!x;OMPt&m@C|Ib!L;s-yVmIIE%JcQ6GXkJ2|{lIJ;7p4DN z$TS%HWVE<1oY~&yn+m6Hgs;T{qTd`003us-4;`mlrKGc`{|& zn1AYlh5CaE+AjmN5 za4k*NeU|+|UhM}x_D;P6%;2-4GT}*-aH*F>$|Lu{CvHOuG+aa2+l1{O5~hlzIqlv8 zvl9;R^d(!y4>v2JK-&{Dpu%IGQhQzB^+T$bXXQ8PH>sHl)7$SGY=2|@Ii6H_=Syvt z*kz08w#eYoL#qM9A<5xNUx#U^m66$b0}Z#Sm^7a<4ZvM-cG%TKN+mD?Mvp*r{^8@E zmvmdEOlPfEJ zAh@^`Z54~s8w{PA|HbsZL9|nP)<`YxECYPaxKk3yg*4`5~3)O;b1rNZUt)_;4t0{ z5G`Ns8n8@3fCrBQRDi+{RmP3oyEi(=G0_G8SM}EhaDj|}7g+X9dUft`-ldz6V_XXQ z@Kmg8z-7*;YtZ$@(b@0h>H%rWcBL;4YLoGs#CJK_nnKvtijI2 z4CA$Uo~9V&Z>P%P)~cn;vE0ep>G`rKBM+g{&s-+1fJeRa&Zx-mzm{3rr%MdjB~nhc z6cm4@g@^F^e|<)em2KHfeCfUrQAk;&yhSZj!=;k?ze0+*07m(mdp)Vwu^IVxKE%BB zJr%WbA8T6-dRyLEB~c4cC6?lzya0jnZAgnPaM9$1#UGM@V$mX5-&@?z;JR+VUK9V? zja7PdA09w&$?Wk-FHC;XAS_T>jTy7)3dfM|y7xq~oI_Q3jzGu;>p;BYd%x8^f1vW{ z4L1Ai3HUCSwOxIQC;r z=%W~?g&Cvw_cMGsADM6uon!8xTn2;gyz%OxCW{!{)xk|MIl3uy^+lJ6wRuOfeluTG z&afKm_xwjBcsT%@ZhP^N)zEP6PSV zSN_%^iwoFq+->4Qn#|lM9Mv!3^eNFn9w!v|CUHo{7u^Cw{`Q!qou^u0Y=oI zt5&w*_{T$}+TjsOJjU%D{?4Md=gwC%Wy!63FNJciXIT#WRJy$6rn_ zd2RcSpOzJc1R8Lh?u!j-s_izEZZzA{!EMYL?$Z9@0xj9su5*`NF=W#@Iw*NUWkk6! z;8Lxz@c!zibMAFd2$5l_wFkGV3-E`8>J%z#))>Z_^J0LP3W@q@*mWtQCLKtI*? z6+Epu7|U(4it5NLZ+{$yZ4|<$0A;!z#be1V@PezT=B^%XYkXe}r^kVZ5={S5QL)F1 zj$-gOX>bSZlJ6X*9(4&Ggeu%FqFgN!f_F^-Ro3BHd5a)Y`!_r~7digGqye8{=87f# z_Kfx6nsfXHN&Q7U)t%_<$06S1pILveC~rsme#hLrV|3b!KaL8u>$w9MZ)I-X_Rk>q z!PdP+?fQyQ#c3tlBZ>n!lRd{z>bacEb+v_K?A2)h5z$V?dD#hX_rs4h+JB2g$ z)S>zZf}`22Z?IJqotIVu%pA*X&L?kUeon6-`ZJX;5l>?C-!^O})J$Ph`S`WnD>)N8}J^@0;F z1Sa`Bv_ul-c~4VLojQhKFNXBC*D+oE6L|uU!ad1Iw3>AIy6Y-!82bPKv3|PvLHyX8 zCH*(tdi1johN|3bVXaC^CM6dbc;Y}Bz&nt4uNAfS#tkEU+P4(@*ZDuKdxt#!-KdkAe!}t$q@-3 zpQRyXb5Z(IT#-%s>uTA z5N+(ck9iyD;4(P4SjAfRyk*=2voN#v(Bp(s##ED^REwk9?Ih~H#W7KpA_WpVYBa4- zo~q1y=F$&Iw`4eknVwJ-hye@-Z*kaj1&FXq3u*_9RJq3+k>}XtAmeO!{vMlv{=FAj zYK1kHj(fKV^h;_uySUH-U|R6FnW7oZ9}@*Ys{{CdW|l04EZz=ZJ#j+dFS7$juDWMZ z@8s4sY`YGCT9f~^>-}^wjFfgGOMau~7q;lPe)EcmWy&S0FwX45gtk*T`7F=^%an^~ zT0(4QBZmT?Z#%i2dAq3lI*;YmTyY_Wap*~GH$nihLHf{}`pXE!`(yl6se8@JT;vn!#a6o$K) z3{LTiWOV@AkuDb#bP0A#&p3}ZQeu7hhD(Ipe1(E!=6ACJLg!)-U3tqQ)qg%vciS_- zado@zD}Sk{6{U@jKs-Bs?Xxs2PhP)|K-_T1p$X5i{2A**ZI}Slt1icGLWqKG0E8ts zancIAvch~cD^-m8aFUfV-|)n3{LVhiysVT*LM-TX0%v$`1#)w;Ba(0L1Q$pS5{4FuM4-l53BQJl}@K-TsdfcJB`_uBQzFZQ^E8jLb4P^3!@%1rdw#ex=LTsw>r zsO8Pp(rNw+eQi39ya$(H#}pm`00q6H10FU01E5~WTIA2F#taX$OcBIuk!4$g$R%eo znzA;txlF1>GX-yP*Lm`dLY~}@lu-*wM>W(U>Wzh8Z8H5+KL_x~ZvlHEZ9fOk=>`x> zWBQO(AySVK$+JLd3B)nBN1Ig(lZca=C2qflK*AKeurA_NpGa7kf^k}1<9iF!)6kJr z;I9$u{O*!g7}%1i_2rp;%CF3bWHr7#qxPG*ZAO0b zV}|+1XmXc`v;%eud3KJ*Kf8r^$3vD<%3A>R=mm(3kcNOsKvoNJC;T}svE7-v zm)<2>HzoY3{sNGzXmSNm4Ssk9C^sStuF5f}ie1D0=!DUgjPmI3fZuDERw_{U_;DIg zvh=XA_r5;8zbXDrf=YkFranu)J<^sHmf@%-;VW!ZNwMktSb(Az3qS~MM41`Bdjq_Y zGeZ=w)#6POA&JraFIu%RKh`4WauFIqrQ7a$ZHzVv2d$v04qE(W<009sJyzV?q&Wpe^R4W?VpCEK*kJHxQ}1Cz25#kpfO%dN8d>K}=KgC;bZ8116pcu5pMIP(=Kg>p z!Xr46`e02B;BwBan!cF~jf#|8P)s)!Wi}%W(Z@D`&^R!vA=B&$Zr&^1XNf#<5Ck5t zJrCACL3qFvuR+1;_$%`4vv(zJcD?N*mteC^fnSPP1?PLb+6N``h$c4mMPDhtPbHmQ z?Ro3cHSsol!36dm&gjyD2?7d#dG?n9;g5_2XXWxPKR)9K)RLoX|Hht${QNiiKRcZ8 zh&oM{5sL&NjFKeN)pD3&gZ@OYbja9|3d+l0s%6*`_i)oywY`AZ(8lj|i;iA~83pmpfJd27 z12??XMdK3Ds-`Bjp8c*XH}#W&I1juus`_Q*2kgL#BE(htKy&u*zUrWD!r_sQyPZSx zHE?!8vOGq>PpA3Gwa`fA!7!`_5#C8sEFFWTkuEIBaIRrlmo1ig&@ckq+{lKq~0J~#Y z3<2LR^mbRpELoQ&CJPL`>rcXVO5a&Q6fXEmQR5(Vtgg=+VBBF1Z*8LBPzGgZh}b{czA#N-Tu)21D1Cr*OcnG$aJp}xCoqkAzGZpu3vs?fta|1 zjiTC!MM)ZzHQxTm@3C0U`zrm^bL<3pJfjiOqQ^m{u#h|!onIG?N}W*9^UYGEE3(p& zr1hr11c>g&1`HwmPfvlpPQCkHMU5@vhU5|qzuwj|Thu9^+ zg;m$vaerSvHKqR3|^T&Q`dHXu->mt*V1- ze)jf&T4YnCX(1$s2*kw;35*e6iGbkVy5^JTmD#)Cu}%RM)(Vhj{9j?h7ziM*UD$cm zfN1IS@0qjbOXhK|)mmAqNx!yFea_!K`*D~#HK3wDq+1|1S25tddsIJ;x!d&_-K9umKtD_d()a*y)$s{P0#kptwyqkCf z{7pED+b$`p3A63fNrs8WGORZk{&(XfNKmuqKfGL@-n(#OpkC3`;qZAVbRyXJ!>slm zoOpq7OvlyVzC^H%UBXSrE;>ubovJC0sqZZI9BWnbV>!TN%-ERd|c+1P_qx-GEWA*_<2jU6zjBpF`2!Rt$9D0><_%mhOk}gca z3op=l4W`4@C~{%uijMZ_2?&CZuE8V=*R`Lg9RpiKVAx^rZ0XkKyw`gvKaJZx{TvV( z${wR}{&|*3+f%CX8ju*iLh6;uTnA`EXj?p2eE)I9*IAlz=ZBMCF9JvsrrF!Lc4H;W z2UFXyV8od}u4D{E7fA4Fc#&OMsbSTSTKYz&i-qdRzXLomY}BIo7gp}f+DM<2TGmKS z-&&k09Jg@ADb?kcoM?jG_|e{;m-}9r3AdzRcF-HBm(%V9-j9whj$rNReZY*~v;8ogJIVgpaGBH_SK8PddM&|*vgl8U+fyVI!ZQ;CE~J9OOV`7 zDl|E7g!Ul24EaF(yF!^Z3n2cnqR&sS|L_F!o}kIIw6t_NurIo04NdO|p`*}*wYG#- zjr;OI2-kwi%d7BZeIt^gh7>r`IwyG|*6u}1db-Qf%b@YD=D8GznR1vpRdh`20tuIa zR8}oqy^de_CQ%^ZQ@jP;k-N75u2kpGWxZkr01SL8IZ&}Z^-1AO<*4ZrxK95W%vpcd ze4b&V8K9RTzCwz~i{@IV3~)=gZE4E>%&d?qOjP4|Ww(&UTh8Gq+BZXG$j z42?loSBy`%VhkhLwIEYM5ZZddcLB&Lyf^I`JEVeK6JE!GD&Y&$H*f@gV)CLd3E?X! z{JJW5P>~n$vza)2bL$*IW+im@l)5OG$uC(I00Nd!^`A)bc!EYxj6z z21Zp9N7egveK`P|$Ok=W6LeBd=`=F3EF^pOCubKl@~Q+nTv2kTUEWWi(-`350hkDJ zl=f*ng8ov;GbgPACCJB~zm9iRu$M3V!t!6(0;|K0^C7xWJG`9O6Vdt>0O>bO0}YA+ z06W82L@ewKwt>WsJgGILb*-8FLDXx>S91uf^7oIsI*$b!4LnmVyUn3~p%P%uenwG_ zm46wVcXB3cEwfE;CVzbfwPkxY)O)>|hZPcf zPnWb9X(ff}aGmdu*0Vfc%X0KTv;J?PRsep!tNze!P+&PRcMm|&Huz!6!{^;Pj<9MX zH3p_AGpT@6Y-)cUNUBh-IMmYQMtD^C-~EKlP|l9#E4cyZEnHElpsQ*Gg20qhxbTc~ zC;phZ3(~&`|D7+xwtYOtGrSdQq2c~6&^IG1!9?wxWVu}$M+}p^rgrjE8YJ|EfJM{A z20W+>a;t5(^VMDztNpuXTyn@dU}<@lDY%iRtL1MgFdVFDSgr>PV2#-XyI@RLX?*Q( zi$7AzUPI8T(EP~>z-rS1%gW24I&VDtft_Xr;ab@K^4=L39iK81o^OMkDIsNjig-Bk9YPqB?x9Qd{37Rd8 zKNK7y=hrgQ4LT2I{s*CVS~=K5q($mpg+WhM&t1%1Wp5=%h`X%lPepmp$(rl+L9_4X zU1!g4&9C0H5sBl6Wzs(dGZj&}-jqN}754vXoNwfl=EPTe%xImtR?*!GWxOQ5|M! z}>A)NNbto+~SfkkD=TNU3-xz2HO`u){~0aS9}zP&Bw=s08e%Jwx# zK@{ymb>j@acd$mzxiJ4dGtz9Xi-f`Y9m$^A9~G&`8~i)COZVA5nrdxNYkK{Gwsl-H zhB_B*=+OIlbF5x2rirDs+pR$>M+XN#_c|&YLY(S3>w*rRdYk;{$BqKUL+G@KawsK# zC1Q0<@W7UGA>IK5_NFF?V0OGFc-P_Jd9a6n!0{eRjm$PUhCZNJ!6TMgkDZC!Wh%oc*3i4 zbjh_=%TJf`g8x^O{T{DFXx8Cc`(vVE*doSjI!~o20yRC+;8~yQ^$ni2pH4MM?TSS( zid93>ec;UER8HM=aFC4^r{y1=K?#fs}5`v}5)z=&U#t(f;H{)v?uq`!grpgsLH-v zdy6un3uC|gKhww;ISG%ZPA5pUxMkSNWNF4dF1{2hc`FNyqIWS?AMpQ8ZjTk9*_Fz;}M0QV4@FdM!9V!`dTJ?RfEa+j8tVp+AJ z*G|JL^#XV@Nb9d1jv}=ITl|06`s%Q%w=G&M5Caq`K~WLu5|9oBHf*}P1nHDUQbk2d zq(w>^1f)B*h=8~qp9IB5C10NPs_G;K|7OGCm_GUzi+Tar&3STMUM)8E1DNCLcqVX45juX6 zj;bTE^dY0EsH1t=$m4nU@qM+IFnkF|b;($So9KUz3M>d7y6I2}KD{g-mz*}z%r+0@X$g%BU}HH>^?DHQ@=1W!F7xVx}Tm!W%pcC zEvfYD-E-LpHEqVD{oj2k^cD-Iw`V+VYU4~5kj$nMIaMLTVPpP$9Fs*^FOm(B1WT)= z^vv*TP3gm_8DCZb0!p%<|J3>jj@^-{>^~Y^QQZG};2f-Jyw&EnsmiPn)pIM8(!)tL zd5FZc;yPzEY?^O9cD`hcnBcZv?a|GdS##NCwHedZ+i!i$`rRO}CK8^4(q={-K8}@z zJ`ev-pr61LC#kY z{&Dn(c4;ERm8heA`QJRGb+~Ypd$hM8gc=`(MTC`)m>MUFK92mJ(G!@7)N|jU)Q}BH zBMN)H{!*K6WvPqi`bUU5@jb(qt%%>d_R;*fe-Pe;>Jax@gFV}8#Z>vdYkPy(p7KBt zN|UJ&`5w=5so$8YV0pNey@3%k*Bf?o%B9#LIMit5PTyAjCnn6Q>V{w`YO5k#n)3p0=A)*7LD=CHXqHgWET=poji`M%a=~_z&kj zRC$@@d!r~rgXQ^3k(1a3A!>r%B2G!gRUwK+|zs=6@87*+)L<5j>vXBhhnTOEd9uz3ZW+eK<*J zZ5~QZi05JH4R#Y-9ltQ!aLdbm%M%ugj}-bt-0<8T$2p?%+*?l>Tbb#LiHR}gAUj%y z{dtVxlKA-e@MCFaKei5v4olq>xw2j+Uq*U;r@#^e*X9aCrVjX?y30HB21us3mqp&e zXBUR-Tr^boO8C~zLY3r#Nn>b& zm0;rs>wb7bDp5uXa?gwAJ#U5YY?aDPNnk7D21(an51iM$CrpvrC!-rv42O`SVa~*_ zYLi0~F2P=O2{cC^WKkB5np#>`>r`_0`E2Ih8QLXy{^{Uj;$Z-h3K3o3fB`9X*Z6Sr zO~2?^U1J1A5ZHk`4Y?GJ!Rb?=9UNwWO{vA$XxpDm4PubkF~IN%{(AHe@jp z7&J{lLqI&d%?mk$%h4~2R0wseo9;^kC}^sWj}9=ho`a>5ZjB?-_IR)Bm$kf5i-~?5 zlg4R9tLh*unC@R*2ATJB@hG^1;<#{z>3`bsS6` z@gJ<6+=E1>Xn(a4!nD%&-b?On?`^Qed!}$=5L>C%MF7)2LfkqsW1f{_X;JNMp)02Y znP*0^)#rjE8GeuGs-iMah`1{bw zepkn{?C%35s>sI2>Z+ENpLA?p!G_`dDAP%{E!Gmw~u;Ef7#xo;Y^E z0ZI%ClUHU5)dVcQ+Y?4{NW~|t<0OdZxPP>&orh;t5M%*>be?jz*bX-X-hpgCw%&{; z^AvF8sJRSxm0rvq4PAECq)HHG(p-)5?<(&2guA;`cps=%?5dA)`7j3JB_pV>ykda) z5akl${4LA;u*$N97<<>+(ddeCW$p-H0e6d(y8K3CGRBRu^d5^w=PUK}I?bhLtk+5J za`iy^PQ%cRkID9avqwSW9a=|NF6TkK^$nWWj5IQ;#6U#1oc<>$KUbj&@PQU_YR9yO zc#XzwujZCjB;+q73dy}rB?7oW`c8A*TWRW%u?)p3$r&0Z zR68d^Z4AyQA{A96zZ`!3&Go|IZy7%xYbNcR>(GaO=J+gb^|=HEQ*Q1GW3gK)bs=?T zR@TKooQcI`sUtIr%#VJSlnljvN`rRr%YXgmhd+S!zPNQVcLBY_FnF(i>nzVy4wkgy zv#s~jrG_cMT|O53r?Q9Cvjv}I?#N`U!_R4$HlxcRW%llniJUL{; zNXhXy2{yjEUgIi0?G!pk@0e=Z6NWO@86y8jBh~GRiYv@mD$Lw_0JZ80@<&t7z$oMD zSZU>l&IRtHQk46iP8HeSs@uxh+#zMoOuN-?ipVx&1x?0scV>=vefCctwCA@N_$>PD zfod&HC8~AsP$!?c`w1WnsYFwzegP+6(GbRa*XokR(&m;O_Ov_$!Oa&_)g!{2k|BoF zjBGnK+| ztVup*@E%&5x3ef;cN(?4W4F+*c^2C!i5aQIj1;=M&Ohb;FT;cZc)&Ex6;+2k(J;12 zWBYu$H}mSzC-_@AIB@3AY)1#-41AFZHSJ>AtvduAgwzfH~we9rMk4IxQ z;B3i%MK2B%c#kv<*oKaM8cGR8Qcap-!W8Ibg*(L^X&I(zy=Z5i#I5PLS>FI7zNb(~ zOhyhuo!wh3_E+PjnG4F67#r4|{bMJdaNSe;v1cP4t^^L@BFG#ru@XyzD|+_|{<_bH zN$$w@=@0hnII*F2=#P*2k#Sb0lrBnV_|IX;4q;&GHDc`1IqINY*Qh&g!>AFq&SxTh z?bN;~hoVli-UnJTVp_2XGni8VVr&d;e>4`3UP|ZWUI#@kP|^>1WAPqKkN+@a1ABZh z7Spqk7hrDTy{{nXHu57+O9jx`IaJGEy%v|a6|00|8R=A{jM#Or3Y43U5B(I{yHk;w z-C1C(19d!YXBt7A!zaxJ^S-U$TF(uh_UwvX2}D?OopQmc2nTgwl)SP7t^+0Ci$9$?Bd6uWKc{-7)7{6 z1zTDHn7cvft-B9GKH>s{+XcU>c+9;mG9g2GYGmr~4*7Qxqu|XCi1n_XJki^dWn&l_ z9|ZorQTmXE?23)5gy(y08Z~*S>iwk@?d|=ZO!$Ysu)fJGFzP%7ql@nSAJF5`_5S%I zppSrM2t_~VoN+=2E&-pjS@x#?U5^pDB8$GJ1P4GwA_S zPuUqOCtV;htRtT*?E^;gphX{H<5(v5O0lcy`Ukytd!IwulrXCLtATD|Dcl-jG zTaMGgy+F-%I2Z4-U~SpfUa`1f^g1kC0WH(jARgVrZMz&j3TcZe+Xd^-NWLM2XU)um z8_Oy(!8W15fq z^3Tzv7qQ02-jX)67r0N*z^vRUV)>n9Vf9f9@4sOtaQ2?0ZJ zf~*m)sLPCqtIilwNe1ppoaZ?ZttO89ipI5flp5HmC;m zW#93}5Xp#Yj9x*33K|13H$8zX3G#94iH7mZXEPOE9`?Xq{6vrM;GOQyR5+^rYRs;`^K;SX%;hAjbbdn_TE{d!baE< z`3iYddmWAkb%YU2!SBh|oF&b!k8UjdZu=9lk$S|Kq8arCPl@-eEygRJi+(AXTYX)( zc02~q?Q5EXYOvFnDM^*Y;d@?JU$qF5OpFY}6BXarFRv@ds}Au`+mGtPNRbICCuVX7 zo7Lif&4lW7;9#XTvjCsDlch=TK_BrYbaI{7+;;7|Z42k>o!w8Kt`6-g?Lh6@ z*lg6UOrwI2S>Lw16>8pVW`ot|0I{#{bw6^yRMGm&Hrfkyqvn+&jPcUXIno!HX|gmq zLH8?{>PxS1YQ%Ly$rh)L_gd*Dq!Ic&y{7lKx94`wQ{QI2^`4a=L)$$6BYbn+X6g|~ zhBW<4jjYF0m~`_Y{RlpWPfQg!567xXy(K@smFS)ne{Th!iLJCi9 z3_)JlK^-qa#Tq8TQ>IPkUk=isB-aRK1KDiV7>dAZ)E4g6Jby!8$J>Q23c}d%N`3gOq z)`gkrKND!73S+$ZY0Z*>??Rh~6I?KIU0}Siq_M4ta0N4~pViw9x4H{sXWkteye+q7 zHr-P@)MvoJymbE(WRFmsGanxPzZ#RB0|%mv4pY;j!66GqVyO(}{qH5Pd=WN_d<2ON z0|>ip)%98n7z-+NC>9E!rPixfa2F*%coV|@VXR#h*}zyfJfA?!cM(+Cs36U3wrIQ? z{8o|)y3Q}gy6Xpj_{qdOKI_HviBY5*GN<~{9-#m%Z^RobCQ-$!X?=5RZm;T>^EZUN zV&d);tnt-(RrKiJ8$MSuc2nc6^e)Hj(iS=xYj6B+O+3>S&fydHtH%?g6NaPRN@-my zt2kevl$SewGI#dX@Vmd&YsqUT0y71u+gY`s;qnnWqxt#$i^qBOQ>&#Sww?(;hpLYB z%FYTo-iv?V9<^YJw>(&NCv-$6?!sfvzlB)mp`Z?8aA-(NQ?tD4_i?}5nYXHA$H&JE z4dU-6k?*E{P&HD8Qi-!Qwip^yhe{iVz*|(*zEO8WC8(nesU<=TsY4~$*u~i-LL`hs zs5Z8@*KEiJ8ha4OVw)aX+FahP9uF=+Ffe@0A20zi{6@2VK?Bl_WFIw;7y>1O;)xIms%fHz+{z5HnA^uo! zZ)`JRcBZ)72b+O_w!Y}sQ{L~(`=iy1@1pW`&Ww?uP$suRz9VGV7vemYqRR<*q;D`~ zF^eiqh@Vcz&H`j6?@Dl1TO7^;YZpg7IJvP&S2VjFrx4TLJ^r0WsSma-Z#_!o z1!Z_sbq`Xpe=(h6fOr)4{?H$#ZBcQ?J2fTyOo#W0orpdslE~-g_Y9Zp$>}LOeBu4Q zJ*8DoNs>H3BoSwus>M&#jyWf@dpi0u3j-a1X4Iz5_*Z5AY8b2q>Fs<N`&kx}PwS#<)wW=f9+HRrXfG@5N5@C8 zq-y>#W|j13{JJ|CzxqEsGd2D7*`Y@Q?Em2PV$ zHb$2NM^|rKQ%^@emly3K7^^^^$wDd`TmI(T*t~?Nb*cW*ASuPoYXYBX#7wqa7APBoM0&&QHa(k}LxAFLcNO#fq(nFR zIXG5lcckAHf9*N?(I*fc>D>4UR!j^yMbfv0_V3552J`vuUB(^>7}@U=;YrmaxcK*j zFQ`(zy@vT#_#YsdzFv$Q4IGFyFRJI~t**3xn=*j;()X|7IPP3hI1-)ks!e)AA7^Sx z=M)Ro$sd)eQLz(4<#yys&Cy=E@ubn5$agRtO0EGZ!p*jy$iCJj~0YikV+ z;>$*|44q9)7fkrs$O7?vL`)Hh1{ux?iHaG@N=ooMUY}P@E@|kV`r$ltc8AUJ=I(~8 zi6T`R(cDVIYHs7K#z(r`_Xo=FFN`y4U`%`R$?t*dTDX%jd0O_dJ(G^d&VgdFQK19W)fjh2K<>(mJ>;2a=DyQ=xFyg z#MMO`vEtdbQ8a|eZN86tWrJuc4equl@v+IchCzhyazG^a1+Im8Zg8X5h zzlK~?O&%sZik@g~z5HNR%2TGSqC$!~)XQ~ecGlF(E8dDBya^MpbA(RfHP*4rt$I1m zUCvmd_QlyBSG2;cewu!2@14z)+vL6|YKpjUF0yFw$j~5XRTym{OaF?gf!KeSgKTSW z&KTEQ+dTtAeVyQK!XTYKS=+|h->?2}V#^Ykjd)M4Ft-NSU(OJUOG-ByjcD`|hKVhAPxO6Xo z)x+ykhw1L4H!tIkJOfQSbayLdY_Q;-7JIUgB#|TqZ5k<`ONz3Ks;lFUwm%=lY{(wn z@C#?iR;{S2I{d7H{0>7i&veck>V*szBsciR!WlgQX88s zn+zI(8U3all9N8QwIUJu<>m8Ru5-qEy1Fn9Gcqzl9qKDawh8+?6T={iO6qSsQ1ojx zUYMJ^e&dE=1fBH`Jo@O`XOJiFDT&_SvYecmxd+E(H|KP0 znI<(_V|&zD%&f~bn+AI2;HSlWXi1%JHR`CvA~>rG>ct--v-7CstzYk-i6+g(J1|;O z==oA$pgR^lUPm@^OpOzE;ewkPc(5!!^^DAijez}k2&sH(?>~5O&+by&N_Eu*^%lJ6 zeb|3NQ?*p1-&p}QIPd$OqKBGRLLMKscPuttnT9ARIGM}S5JA}wcU7s{J6m!acXyub zYTO%6duhoj3$GT?ytoiWS~AO)_N~Cw%i2r>jXrqWWH_xCXES73OE|tY{cd(98;!~K zDaDjJ_Xq?$nmm(wy$bWL;h#@%a4nI`F0Za;wzApX-8E$;g_j|~_qn*ZGz`4Fy!7<+ zILMaf=4{Kf!}9aI1N?!;r4*5_wmK(UK;^!;dhZ2!fh*qYhZESxq9(I6o5v9O5zW>u2`i%kB^ot@Fw zx6;}yD@^D#x3Cy0%ct8eE0ygN5_sV1Q3g0_T|bny>e*w&>AVol z;N_K-aF3>OTzlD#3%532AyQPson&JZ5)xpaU1LD$_x=6-TSA^UC@7%QY4jio)|D3* zb4tSc55s56%gd1SXbI;R7Y*q)L!73Z(k|1#*fv-Jy-JmCdodp`FEcYU`~&ZTf~1DZ z{2@^~&8ch=b-oMpvGZf&3dt?o3)~9%&FrNmM5iA7N>u6oe3LUJA0#!Z^BDB{9_RQG zzr&Uum@51|MBFVWY9!(9!6!us;di~;Tlb>Dt}3QBuJ-pdJ9V2mmlAV4b{ok=W>25y zrluNOTf=_C>Bjn_f*4L%jVVp-FP1c{WmkrVA71447PKJ0e;Z#YN=T>yWwXcx1L1{f zq4!M$fJMV5RdT%9Cug@bGkd6E~d zn!?1ygmGBuQ6f{6ib%~{F~sEYBE^0gT$|1+^u1!L$0#QVr!81F+2YFt+$7_?~Dk|Z^37@L^wUoa>zbVrRAGW{%C}0%! z6J?Hs4SaTY*0!czC`UX-xYouTsrStlT3)y{`CNC?<-#kCV8-7y*DJD1=U&&>w! zSt^!PET^?1Idj<$;Vz6r5Ic0Pue-J9%WT@0mJ_Ehpw!XU!RR$h1cY3x<64QzBvG?#|B6-Me>VV`F(^naKh_wgPxI_pe)r z8VgJ{P^?zQ<*P=pHnSSUGB>w_?(Xi%$-CjdpK_Zvz^1aI0JtZgH*ZSm!knC(Xi>08 zx+MQ}5@|>y)eaj)P+w2a$nbD(B!oot`(1r;%BxPd!H_0a=I3E!v^EcHYE!&~edZkT z`Lo}bx8eZj+eG_R{M;3)zM+zldd_w01G{SSqog|?Uh>+w5YU(MN)B$PA)6^9f z7QgKp7%J~#zd^q6x>e_>*i$i=5_jOd4ZPG0v85A}2EV!)GuR0ndPsDQ*>FUWaBU<0 zgl8K9^?b z6-_KIhB`UoP;L*_l{5t10yZPDV;0O)tM=QA+o}Ht7I>& zU|5Z)rbc1~v#+m(ADzqZq&?i+EG#c~b#meim&{Q9+|#2n=>PeXU3&k*g2iC);S*k( zpoY+*+2~f|gUvZ5FE3Q{bb0A+&n^2qYqh^`A-QiHJvjByo}GdJ%6ZV0cHC(&xX)`o zK4DFuY@`v)$weIVj;^orCcl42uzoqq4I;aFT3J6QSJ$i`BQw-y&IQkAE9}b)U$nN# z8md-wX^6Ml+lMB`r+4O8O}`<+C47{Th4h%OUJ>j=%`|J|WM?iZJ6#Tp(j1&tZWeR! zkAJbQ6sc3Z98ymJcCA4-ycwu18MkG~Q(=FdDuPHNTfwAgGE_!l&nzcDHtICSvrPVE zf>&+A7H$@aKBtm#ArCy?@#VU5Gd8#D49nrxGUA_$A;nybAp)Z5t#}F>b2GMgcx?wH zz2A=Xj_;9fcX6h06mbN}$r*YqoC}x#&kbeBGk_9Rw|y3*grGAmE{-7J!I5)z=4vLs zgbi=X&`?vGPOhy5qaJG6+p}A_RE@&_$Hxc9S~VGVvY$UYY|Uk~y63J;rX}hvinxFL zb90m9Vx#r()`KPkF>LP|^CH0k33*&_CN{4l^fPDY$cw(WIGt}xri7$?kft{FB)fSt zrC&X9!14_&EStXZhO4-wq)t#j{4?rprkABc08=WY#s3o3A z0Hs`Qah8*NKAl(56Y{||qI1B3w4cV9`@wgE=$QTLLZT{SffKM-35dX;@(mGPFp~Bl z>`*NG*i?kuis#YX<`=~jX>FSCM@{oqm_YWYKqfLr*j0?w2SD6;rAl>l1G~f2!Pr<3pX1{6)5(kWGIS=?gn17AuO;j2Eer!MdZt{1bQ+^5pw}&8ewl+JrI7fX&Y$O z-Yld>qI1T><>~eHe94pvt>99>S2%3^{4H5Xh=|HprC^0$E@k+xq&@F!pv;Qrm;V!N>wbarJ_O<4w1>&*jPO(OyAM*0Sb0{c<5v(v&o&g$5wJB z4OqY#0MW0Rx~MSo3i z$tsHYJu_orZcau{zA!%z$S<9PIr(%pGC42r{^z_uM1jU{8Z$7X^DjTZH6Y~a8A2qJ zj+X7$;(gLcmLBMtYe!oTZohG_DiF}fS50XqRzY-Zz9z&~!AQct*q185N1*TE=jVUQva-CqZkQU=VxwG|{`H$1 zAAS6OZWek^WMbjppYj|C%k0Xu&BIf(WfV>_($KW#54*ufKSO~kI*H92 zU-oJ`60*c667DN)HO#~EcC=p4@s{~PvD|UddZdx1A(T4|uYYKHD~NR&`;6N+ z#xhwsIk>)l&dcPSoScFl`^vlW3H+E~!eo$-cM&!61Cvt6-f^@5g&SCBFei;e6WI*c z;?IX`R6*tH=9aC>P=P(`y1PEz%;3?k`01p1!29o0ZfFbYZuusOlC>cvMOWuv=XSdy zaDtgm&U!F_3S8l+PLHx4bKK5-m9`vis_R9yTQ3xn6o)M0^3`YR^G9#qxCtk)=fCB` z4}`~8(xz>l;emkxfN>%G(`f4m15-=Oxk%p$%}1Ojw@zQ*%4l5(TbWMr#Gz;+OOV= zo&`$&rJ_bK==Y%)^Xj;?VC#vAk(cA$^6>C#&qCOjx_4YE(q0aTb6OWlj{NB%fMwtm zVUpAE-fb2C3G7qJ>;c@jiOa*r);Tbc(KVOYK|2(jy*${}HM!-g;hEMYF_5kWLjZGt z4?3fQCXUfFLX06<<@rq}DqYuHAajkmZx{xC<1U~lkYr@&%y9}UnL^`a#mGqT&9Sk|Q&U&Qy=mjWJtgB}dTm;(|oz%H#w;qRo976<}ao)tdLe}e^wwu0)&B(!-T zuUB3S5#0<)w1%EK(XP&o8)qLfhF1tr|kD$ zb?n>U6m}p+<8@^D^Q2)8oxTRX?dHgBAv3|XON#y%;?(%3=`rA`sy|4nQ zx7exlC`Tg5QskyUtfkvVgRF(PMQ_+f7%DrP33HbcyLG9n7c)b zBP5f;aN{7x*c)3|T2@w6B=vXC>!mU<8^Pb>$IlgMk8<=-5vzLp|GrWifN&~_rjL^q zGni`Gd1R38VsA{k=^y+SdwJn%7V^`1NmIoBR%5EF7PWZXW=?=iP*;*=FAcfCg6HmP zfkmolIrGko-*Zcd`;aN*)2M(#JtEuiOhpA>7Av&I*S683@=iZ-Sy! z-2K^Hzu8#ZH9eieN?vk<+ypV9NSh1!Mwz}SKc9__ zP4U~C=Rj%w=?1aBt$YOe$Txq;#Nn`3ZvQ(R=S952kE$jd%PE%PJ~9&z z9AxGAB{bMF@GeYtMUg_EuXB9YT@V8@*#szF)#}4&P{Dwzs_N{e+)H9fw$Ry5O1ewz zKh00Wf}*-$77I@WLnQGevbVPfMt5%~c)mlS71O{0uDj4)lZgcS*tC4kXC3ZJWaa18`2vYySce}qW*Pq)9N>- z(v2$J9UV2tWt-E9xo)nmvri!a>d_r>F`h)yUVw}(8SH#~Qt?RR=(?inT;lCxS5~|O z5-$F`Pm(w&J~`XBC53KiL}oL#wQ7n6eM0*vmJC%@R<_V*fBBM%>YlS-Aqx~E!=Eal z433dCfiM-Qhq-4|Q4yk+oHOn^Dd}VUp4Z(URN4jyHB}Mc3hsO%nme`;f98(?HBW*q z#0OIeC6AT(u)QCdHxv}GZ+99OM70|4HxoOcs;W{t$oLG&^qs&Pot%DK&W30cPz231 z_9i5z>rEbME&TfR96J=E?4H&(^tQ*xFV?QQR$0b!mlMuyzaXN+_pe6yw^F*2KFr5> zTsq@6%aPrl6OIql$D<>>ZDt3?K8ZZQ(p3{J3v9B-gcB@J+N%S|CtP5!7cgF(i;F-jx z2uUgeLi>*xkL-*B4#?FtPCIeA3=rR6Zv-2Fg4Kan=gzI4F*n1@Ge3W(2QoqJjN1aR zNZnE4#X`Rp_V>$y#U=L7W1X_w0|Vb5!_!DLWNXZ?x(=RftNE1aL%^hlXVO>WPBspU z?x1Vl`)hjxR!0@7HNLN3zvegURB4r|lh}zJuxu-vXg^uNzC*UN4mH`vO#3}Xp>Rn= zQU`3ZBNMF_L)&QOQof+1EhF;v-PhaiPD~RgyVMPQj^1Eu7(a7kOndoUOQ=2U@|F|p z_T9K835aEDmt#M9J?iGGI6ok?W{pnTjwxviG}*`&Up6i-nklyRui< zkLz-;XQrkjMek&XwmDcq+l7#II{AISpWWV$2hbBPyF51)09RI3#p*nUHmSa~zO|i` zBJlZ!7(QQ~&axrIsm~6jYQl;KVu>>*_=!FzC8hj%f;>uJS#a@-7cWvTJngL%7kQF8 zAJlV8(0v#6`FNpc+baIZ;#`S-^q!m$Ih+*aJI)6W8V&PZ_}i+>n)VHpc3-Q!7^_=D z;V>*O41oQXzcPQ8c!>waY++`W@SKN%Ao5%O11_!tNhw-rdcV&inuFxB871N+rSiD( z97Csn7LqwKGbbmfs>;LM+)of_{uOaCvDs_!qejBAa@BxSC!z@~_o~T@wn7Xd@n$V` zZhj8BC1jzTIv{cDSf?qcV{?Yi-pJ_no1kGSC#y6?y38KQ9ca54IO9Y+zo>uhAlIQI za^{h%v!!Lg;V0QW*m@pFje~{-;Z zGZ|l}M+0`nxAoWee+rbAmiG7diiwN@`5$w~*5_dfJ^N%=!$qSXJVFf>@H0Q1*KPDu z*6c`szA*6`d@W`xsyV)EOi5cudvEdC2SKM-HH3;!RAct=B`E=IBE(&7ZEJ)cN?!f) zdwZ&pg*k0inZ!;&KIm?tdexL>6Ym{43PE2y`UL~oT0g4C!-C7&c7D*{WW?Yr`g6_w zVRf8kmG%zrcjISkPJ#v@9*_~8nxINZy<@X>)5@?cxY%Uep+ggR##>&ERC{$orL0z7|dZL=TFwd49qR zb)%!B7$UN?b!Dd@u%Ms-62knM4@AD@Us?33MJak9Pd2#7SO9A7( z8+b}F%2&g0oj*G`IGElHodf6$XfRYrr~;OY8Pp^qA9ZOf>i=!xOjgZ_+ePQ46-_A@ z<$y0MBh!)Y&wRACxx=}1e*%=8bwOW8`KpJ*&%?G6hi~il z^$(pXQfWc4C^`Nb0S{_O$GupC#POkj`v19``6A;-{o3yxkC>{jgdSn z>Ehg+K1wDMzW+a@k~grlCv;>;G!b2tH1_FJB}2f|FK+D3dRJFhdm0$Lgv~4zc7{aj-K$EOqL>jpvK$Ov@|-Zijgmh8N}>>ZC__`A}U}Ut_i)j zVNzU*O+ugo4jucOiB6IZn8+nlwkTCKHF0rqI@SmS&S-taOQN~(?w>z%OKdk783?9l zdi9z!wzr+iv}L{Kc%cKDT!9JhFiDhoJ@46@&Zvq2Yq?_o+Hq4WtCXRbfgX{BD_q$| z(6EkS6pDUq>E;&gf%kLO+SK%|wPLwF%HzPv&Mu^_MT9!bU-84ecx&nM%k~lRL}xNm zQ{TYj86Q8kwYMJ=!WBSKb606^^BHAfJxT-$bU!HSz9DXj0%JCv0hfYN`e<0b;Sw>q z^AFH^eik=I$d3Xf#Ly(nz}$fyL+9Zkycb<8GZ43VXCSqKgRHe6!j~3v3qQBEwvLVj zxn`7bF~r%QuHl6TZ%aq4pNPSUwD=L?`53VQSkThYm|Ixb*mIw2F;8M=XTR|JrDA7U z7LsZnZ^?0~qr|J>bU=C}T#GBuFW&CSi0m+7jBd{w@eP#Q7u z@v^af`)zbbBU@EH7}{a?!rfJ=$ZX*ry5vLROHbN=WqhXg_8Suu!pxBy8$XwRj*PS} zkJZ&7%DFUnM|K_b*e_m{ii(SK;=aL=i(&jhjcJm)lib-KNSDPx5ybW2L7vB%7Y{*? znCq^8iH3#-dW=BKI669dUGVifPuThG+qa)T-}fyLRjAy%gF$N`L6P|%dC^1OR|Lme zqdbNE)LUn~ad6j8v=>;kxVe<~C9M&XCO0_A0_#oLzTC3;&2h)-hRt;u@36G5uX{et z{EHoaaz$U23Kkpo3YAw?LDdWmL&u5{IFY$A&hz^)HvB)d=@Km?RnULPQic?HV^3u$ z^W7^fSV@ydc%)cV3^}8_qRvTbAYX&XObp;fU8*A34)vdtH0wUR_)aHb%(TJz0ehaN z-dO2)%2lI~W8&#P(**6y52SC^!mLvztF z_kq$cmxu~yH9-#FNoJ3i-K48&jXC?rZmBRl=f9L`ijP@5SG0Be@GosIY_fj|Us6}x z#5@gyg(V5pV?bXnAbX)Q=orBgfDYSC6Z>bArVEj0=1iLl%7p2lfDyD1q?*(-&^^JO+!pfe5C&C+qYl89;;H?p98DQ%cDz?mX-$1 zcAhh|=H}W`++J8S|HetIfu#d!Za&K-(XC=Tc+)c<7a_J}D4RpdUVp0cli8u`b*;X> zjst@N5=4f|7sEpfz@Sw#!zq*T{Q&cq48^ijEnh)y(uvOat^1E8E1r-5j}alMj}b4q z;R3!EU`o{6ym&!+*&UZYiW8$>fNbt>^XO9{vca#=&9#K6>T2e;T0Z4{KrT#-jL`0;}KMt z;;+LH)E8hW$`jJlp|(R$Uwnczp_ZLY6==TJ34mQT&CS-k^5*~Fv=R%7y(akH`cs@F zqOS~hZZdQR3|T}wy;V8L)C|6z2V^ByND*bnO<+4E{(?jx%Jv-W>?lJUsi~<0#GZf^ zq_jLe&!Bj($vVDc3!lUk(P`#mVr*Q8w|Cvu-90NKEp+r^725mAiP)`{{E!C zk0END+3@humGMNOT2!ex(hU(LAT0eW$UL#|Un;@xh|FqAO!C$1KHmhz0SG=$VC(q! zqZGxCf6=k!Wn}|HLl=E&hXWWpf#C>GKR9p;m$bSb_pH;0gN;8)HG`p!v_W*_AtSNd zlIE;xonbP@CkRg&!{xIw5PZk|51G#vygPSU6r_>~-;clJpPbF70L;yk1(-Ry=9&Ba z?alvbY1Q0b#|F{&YiL|(rYJ7#`y3Z4lBaZ4n1}CcSda;?Xkf}1kP3f<|Jno`XuR#g z?)F+SNcHGI&W`n~2scKc&8-9*TjrzZQkebyr$@A|pkjf)X;EI)WN`Ns#d8Ri?!5D*Y?CuZ5{Y>1MQof`S+r7z~^_h#;=ft3lJ0oxPy7)h^W5y{4>WS>UpD>WPL- zsvu@GJ8SR#ZCm(fXitN%8&31KY%Zm(Xoy0QW)=AekpRJMBe)x^N{Z;EB6#xKbQ}gCMixnS^`@rh6OU-E`Okcw;=)T0|z7wECjJ#4<}GYe{6_GQZuoS_gg^luMf?%I z3SXxDt5I)uFqSBKc>9-AMRasD2xn$zX{`!kU|ARJ?XoV(RQ_BSa0%PH$Gmler>%sN zE?hF=4`9rDsC5-?_~mV|PoT`d`w?DvoqlU3t>2P!etJVMFHb#y9Q&b0$jkpqu$VJ# z3pRImks^-_|?~e+MRG2Z`*W~Ud45Zw|^T8Agc2{#n2fICxwOV^`r8DD4YK1`;rqm zJ3O!e^9F2DEye}eSy|AK-gB=G4!#=HP-~}|ttxrz)~&udhq$q(k3@7*?hJ~cQkMdO z8}#EbdE?O)K$t)$8z7*o2_F?#H3(nup7#W{ zjgTig{qtu|+auNxQQCXbI&v*7TqjW{=Ey?VMt2xEN@t)VCX^?yC${#$siCn`!0r7$u&3Z|6< zwOvjQ+!A#zm_y~u6;!z&iIaWD1V@D_M6(+s}{Mk zc>#FU6MDv%h>pNFH{oT&By4XYBO^<+efiT*w91PuYHH;_Pa*;g1T^zJJUoE$28kO7b#iC=(tmYLbNTv8v>m(e*}9A#&VBou z|0U6dUsp}d+1|cZ7qarY$b?Vpmrlt_aLWpEXp;wP-C<*G|Q|hW(o**DaTAG>7EiMvPB8tQ6kF=+cNSRtVrjn(I^0z8*xt04ljI6xvnnLxIsK!F;QBZp!M2#)T2 zm|S7nnrWTx`$N1w^aV!#0XKkahLe$=i7izmxpj++Q# ze*hXZ#vArRJi+|N!6Z8WEp?IJKcIl$+Iq0F{vU`WxjfbYeecp^NiW}gPyLtcP~!a~ z4G?AsX*Hf4g>n|b_>)smP*CW&oxQSe;X#x%A0OYa*XfRCSRLBYp-?i^stCS*Ht_Df zU=Y&+dV-tUT&YiaGrPQOY;(@L2D+}ms1CZgyHBzFtS1W_`~sb8v7q5BWTRoo8~^U7 zVKp-w3TBvcnuFeN&j-rsxqV>zf%Uk5x7;sF)#6T?zvQY5f7me)oPdV4k?7pAGa04- zItuiwFGmcnm=`WU)C*!=I0q_g;ys5^->Lill_2Z*r`VKya)sxgp0ij|@P0A4Mqb4m zXHc)+K5u}@ppS5~k;MM53mh)t9R;D&gjGaY!DOl1QhU0ivnESD&zlQ>pQ@y;NSO~vPy9X z6_IhwgzU}XkUg_ylbtd$BE%7Oj70XHnI$qBBzy0Wy;5dI#{W6=e&6r!_xpEsU2m7J ztM{DGc%J9JpZk73xCp!(l0ch7Rj!jo`+?Q&@atWdwAE%fko4k-K>AjL;_cfG*EVxR z1N*8v#Lj^v#O^cpm*?DP|6Ib}3O{Zof6ukU8p;_VF3EMOH$*$C{r& z#QjkIKJcILu9*~T9fo1=zjn}$^kD5LN|xEDwMqcILuL0Lg&bv8BmbU!^jn*Tr^EXD zp7->ypT?IxWdRE6ViRxv=al~+X~$7LNqS0B@i4%qkk7BIbXxO*-pkFB!RvJ<1_JZD zn00GIaM}FPhvt?7B{el<5D1)l1;2m(RJOjH5>{MX48x{CJ^&*D2?|&prPbfk*H40m z9jKn{jCf_{H+r{j5xo(>>2$mVr8tzQ1Ej#4nAtt=>FJsM9LggR?Q~a57k1mR#HU+4 znmiVUsPz8@^;pA*a?Z1<>#geGlIf9+%FFAF^WF!OT5bLfZ=1C(g?0DCogY1>UP|9u zS#8!GzS{q)8#9HofpKth!mIYBOt=$}3Ktlsb@g<2SIyD<$7A%rmI8udA?WL=iE5@4 zYQRiHtttRtdg<4%Qu_?`ZS8)sizArzblix5lx+=pB9vh4Shj?kjVm2_Vp3D>yLVrQ z7=6+4>Aon)*GD+-11O&yK9-mJuEhK;E32qLA*Hu`CWt+4y0HAi>7i8>T%6B<(&gBZ zYfqEhjgQC6ii;&hy$?HOC+{yrN$r&v&7Q+m z>Nx=$6#sQs8w=2d1(A_t)FOa%J4)SjbozmO8gLuUL+?N>c5U7muF{3^&?4jq$R z)%@=OPR|?_r3lU7u{5tRsHv!szXWk#S1_`Xp_!@j|0elt0bD0`g;ccV$>%%!2gKD- zVQMPN?_z-di>a70kP!C(X3eL3z%1TJ$$6FyWtI}>@SGIUx;EdY`I_BQ*j z6g?I{Fky|0h1k=EixN4*?YbRpf%04`JuZ2K@3Ljsx*mcb6^CAe1GP4T?Pb=vBv8j7 zdjG1 zT9q5Ypy&i%OO753DGGQVP)R6kZ>^YdQ9{;ru(ux>9v1%J?%7w<1c%ny*4EZ2LF%wB z&<;~Ap}grb0O?wQz=|k3Y2O}|%IYd~B`-m!4JdPMJtRU%R5ZiH(#lFB$Cz&xRKpZB zS~B$zc)D*5-MW2|%}-&b@C-~m3|NFIqHjTxvaz}O-rzO#6ckciK)e^b6v7su^cpGB zB5}2&uMfmFrp2Iqu&3%X5ByL0|1WbG@-qHa-2LcZnTL7rSQQy~*cvNNEBPLLNS^=G zWVwAn052|qfop_bkgaP&<^A1^T{3fp229k8-S{zbhJSBlwq!tM-o{gqR6%1F$hcdZ z?jle#*nOgVA$1)leIDsE5;&`QV-@XY7~qGH1%Qk-qz$E_BF4+>ZOTX6#XxVf(iLbf zP@?H7czcW9!w2z%#?-!$25OLNA#C-6rl?a)&(LZ zD-@JjEttCK-y4hlAct}N;yC3hG|z#FtVUr~4a?~ALye_q)RdFj8>$VAjI?t!z1T=~ zj+P6cl|zC+UN0dVe8lsbd^psa%%GkA{=M+RM_L-1h(>dEie}#wmul9tCv$Ui5rn79 zRfR6$RgH>DO+EoKkAg0c?odFuP3O!yC1IqMs*l~OS>BD%drYX$!{LYL9b$sQFkG_@ z^JFf$=E;x@EDRW`4u2VugA5$3X7ccVePmvM#!*Jl0|%uY$hH4u{r2)zJ87M#FQ4wUjXzO|ZwK~Oim~OdnUI%VtZ+cLu&;9 zRnuT^Z-#bW5@@F~*sJ{fj<@MWY3V4lk!pb?Gdfy8TwF!e!OYAIm>+2V^`b1^mX)b) zLoWblLVlVHNhUMRgr$)6-wIkD2Y11T@xs%oPcW0`@e`o^$}X8e7;g zOn~kb8%}k3zeO!CRb%q~ZDPwc2=Q&o9>8_(A6xF*L`w-;vZv{qw zcza*yN;Mm=+uGW$JmTb=t>#NnvhLsF9&e-iry%Hn))qI+AbU;bE7gGUgnm|N?A`*q zrU=b!n|Q)F{F-nOoeMm~G_WJd_w4M#0;~73x(RzARf+NBgBQW%@&goIj;i|?8M|ycVt{Nqg0Y%Hpi!;ZJ04k| ztDaX7Svp$%RZ?m<`zXSkIf8O<;8XrR@b&uGy8Y!s%7plMKp=oEsjXCNAHr4)-K+st z3JUC<9id^L7g{0-RPCKLSaf<$PRN6l<78(aFDx#89(QWnJ=kvdEg`;_AK*HZrNOtI zUS!LuLZe2!t6O74U+DuPp2QaDaY4!%q{If5g0tZV|NWGAV-~Q=vpz3oLh(LEDNmXS z%{D&G=A?G0>5@s{#rGkp`2&MXAY1j9(mOvsQ+aRxhE|Gd$4Muln0tXTBm?W;n`jE= zPsV$yA9*s~bImp-G09z~rOkTA)b41Zlp4thw%17`NWtA_Y;ev7ZMC+Oce-i2%=onG z)bq6$hu?yeLTcx1oCPWZ{kTTvADo!nrF-_v`pL)Vp80N0X|J0cuHK{b5S{@czC_hX z{mXenfvENiWf(p{yC0p70|IAM`CAGoHgi28x=`mh(Zs}SuZRoggZ9VTx;`K6bxBo4 zh2-p0Yd41=R_Z5ef;NfdEP1CCQc_4SS*qFO+uos3)@@r^r_D!~#enV2y~@b3(xhA< z3^B-l&chZ$q8%h;V>4bmcwE7p;y!3Q8QDQCP%dy%*v&fd)Qjno_7Zy7OAz@|KUE*# z72Dt0VA}-P#Tgbf_~6AKv1co_&$E#}Lz%?DT25EgvfjdBI(K`vh~Xt8BcseP!0}4j#NNI`abuNHK{%gUsai8hD;QZIcFfk}mx?>8D=OhpMiuobc6Fb(4O7 zFO51kSd6ASwh-?&I#56-TXq34;QAB-mE$mm!H#I2A9?Xv`eC{7cK`&r$^>(!4R*! z)TTGhq%WPCa~6QfcNrOhmqw%xp)@SJx(H2{{wp!wxta$;3G*!xSl5HDGl;Q@h&B-u z4Tc}JUXcC0zpt;atI`2`=Mm3VCYeOH?mQBy_Ch|2F#TDS?+DY{T90vu9KE+K(7VN{ z@17GnzwY=lFhJRhCK-qM`^~FOQ3{~sIy~%6^^u${7|q1PQsl@zByqaf$GygPyZmTY z!?8zJbV_Uy7IsY)RcdY2z!H3OD8G@Wio`{19GUWe`2oy29&8E&wRq>d09h35c$wtm z^!QpUE5Ur5s(Nw@F3OkWIUoVKdCfBvGoxt5Mj3>Y21NnfLT0ensX?Ox}~b+sfH z7gBS@47f7k0McO(00-3Tz*Qm6*xVY+*wm7kM1+WbVKLTXyj5Qx$mP5L90%^5HGXzl z^{^Fqb5@dr&<9;BNo3dRTJhiKx{KL8(P(+;T&x^)9)|Wq{>ms1*R?C^p0kTXO8m_k z(IjZsrlYbwJbC5m-0qhD5!SxKy3jY28J2faza^qbv$@njK-j zO;Nzol6~99&$$#cjbrDbxQQ+TLGs%@pIW2B>)$x`mxjwhCoIwn{6+WkaB=f=X&8W{ zS^xd})mtg65Ix9qo_`47e7JwYo>w4~Y`C*il6D6uL|vjXXVS`FYm^5>AsDUUuPh{E;yFcaXy|cTR{Ctq;pv`a)d#ZkLhE??i@YF$#uy z#nuErCMT$Rc+>%fGCNh76Ziz*!gZ_@cZLUunvTiKNNO@O-*@b}QQFdTL0zS)qM}h3 z>GFagV+NrB9= zBLEbrE^RX-Fk91_RB+rpXSEq3>!)-#g8_A2Jv>J2_E)lT{egQoDano;U&mB=VZjQ1 z&@2*vap`+G25eNi`21ERopZ!~`eY!EwMkvee599qSIG5KE zLB1@N4lDPM&C4QhB@l)tG0dw78NK_L`hxzdHSf^3Z(e}ryuFnS@%2sTLByP*I`?0; zXtiOUKPRRupl8Aq3G>FTU$aDB*r61mQF)QpXITnHhMd~kv1ylLmOI&_pmlr^)_ZHZ zBRpO{P#sv@>1oE(^2Mi5_(8cPLw_N(9?mM8C#vhj#upgm1TG?`W}ZY15K-x#OB9dh z5)^D~d4TGPkaOLzRC?5?qg*avc(Aq>7;c*5RjKU4r;ATuMJJm?8M; z*aU6~UyUnK_u_!Twd3TcaM<6kdaHWw#!*?NBNeBe4tf?X*4~*FtwGxW@{sEx|8w<# z8Cc-x;{oAG+3-@&>%Rm^Woc>m@NlyW80vZ2apq+<=Kmo&1lq&}qp;>KqS<+5SJ@^i zQ>%t<=e%!wOqwR^;CrS>8&LMRx<<90l?G?;ukn}yH{g5KKbjO}Wo3OZZs?9pA=PIq zh?j&XKNu=&G5ey4;*saqM-N$)h}&4g9~0fNZCg1gfprBAVZGar1%PKDh*k`)|naI$B&eeHEvXcXRQ{#fgSP;omW% z<2HKO;9sK!Bay=;T4(-thX2}Id#=L6IEVAf!v6L>igmF9g`fKijR&h(9=so!cMba2 z4Fb?R(E^nk!6Lrt0)nL1Ss;nb<8SXW1Sc|r;Z`xui432M)G!uIA?+m;^`^y|tXeY^ zEiR!N1F#0D7}*u*_(yM_zZwDULSmw2b2H}MxqOISpZX9q4-?mt?ys)JtMA)ZJy-oT zh%tXfeUDuTy2zRoxcY)nSX}*H=-nN^k3IzsO%?Ba+&Ta1PrgTkLOcgXXdDSoKnS$A zyUj!M>>nGX=2J=YL#J{{QZhUn14AchaJ@IPS{6_XLyuWw2AT*s_p%ZLxR-6>L;W$Z z^M~!^;wY(nG=k&R0MlWY2DB}3bpP3GQax-yXjzwjXlQ7Q>BQzAo5%WY(T9#n7y?cT zZ?r?_1_WvBL~I7qd~8Kl9H*NLPTs07!3G5{e!w`ONcjSm2iB4f@*@wjG~)I*|9em@uiM@-2t%0ss9 z?--aW;<1-N*YWc6gHeE&msjBOIUlrFpcGwX*$9OULh;>leyI$SQ(#C$D+^V2c6NenyTj`A8v(V znw4Qvao?m{WbEnfe>WrP{{DfhLig?DC6;P^wyu?pm92I6EqZn!<#3nt(Q2uiXE{!! zaVWMrh9m*iC=Ag|G(ZNC9sKD!yzMKXUml&Px$Z3rWKMry-xt#x6d?wjW8c2DY+D#| z$C?k0;w<`qMK&vVSSXfG_!?x1MZ`v8$d-JZov~Uv+{`N3@V_x zQqk5-^h@qvyf1LMU1*nIP%7c&%j6q7k%J(ofFkZsa`O9{l$1N*v^~)E9GB4Dnv$@3 z8K^Lu#3}dyrFI|h@%YVs!K>6^VpQUPjfkY2T}_77AQ@h#LWTUN9sBzuBgkBj>%9xI z+keP#pkB0#5%~WtZV|m`21L{o$pWwn-?az~_jHat{l{>#pz4s{FjN0U4?@LpM{bthr(p2I4#YL1I&;=p>y)Hyay&yHWg~J z&KKl=fT^A!^)UJ}xl&mv^pk%OS}s0l&CVY`*q69PMX!X&Ah{Hrg%S|HfI&tatR8xZ z&>Xf7afJYs?-87mUQUS0Jb58*Sn# zNlAg315h3QWVTlakRP^Y?ba3}o5sir_(sPBKfkJj?Gd0o`3!^;5 z4NX32H2AtCw^QZy4h;-^o$*gyJJ?v%acCGBF@&oxlH?Ir@-NWeH9OeM0!hR7!6=~N z4br3EjvTe0J1kWAuX%SA-`CmxXY|97hFKmqSKFjrkMJ!tR0sR~$VULf-?x?#+I?XS^BDGaElIHe;|- z$oH(_vDfn@P-SzAkccRqxEgH0g4mK#k6Juj95Op;_uM2VF_^PpgkK`d##}jqB z&$q#EATJ-%zX+kat)=DZ$iXe+Ia+5K?3~UogamdUCR(RF*4l)I0&tiOKdWT0>a-|- zup)8Mn_eY;P%c&<)q^Bu;spsM#7uH>@&SqZJ2n1|giKmH4a-!Pe5%gQH`Gn}g33=} zqOjN-b2?Djp7U~d|L`;mXzH~w*;eRU!OZl^%ALKr#}knq38+R8l+{=26GV$@MJdVXpCN4Rlmb-^p50&kd%p?-9;=FX&O> zO!7E9HH5y?)wTM*=Yh?g)}fK%fgbZquUs5xe?6R}C0FX^h2K}?$l!g@V0w@pkk^9Q zbK3EhqoX67L}1a02nj)+0wsD$X(?FDioisj{hI!n*)0VH5N1s|ww)D(ad9A%3~9ke zzZ2%+<73*lhw&Su9UaT{g&^$w`0*q2!JmN3paLYVeYojx@1902rfPTo-R`WBy?wzz z_sGbStI!!dyr~fkC@fRaya_*)k0S^~t$ciHDt+DDE{fj!(RK?lua%50RFV&)zoQpq zb6)KC+=Ze$<-?uA=9ZSWwV5~Ld|#Wr9E3z0OFkoe0`o zSgMm4S8wmIzF?vTT}bwI%BT9eyRS);-T~zf{40OX9g$ZNQwPlL#l9ca0W?9c#so}eT^6ctvi(Oq< zEy*phndLR&{8d04epb6*XESWQwV+L_S4*o$Q?YdzH9_*5WI#hxuSfXX;)=&mOa&R*Cl|TfeSk!`wY4YD*P~>%-Oc#dXBhoKhZDD^8lDA0}9b~e$T|+c}tLLw$cA4 zK6j23l{#9UqZ%geMX-{Vy!W&G8C$E zs&kLv(U;v-GwR#7yEQmo@MwCy005w*9HvGmE^b`=gNm>yI~yHeURtX0qmHG>UIN?U z&UH296bP}P*M2-^PBm2db(W{E?=x8~Bct#|aE0|mohc*8!NrIanfo3dAtKHkaGq9I z)D;{h5FzJ=J5gA5qwBvf56DPhC+;srNy=bbs;kQ5BV*osR8`z7iA;D^JwEtSKjD^d zq}Y2A;kEl;A{+-rMu&vQ*m97lSb_5R0fBcy-)ddSUm%2P1eYDc8D~2lzFW}aVUV3M z>o!{wU7{IV$*Q;UW9tlYgsXT3Rd~ga<9*vC4c<|xNKjQaN96drw}{oQa?cX39Jl9t zWp6r>dtF4{@52Ug3@{}xBQX--7eVs zF_(}7Hn;os78VqI9WAV^&Io=LJ}02#=vW92Cb~eEv@=z#geXzi;EBb`hq(a z0VwP;!QS%t=kam6O6v88bEIplt93k4xp{d9TazTNGB9QWaGU!@oBiFjb{OF?M|K_F z6!2J^`TqU;_;^Zs`YD#qKjah?^v8wF&Hc+eLI{FzbDZqXqcA)ehF|NoRZy99;McX! zLDdfrQr3%px~NK+jGntzC!;`g7NuukfJndKN$7eW4?&O+*lT-*n!0PZJq85ql8*HH zQb=SM#1v7QXYO2K+tUraTx;_=zy0BO=+(O@eN-;kcFus7dhy!s?&a2F1yb=Bj|YpT zK;7oxpd7^T>4UC0r8P4sWRI&cyqTNgKf^+-Mz17+kmpc|%ykvGtRfq@!9r5+hCFx(|#lFsIB~2y1qdOhOTE?P$Jb3(N ze7FA;3{?2eyX3b&^Pzq-r7yZsv;sJ^IF+v z;Z}phVVY24eoRJ%qW;6nzp9Ru4@P#4jwaTdetZBo9Bgec%xr$%Momo(j;xJM&WMAr zmlt$l^0Tu$#^ySA+f^h4VU|;l;L9;Te+U$q(9pZP;2!He6C?tfhV_Tg`Qzc{hPiDp zy%m~h*jL0XQI|vqKs5Jh9yS-8?mhaq8X0lN)89VnluSw;gBZ-WYK9 z@9v81aJts69=C@j+^I7?&ahHaTkGrLK}7%^Y>HhNW(rzxF#&D#oLGkvRg|)w6dur-ou6;#>Wa_;G0&^S#Be{QwR^|h^T1XG^E@VQgVdm} zNXoFu$|LyI`d5vPli)3+6js|DxR$i%CmN&`{#-)bJe9>HxOa@0jOdAo_$QO`ltCp$ z#g{Ua7c-7w(shtX(gk>sHPwf0mlUZE*(K9>?kfDpT;pSpl5(w-40eM1LQ8Aw-u7A+ zoFgHwNH|C8Jer05p_dayHekf{YohyMsS=%yS|v^tPdQWXzzo1Uw-KkQcWdH;lGNJd19Z64#}yL!DhdkyPt~gUh96J^*$y2 z$Cdd~rE?)mX^UxQ;uy{0+o55vKfd01u@f>#FuuJJSSWmJuwpP_FCp?CZQere526EF zlsZ|chz?2Lj@%i-TZC+P*(QzHU6D)}neK5QXisqM1wXvu398W_G1pst11@u&xkvZ;(Ov#i%?A)do{x$!|0CQGkoJS#8NF`3C;V+N``gnNQ z6yH`2jgF4a{scz=oOYn+stP8xkgc!yq2VO`Si<|R4-F$TbHnlvk|&>2Xkj+POkI}%9%_DS|M!gJdE+V(sBe3hN=TQ`;~wyG>8ELqH2 zo>6=n);ZtVc>TbO@|i=p&%vJCZrH=|4wX10snV0V*2tvafj95EIhvV8+S}PW4}T8C zTI2RRb6z$6E3#|i#ruqcJ&QJSAxzW|C?22Gn^I9x0pA9T9O5**E=UhYh9I0CvIANw zJ2P`;ZVrF1y{89Zw7=3o3-tv=fw^Fa5JhtN>U3leJJn9J1Q zbC~(Ny1Q5S*YQK>0|{{!Bl22kctfx2OG87wkF)RrU=eVgLusF!d@P!d);Gx9`nd6~ zt)nA!9b`C~dKExhb{fMgBEqir7-k{^pa$V3@Mf*;o8}f4+g@TYXy+F{Es4_$V3r>_ z9`4?!uw{#NTeWoNH>IVI9Z>l}(OY14yP(1r>UQ-y^Zg(~!0mx(?$c2|H&S$vsc6?z z%C`|Vj*cYKMU}LB<|X3JA_&H-m;kjmSy{8&F)*hS(>6A_@>LYh)A;Y*PC{9HV`VP0 zmakvGwzk%9o-Nj}xA{0^p1(n;#xR>%T9C%EzHUEakIFzf$vHL@J7_UH`}M9mOSBeQ zo1a@%Rq1umHT})Sa@MlHyl0ZJi}>kTcL&iI{#6mHq&IK43@qEtT%6JA)q=|4oYJ|5 zYV*0n_%FQ@HJM~g_+aC=ig15<8_fg_$=^ai1`2`sSF=ff*oSAIU3cP4MXQ5VQePk5 zIcGcl)9LR8U3LRQVO3RCYO1NV^<0wgM8$}QhX=qRz`Q`YJ$S$8d+^5?vIa~VmIyaD zy|2Y!2L<4Zs3<59CY)SNjS2w|zst!Xkn@HoiSv)P6nHtkJs}a1%U7;MyIFw3FQgbA zb^fOa^%R{0HQdvkd%ImnuT=2W6=$QJ<+2J3VPvuL4^X%{+`Ze~eG*C*;4ZW6;)lFM z2FAwv#iIy!wpOkUb?{u{CAprDZ!9Z=^`GC_pt;6&a}P)x~qsg zgx(#K!py^?!OK_1>4nnO*Y7^@rp&~iyU#Na*|n9mLPGob{Q~6hkG+x~%EMnj`+fcQ z^sUXXhrhhq-M2Q>KGZe%B-;G4YxiCZd05;*cX$COq8}3p{Jhg~RI@kE$;e`Vzuqq_ zF1DHezI(b2q8lR$QaTwe>eKHe$eHkf)YtO;{Xa0$_o1}s$`;?vV%BuTpaLEL6qQ4O z43_@542aVq7uP?4-2xV|u1>r>A1(tSAz{37R{!Fg+w23SB_-4yLOaKCay0N!6nuhJ;1E>)UKEkUE;@7TCLWs94 zg8z$68@o63Jdc#x%822Y?rQFA)n0uJdR7tj*bmz*2E9ND+n8| z^Lg!Q06=*~k~@|jnDL4W34t3BTs>bE@;e6(Q+Fae+bAmkT^bs>4SNP2+?G8t-~IgU zJd*p&3#N8qXs4_sUH*hB!X=Uke8lG$;RFDh0Sy1V#fQLq6C*abp^k z=XBoJ+NZ&Q;MJ4SAuOrr3n%Eo#$w@fmYCQiyYx0Y+#zsJfP5H+PEt-05)rYnvhp5a zP{A3$tISCy8gwS%FPlBSc9W+Ies3lbt{%;M@Q>61z1 z)FbBuK@}H8!ul&HsYbxg??)Uo*(_BdW0KwXW0g2-IuSrPG&XjfmGzT3aW_xTA3%e8 z#ykmnps z+3r+Dej%5im+pHw*}^1O9Ai~)?r3Q_gL6AaaVS~(pX_9^c)QcAdC3cL>GNG_?C-YU zs?A%MHqmnoxXs${AFWLoZjhe*{We^=3;h=xBNDzNyF$08)zWopFuTpckPDgFG3%RS zzMwFaExO$q8iG`xdh`{J%XC^AkS1X0>pkTnyT&Ct1mE(mQq=qifJOtZST72vtF z6PO-2b#JenJF~-IJo9|uaSViC5P-WlD^$sx&e_Q5doO{lql1F#Woc|O8iJ0g3MA=A z0Wh|pmy*AKa8k$YAQ6E^-P z1x++Yk#E@d?ujIGA#h;pJ1ebI`K8rqr8yNAQHn9Rz zm6^Y^*QA5>rNU(}K%1n-@blBbGRc4?Z?nGu?3UMykzCo|9~Hqpf~2I>dMi98gmyeq z;W%P`ch}3p0%dmqgIVB^gIMp$x6*((6Oj9zJI9R^V_*+0Zj15Vb#glY2MC%KL6_Z#J6a4{q^O8Gd0_3; z(r*h;VgeGi?a~H{3kr!OhfRc_vBvxx>g@72Zw@z32Ad26AmRGou!EAUIgw%{M*?f^ z09H7EZaL5QBjM!?)flwX>qlo54F}Z8)l=)6U{56l4BQ@y$oM;u}?b zh{pTldg4RD4=G(&A+hZ>iP|3c`{lbiO*Jq4{0TVX*^|FGLKJvQJ~?b65Q_i3Z8i`m zLy=v#($F2S;<9qY82{~?7lVc&->a(EuAUZkLKNtlT^PL-Ov& zo2h~Dcmy7i;$E>o4GJph254ki{>Hx)m=qa#?b_hCZ*?(*#r8-QT_#Xf+uDm!kxw-6 zk>54u+hga-IhQ!b__Tfnfp9=z+{wqV{)O0PG1j+j2QZ!Hb7}~ z35x%AUNwfD=!f<9Yka&eZF2nu4+uUrhVe(ph~{a%704P9d0&~+AP@1Xr3XVza}G&L zewXnk#`VF2AVI1beOzncgG|%n22>8CAZ&2lqB`%G{q7wl%ZLO2C_rsl*)Pe)uw??v z9%2bEA*O>RSxS=8D|PE%>p>4~2??;J7tWyT%P;zEL{{-`J` z1G0d@JKkcnGf^)Qx$%Ftw=141T)iH0KDs7~c&>V;R^!j_rC~sJz#bo0o0leK{`BVi z2b1XR{Sy-A{&`#?lPyjU6#H(l^fOEBl$Dji(1ZBX{ee;+w9-&?$EYH#m5(4K(n+yZ zjS0HBwkEa}jh>R(NY1MF@7^J`Ri($REG!bXL8B4ymbr!_dzz*Blp~cbPgHhY^h6i< z0-*|+=BLOw1o=zZ$>^=`x7jJY6k)_;2T$_Dk8RK#Tn8`$M6s4Xk>ulsMCw{%6-g9v z5OWfqGFH_Mebce|~J>`}H|N8`#g8SLGZ|^if^9ByGn!3;sJ6vgTg537X!>vm{TOG2 ze|L8u_=rk4(~;ifdh^X3GVIFq9RNq`>zbGSKOHQUk2URnVodXwY0QJIm02tNkW$!s zKCNq)EigG*RmQYFT%sCQRme{6G|GRO3UTa72k@E(G3ScY9S#Qf_$fydd2UNY5rlH_>=YX#E_V|ksaiThQ#}nWwR5HnSSI4NTo^DUUu^Ph z=Mi7ka8FPA_Kk3;Mk@RHkQ6_^+R&paA#TgWL?oCU zkE`)cS)W>sz*mdq6|7>DB8>xRe@gGqbUc6K#WaB09HLqVjZd8++D=|q=rvA zWC7|?j|n}fU#2tyK!A7Hl;c1&;`09YG+u6Q(>Hq1e5{u;)tpl}k6;rO0^G@<18{?a!=n#7k5whj*E54?9*qe)EDj_Qlai8?u~?2_^2#WTfXp@Nw8U9G8` z7WTjXQ!tXDFt-6~o}h=Z{7KmiJ*%rOKsh^<>wh2Dvj*m5BK2w*RE}I9xs%b*Wq_v^ zHH2Elhd-Zsot`d}`1KKej`Ds>3MJ4DA;9E^+-y(aWOl-~6*urW86`2jLb zxl0m}=dC1>UuH#xAXP;0DJ$%qWUQi+QjTBFG|)Q?IiS^GAPS<-na&Z%rtn=k z*AS^j=VQThhv563&K|f?R6v)0*9%75;4R#jmC8t-VOB? z>^_`G=VXSajgDtCzAq>gjvdd7eJLTXcePEFpI=9j3>NUJnmkpR|R-N5tDv;=k%lgMbx<FPg`*m!Us~&@V=uPB1GYGH4S3E;~`>`at^@- z*Hx?9)4k%}irlx)$wvom9+rv8D&cV&t(o@=W;KS#?=W8K{~(g(<&KVyN41?zTnkXh zBUnyHn2p8Di>sd1K`pHztB+z|e*IA4yR|$Hly?Y4bj9;+ zkbK99$*hZgzkUqTYi5>oLd~QMzC`U-z*YC6hV#*M3{{#sLf*<1DDA|cAusM+)k7RF zgC*6F!6I||AJcnp2?%X(PwR;#}HX^V5H5HFCA5XFx>3fkXnW2IV>YlU=*b zpBm37DF7>b-_1?#0mfVoTlWU~;ZL{;Yb89G-WU;JrYbhd%HFd7Vs8*76&h4RgeV&o z(inxex0JuVzfSV3{-3}ZcWwqY@7;`at{Nl0fLO}Ji=D{aKgoQ&lE$U$np?155V8K{_-~2TO ziUfsQx2B~`9Y?ERASMx8B=XDSHdY=UvIM@j&{M%+Fu>mwVPj&f&>jyTvPGbh55LQq zhn3Utg1Y)*bTazxYe%J4B{}||@8qPI-vc_3pDl!0#gee)!LmvcgQa|CeY5=NG7-zX4ZVK zsMF%*<7;{pSR{9_TL%|BKc9Vj3l%WF;_lQ1h!(PxHIPe^D79;10JdKH5RT_w&yN9w?<1Umx47MHGI0HhGUgz#@L zy0+ij)i>HZEMC23zc^*}Z8GKWon%hfLXv*_HZ=21Fnpc`j`TZB)eGXhL^4$B5mF&N zjzz#h{g+jv%nXF(n$-wW9aXcL%2^sR!T0E9K_FD6+0J-Q?(I(DN?elj4X?=Z|5Eo|Vfh2gtJa`mB|BJ@2}5F(c3!WD3h^zNMY}@dO{$aggdTc4 zmgeR#ljjCPKyNJj6aiP-e%{KDXZZa+qkA6Yc@3JYp~kRI;3a_#k-w~^uI?X_Nq~Wg0nES6L1&X~ zVP{8te6d#V+P(F%g^K5yAk?dSc8R+odg6oIuTfy*nhN`N6d8of-YF8Zf$hmu1%i@k zD*AXc^X(Pe{ex>J_@D2mlmH{6sG6P~!^|he(lyn4R|5OC)-kPm$@tv_4MD;QT+q$0 z^?2R?Z4{W1qrPr;JC0twZgLO7Qld5Wrh_6v<+s+o-#526xEA=b93@ zcBiAw%VT%i;8~~h^p`mh%BeSJ)he9gkiF4yvdm|;3?$KW7KrYi6cfPP_c*>R1g zF9(J2x2aT|FhNCNW@Op1RGP13A3hhmO(m{tZ16vU^I>?h7=ml}FrE3VM#U z{%!Ys-CLTy3)c8G587H=Lqs7$%)Q>Q0c_;-eR-K{N21~Sybg-AaryKmW2JhXMbK^y z-brO6Lq@c40T6@*Y6fY;Q-r6htR;OX)n|(d z;kUb<;sOFMUm_zswka|F(=NK~6mTBz<`fo^5TQJMJWQ`e(JL8M;~{MJoV@L9*@2+X zxdYvTRF6MD?#wL8y49XN`K_TM<6VRFKf3`a_|DN@)cdJ$G9>-T`+BCT<&^G5{1LYp zwMCCX&|LL@ZR~rJ$A??r&m({eDVNJ0T?^;fVn2n z5cS?WEsgW=uNzXdg|@61>Q&YC+ZkM~VYueY!Rv~b_2qk@$Vw|MmYE4-ZX^~ofuaju z!Qb$2k}@Sjuq;foq7=RKGlx4Hcvvt=C)yx`Sw4@;QnjGbAo4>Qm5D znO2##v7Yv(CZV-Oc)%WdZe$AghDM(I24~j@T>11Rzp|)ERhhn5RI}`#?#zD(f^uVW zoW2TtY*JZjAZPkDK5^y-tA@Wao~T!tvH#iLG<4hn5#I^f_K;FdEiJkmo*b@0zzzu% z1{Z-mGnp$0w}VbXu5Jh0`ARBQ=H|&+C^tQ*0m$g2UGIJsk#WV7CRYx-KK|}haYHh6 zb7rQ^kVeE-6rralFRIhGPEDl~{3ku*#BA)4Udig9H@S2r!7OR!h2iXf^aq(~)~V=E z$JNq-!p<{J688-^A@^9S6>lMW``oLfke+s#o6NlT9>5DF1dAzP3i9)iTj%C(Oij(s znw>OLm$3YRhk`MHO;cUn1sK%)F_K{VoFb=B^kv1`fQoHzeYRtP zP9INTS}!O+bt)*Ss9*%@=N=AW;XmW!A0N>N4lnZ_M4&)UM0V5QCO7Z$kGP-3*7U2Y zaoIDzUx#3R2Q@3T;EXM|rQ!&T+JHzoKc6E2a!@H#18z>*OCWORCOy1u2jycvY-@Ry zmDF-6Ev+_g())C&JEniY>a#VMlD$Gj{Pqdv#bFCa4>&!cR9~*;gxrpklr*E%T&&5*Y)^S z;$pZu|E%~?$zu~2o)bb(Ain+RE$(KVxFNJKG2K{$@hWOo4A5$aGV?Earzs(GjwT}M zY;am`Kt6^Zth(|v_@4>BL_kCiamELR3T&5W7B^3(qFLGFi}3C9qkE7M&ucPpJj2aA zKGv2bc(BIPhhXi}58h<-IG>0$2u68m_MQ^PBFM?<7*4joWW2!2K*8^AS<7kzg z!0P*}_l7EJT%0a!qW=jY7Ql0D$s<_&A>~Sx>+xEwteP96#0D&wKIP8Bdd4Bu>ki{Q zn45r}t`;UgFQ-MID_>y?WgM{|Fqm-%n?EV>zktWQ@<^TTr1GWnDD^wz z0Vr#;0#H`3b8sLK^7fi9r_p@qHSr>1j_ybFqJzu48A~?*g8YVgrw7qi4MBFhhv%-< zlI&u5p6s=T#7FHVgJWZI+++xtd;h@4$7t=b9L#%shuprwYo2;q65E11W>9V;G-=0E z%-8SgWk1ZG13+-a59F#CkWXbdyJf4xsf#mNzr z8b3~tce+mPY(Rdx0j?qV`*uMeRI+}37%Q9UnVF{XhQ*DE^Lru00tLfMkVcw!8H-Pa z5)v`9OI^DASC17jl&#uq+DJ>sTLtIwuLplJqD}{T~_xbNT#@k7`;{+8G8|iSN`>)=1-8AM#&Kx-^+@C@)1D;$I?cO7?^!@T0REB|-{?_=`MC7DiO zM|PFnt9COHc(DB{4Q+rDJHdE#KZNe6r9-tC6oj`MrX8eqQ7!%JaqQegHl=c1TK97m zXnH@T8HlmC5BQ)CyZ z25O``mW+KBy#J4}HxH+>?cT>}9t>p;k)6aAB16d(Hk-^g2^mY7GnLF`h%IDJGLv~K zLWMF$hRkDR4k7c*Z{4Wg_wyd#sr@Z=Q_`Gb&=mXn6cKHzU(He zPhq@lOHFBA90SY^dP;xFT(w)bzPGiVqxlBy1!m42 zT0|wm)J}ki)5lSyLOE7S&;vwFb@edq(kZ~Ahi3KmpO;lsoB?B!NAPE@T01*me4%pu z^#BY_T<_c&fg!DzLB;Xoj-%t%CvtTda&kfp4=(tEK-nW-qu6~1d%}ZLaSGXdVh`=WEK_wyK>2EYo4Go z8TQDwpvtgZm$AOPO(4J)fH=7Aufta!4WJRZ6h3<1fpu|ELR1S}Yk0^kztQt4pR z!COEG0&GMihO|X8xy2_6YXdN_qa%#6dOvc*&@h4O2*&y{y%*&#m|p@7JP7e%bPm^9 zg~BUAE-q5G%142u;$&TT;awiWHPpNRQ^JZ2u%86TO(v6t;uZ)?YU`&SBw(RzZWUge zyP;JH#yA!GSHi->bM@_wkDLifQ!I8MO`(tqeDCnS!Yv7jh@@?Akv*!KDb~tfNO4>k$$p0i)+DbN^wG`WU1)W@R4#b!E*}mP$RAKZ zOG~%JrEL}9p}-9p+}WkxZXHz{8%OD}iZRc&@~{8OB%krbYk^tY)4#^cg+!$ys%bGm zCx!z?GP0cnhX)!$*o@U}c}*M+NTV##{Wxv5z8 zZtizWi>hyBysPHnDuwcZp|fG1RKg57sPU7dk*2@n=E~CK zbbzzv#@Ro(l;i`lIP8aT*DB8W^U8_9a70YWy1QXq)!+yyPg`4Y05JS%WrMkJMP}o_ zs6$!W1%vU(Ay{ndg4Pby-R!!cX)?NVr>6fE&?xPN7+$do3l~EfJO*@}3MS}8qZEIE z>n@P9fbfZELRg=cKi+}e03f>Bw@vCB`Cs4W<9QFi0?Yos2@mDpGM(xSTL95O#}oki zyz{f|kph-LMT-{2|C|j`D)g(^`AFc_5NMMMd^R~ZG#Z^F{pOyR3=NCi3v`VvMWw}y zwrjvGfyr+MFWbI<_q&OVY^>BWF}Omv{(HckmO$hPR$Eooed#P&IyoO(`GpgHSI(c8 zE`h-!lI8x&MA!@Ap7usUYvZY!Y+icN7sw~>?9@TZ*8(MDpw>({d|xmMQptLwasi!> z6*)S2QUH>v>OCSA@M!#_uG}`TgIhd@;v~Wv=DD$U$lCFy>-jU}bxu zS^DzZfB+w#KIy0tFkV@u>;g*i+SIwBmnkZmy;{AtuwY9xy%xSgXVI3dQ+yZ)joIS; z#mD=}gt3CB0Es54oUAUe3V6JWqrDDpmGJ;M*eGjI^$;5?>*cFgKa`YYWn@r@es`K5 ztp@>kO^u9$!y1%Njbos;fH&y$;e}ud*JDyr63{v4T(zOw2#%r}A3htsEp}diS5))} zW`SoY2B-qtnknKk*W5Mi+_q8YkaZEzY)>*9^z`*{va!uN6vG7z$53X$^=*ppf6<}_ z_kbh}iTAy0&o%a!oZ;>P+6CZT<2K|unMU}_U&?_O<3F37nkqvd57@^3P=kh(hs#wf z9*ut@~-wx$G2Fovq%I=ncfmV769)*}sZmDk&5*+Mq6x%QX z+TmR3JVG3m$>A$ME*O;D!CnaR>7wZlWpaZJ86S_j?XB)d0KN%rJ}@>bG;ttR43TAAu{pU@j+DVftv;qSW97n93=H$nR*cR$DH-_0k)Sk+=278#UM z@u){m)zeyv&;n9zgsOo&o3UT6ocIpa~g9oYt1 ziZG;sFd|7$Pb|y1uczla)IGXy0X39$f+~sr#8K~xw3R0nX53#kfo(ce3M@3VCGX2K zBy;y4qhKawiR*UX)B{r}YeHqY2fXEfQ>30RBA)8v zTdoW)#2lt@iHmY&N|nGd0;Uy|@z5b{-zX>`ek)j)SCK)&2i;E;dqHxtw0y?6k)a8} z<;sPHQkz<6G}P6#`n;4m7{|T6)MI(aWq{(#DdNiyxMPz})6)xyBA@s~0PV9QA`3im z2GDY{J>XA;KcKI{OO*QAgGBp`WZ==KhL_bFDF@xP;ZMVHspJ>Ts?ffFd73qdD)FV# zFYC%$Jx8KYgA_Korwil6T=kSW6RN3w`_XEE3Hb*?L{1ZM?UmRRI3)$puat)V*;3s+R)&502vLQf8N})4!4<|66l+UzzzfC&@{qMgEXc7&_#N zD{JfPClfm_UA~On`4qb~Qs90RVW0-3?l;j`4v=QebD6_1+C)8X_bo#6C#OKybb*cS zk-h!%bkwO73UdF>1B!tTK|LoNNaUaSz1UX?(#sm7*qG~&graxleR4&TrnvL^FKU2< z3O#k-7;KvH6hS=t%4?`}$-4$B(`3?Ns+OJ@t5m zPRO%9;08Phs(evVhVojfHTzaQqR*l?S#ZyGW2^O@9$nIJZCs$ju@F`5!>FWE1b^(DC?b4yE*jNHKCyc1e)p6(8{r{;UYe>O}GTo_+O zMyirermGMWES{SL$q;Z3BMb$+Zt96GoQFMor&jue$ax0mEie0TFNiJff2x_&lW-!Jr~*c5-Q{ zeb{1jN91J;GYDi8BVN5~l~ZO8*@mTZX@r~TKic2IJb(3GM`lGv!5AqXG?`gxX%6+v z5C2{3)?&16WFL*>%|LoWH;rg4kLu_n%awhkX(=~&4@C4|=Gb9}d zP|9sym;&T#h=$h!^M2Ulv5dzG;xO(k?FL6Fl4A!Gd4t{`hdw)mJMz zuqI+OtUc`A68M1^K9$^SL!?d(iw8=HG(2;D>5FLq##T-Ie^6k-+Ut?4E8~neB7NAa z;t`<6dV}>s+H0plnt5>$gn*y`U%85%Mqs^va59wF;Q!SZj(DXY_bH-%11kYgOw81h|>jZG~rj88|ry4gGT>(-|3Uy|>5$V2Nm?9yu6 zR$P!O9^FpKFBTodAI_irucB$L%nh>hr3Q!g7);rHN9w+OuI?Gs` zeVrstQ6mXC4P+Q|s}8A|5a^%?wU@rO+nL`mK!ONp$#kC4ZqJl%TWBN`@1L%%#jh#@ zlc)56p*Y*Da^eg16e{H?7Hdxt=~NF?k!3GOCWmf83G6RHPkB(eKdQ~;DYYteT)o@- zH&gEzf=@9jK+-KJXc%Y%FCB6Mt`X1=jRW&5E{<^TdB<c zl{HzLA$kI82r!KY2G|=Q>;PsA3`OK#T3lKJ`Q`HBqVbk={$M7UHWw+x{n1*0uNj%? zVH6{?STwYr?H}3N@?N>p)!7L%Q~0^LnL21I6h5BIr3_Sr=F{uMLuLo7X}^WJ4dWej zK78-%?+-A8=W)l-*`3>mzn#+ZQ$3|bA-6>-?j;H4Sg`_mk$6l3PCSsRow`S`ySE@3 zcnHZkKB%O$8oED(gD01ZloI(03c#ghc`zVlYy!}O|K~e)Q1Fv4a z%E-t751LuVkJKe8@T|StCI+ZaqWpJ=c9AN5%s=kM0ZA1Ig#Upz0F(r}2Y?yi>%xri zg#`-Td2kMxJqU{fe#Ew>rpISc^*~?{-qru<=<1%W2QRn(_O+Uud5H0L&mGA%SLB*hC1#zQz;N*Uq zhDMCX3QAk9_b}gSV0_%Qv<~jO_)^Ra!US4^lmPy89J^F4)ZSCn@17`VRC&ERYcHnt zX};Eljw?zbnb!P*NCT3m3;J1jnz&HNw5Dpt1-2QZZv3s(d43 z9UyaUb3Nd4Ja!>nV3#N@BsL6mk(wU+LpAFvgfW392XnvuWf3(+S40a~_QA`0hU$yy zGY3mH0U-*iZ#gnuU0ndrWR2dXq`*8_`QsIhvdE{~$EuPO=5<9Whsqld-&t)rGPeF+ zFr?@yso=~g#|5>-EzTRCLgwNwnlQiFJdj}(>~@D}z=iY4cujly>ewULT?3=^8D^;! zJ5p6(vz9H(U3quR?BMAP@LcOM1CO7Tveff8yq-R7s=4f93=RRh6uC?!fG$)pt!Q5U zr$9I*$m=8^^;8=sM%mk^Y)%wdh1`~fZa+#au&&WpX!f+0A3qi27<3K21pOR5O}0-b z5>e9L+>OF)?kIUV#ZD=6a7$5m1a~AMlu!UuE|B=wIeqUHT0OPAM?*Az*?x5#X-Fu( zWxuN2*iMhZucPO9$m~+>H?@G1xth2mU!vE79xqXw(%9%I(3)E5qaP`2{>Ier&r0;~ z{oU@Z0Py7m>L<^kiWY#}R>M`v9OaHp^t)C7tgc)9x1I4bdr?wcOrz~n-h{F$Wzof) z+@FI$4d>1oxIh!Ukn#&Yoz>+-{Hre%AEQoLf7>WzAN-sPVM2E^4ZPwfCdkgF<9iKW z0cZR~&*fBYgKJ;J4jWoO2X#*ejnL)GCyw*gD4l!dD~!TXmJ+MmHmLyNlu(NneeyK^ z5Odn5ZdGX&=RXwZ3K=~w$HHg;7OqA?33Dx~iRFuDM;>h~rc$xAS(~>2Q zp6LN$-A3s0P2yzLWk`k>$#1U^9eNgq58o@0o4dPd9S-ICWgU%5gYHrkbCtz{BS;T) zd1f=U*4xIsuhDNF>MLVdi0K@Hro;R^^+wRgBPFB>lb3+O9_Fia-1&_?+nTwp!5}*~ zO9B+nUL>dbjTS|FoAxc%jE+$bB0J!gP<=nvw&jaDh30)DC_jA6*(ObP$l%0I8S~XpPN%bPTm=1=Atn`x&LyMln75QjMA`iE8uWA zz5P*kVj609G|y?I^%1LdX%LC-JB4s7yoA&tmg^sR1Uk_-%^R#_B;3QXt22fJ9JgIy zs&aI+2q;gO4iKMo_oM9twE+{lyhTn;u+!+RzMkoh;Zh(`Fp*kW(PgD)chN3%IX~l5 z5~TuL!@S7YIXR`Q;`)?>245#=hVBdJHii%S<>J}1hTpQGLTL@xu(|FTTREl3N9Md9 zc}gqYm6%G?%TaD!ft1JMGBSv`+FDW_nemBXacgYq34Rvyugi8) zHaND;8~qGlH_C3v){FjxIR~~Zph%>V;1(1W5rLVp@dX_lrZ7G@WA-{h$cnYU@Ji54 zH8szZ^gaM7erpT?GInGR&3)ppALAMU9W>Bd=(&7MH~OT6r!IWqa36n#(!q=u=8_B9d_GswC zOiLN<*1!DywFK0JpeJem^(&(-8!$_Vww9=Z9*(UfFI7;HpcpqOEw4l=n5D--(}Ql> z+<9Zg3cOw;sFQ^AQ;G)S_ld;HZ$6AUieI>W4QbthNDNWY0(ktV5d|Nfh=JzNMJ&P> zD7Pp$?4hRix@`kw2mDm~LDIJL<;xL%a4P@?I{8yWA=aI``vlb8)+Y>!4iQ;VSd-^L zzkr5Qme>;fl31w3MpD|UQJzu+F`VKZX?da!`dmlKZ|*q9r?c=P2p ztBqBQa;EI#uP^e%7CG`{@=he{UdAXWkf5(0KXA>rC&q+%ZRxD3klv@yi7uS`$oiT1 zDoNDfwdcgM)C<#S%u}W%clwNguHV5fZ4Jckw(ExoFP$e)oWJw1S8R~qB_l@6W z>Wqq!eYi$=?2Yu_Zy^y`adSAqocKf@@Jq1^Q_}7BpWNJKHnm@n_m9ed`M8L0_fC!M ztWy3WFC6t=Iv#6sDlKNv?dx@=+1--{SegbvA!7YcW{Q8lf!wrB+Lq2^`iL%dWf`6; z`+kC-+|ehfV!4yWUa-=8MPA642h&b7OXS$^&hAkAEDrVjtMF_KfyHFzk20Rs!RW}| zLiY{Lf{P36*|X8GnTOrYbD1CQm`A$PqWz)&A$<`tWN*S5yeA#pX`S=KcH>;&s6rGT zoJy{e_AvD}(VSGYNSfZk#Tn5-`1{#?yCBJL0+?dMaZkNIAUiE5tu{|K2Z!Or@dKX# z2n7s|aLRz33pp5bNE&dh4Z8uiyQoQL{SB}NKA~sX)`=&}QrH+jyk@ig-OxZXSJBDB zMW}jMzPi!t3F_d5_pIoHO3cpJwZNFi%0NM^IRQzE)C8- zylX{iS;Z{|v#)OtAEzuEp;4IK@Z~AZ`o@LUAevwZ7Wzk#5fZC7L#f_I@mlRk5@?z@ zIOMHU!9WU4=ePrj0C7@DQ`4lNXHh8)5o6^Y<^(~*^^yDXO3WN5tN{gQKTHRnF_$K? zm6O~dW&Tf-9n2O_tCCWp9)_9&;XqK=wwvUKg@ztIpcN$Sl@?McmqV3Ol4J>)G$NW< zi|}K3LIs9%*xTz(<*kJKNjl?O8$GYgnm2p2wx>sv++@-^sUa<0ouKOl%Xqsh?M{jd z-UoFqWG4r>ro>!}5YSUt|A2N!2QypN3;(zawb))jlwTA;xhfux9|Bh>w)@H_$7BG( z{EJ_X=Ai;NLTAdA_BmeI>GDVInlD* z6f#GWGA$_y)0=O*iv8%;{(R=11jNDl+n?gTzPe4?=^<`b{E{)O*=Ctg@h+KL9MRbo zA{DM3QwP`vFJK$|A!)Fc=!ll&b7eeO?xCfNe88#U@TZmgL_vzecz$?HpwZ5D{uvuS zvG_;)kVuT82t7+XeqDA#arD1lWuPqWDEl*;!J9V=F6_pzWjFmoetV(2gv2}LIEQK^3%%r5%7Tmze|SGr@{2Ae~t3E z3i)Y};j?%VSaASp#?;i5kn0$s_w7byaDdBASDM01@alOU`k0l1gZc{iU(OAEE&b#` zIJg1Guhs0gPF&-sZ^@qdphqY?{D(ygY>a2D&AkPi{teSVVd?kGFR0**kaCsnsfA=& z#5|5E|HA?9#bH6?1(yJZV}U1Y9#;66@W9qbPj z<>h^Ko!Nd4hT;bD-VeRaeYV2hIj(1N_f!$-#U>iYI@?Vd*AQ!Q8% z1yrBql4XqUnX2l_t+prt)z{E_P(}bN`{I8hDXN)D5>aWPoFoFRVryxc4P3Ykb5-DD<~m+#z%j zaDjp$5I6>R9UVU;CRR)?LO4XY85b^qr4BVEWmHUz?yXw`zkvz}9cd^@oz|C#pPs!Y zE&X&<_bRNPw9sm+8|qZ^k52$V7xegr11ZJcrvPqLYM`DIvCo1H4AiZ_$!x5v)7IA3 z3;4+DY+u+3l8W5w5$ia)`(bY3#&iFTt5kbQ#>p!vq*0orsOC3Y6~-yV=*AIFQXGyK zzw_y2hjE3h4#oJ}d7HZrldGw>{rz3kfY*C9oBowzgP>>w2b`xpaQOAmPdGLYIP-v- zYdJm1I(mhluQ)Gn!AF*fgNy60;zOlQIT4h~V^g8FnT>z$%GA95`fjbi(8tVCI zp9ijB~$yoiM4m6lj^=!ZptZao)iH7~W$*hopC(7Vv$kd@GIKa$L9oOsvV8P=Jbn zTT{&iBZd~1mklN8VP(A~Ao3w5UeO~Xck0do9=g!tgBZ+Efz%1u^_2|!@wc(=<h-PC890$ikZ6mQX06UkK)Jtr4xFKMQ_uxZu zX({8XZ~yG1yNv!hYAB9v2SG69On;67x=O?nMV(bC#UDPHzU{imYr0iw_J7QUo?K%L zK2Tp8O9I$H9bnsI^CM$pTr1}yNWq5ViaQ|&#^|M|LR78*tk{D z*~Qa9B2yKF0U3gR8Zvm4v$fOOn}u`Jx9B&I0bT9APVdq$?T<_1dzwpQ%^BF%A{-f@ zTJ$sU0=Q4*AA7fRLbwt-xIlOTZxlTKm>Ep-hE`vANC+5K0|%zAPR=U3a%h%o$yVlPawqxc_CW^xk_% z7mxt%%D=vRJ|TAuAzQQr0<98RWtsV*(jCK~doCv6k_BX_c5uWO5CAg+n7R!Wx`EE) zhfvn-I5Lu%albzH&oL;l5Lg+&xd8{Sq@;rqMTD3q7u%s4T2;XWE*RgphoR=4y6{FX zGVupYm{nNA;6L9cDr{+iitL3Zc%`*{<>4S@l2tN!u`1I)$l=$|*WVsQGg|$wZ+i|8jo3s^i@3IqM z(Lisqu;9FUm5u{hTYads3FK6OAq2HH%YOHdV>iIqYRUHDLueR_#B#j;4Lq2a3J)NN z2P8a28E;kFgA?2;*-(kzRU+eIuilrh{{ZZm zK{LiEO}SfreiG8ZeY?UWA2iR<+=)!CHj8`xx@vOQx%ENzM1^!dJl#+rDi7BICan3EmxN;XBSFi_7YB86%bMV{^GH@y1f)f zCeH{jmfZ&pxpO7nsDlr39Pt(fjs}DMA5L1QzImBXp)Kn8ZITF`tq%`FB`e(Xf)) z+Ss@^^^RDNS1<3$a`_aEvF<+A&`o%CXadW5DOEW!CkyABBA(Bj(E<)6I~@N+rH>D6 z`L0ZAKZpWN@Sl$q+yEabd9I8F5xKqO{K|MJSp(sY< zi6j%;1q0!8;(x}Ez_^hDW8m4t;P*|OMkTIwzX&9kSJhd4vSq$z~gLFP^okuyL#wg zZrs}D7M2+tbMVvkz4y=hNTPykfJzOsaldA*g*c|52w6$gn3?TwBw7>C;3bcxhM-l< z1p4$$XqKN{-+t#sdt*o~acdxzbNyQ)7j3Udpa?S9$t?|6q?VZ5rDy1iS)*AtW6Hg2 zVbXLjzqWveR<2okpHf2#it=B- zby78ft)u|8*+XlhJ%tMtPgNbiguG9JYH)0KTzk}csR;Amoa^uGGudcU1CxI#x zI>c59%vZ^JsI_!%-ekM*Xm+r&QKvSu@Db6Wav+v>nU&Pk!u>q>+ZCLkW^qJBMA}Yx zP8Nz|K-r~U#wlU;Dbc54u}TzEYea@H5%DrMWbjP)!#zCsjZ{bV)vQ z>U8n`=BLP9XC-%Z0iFWO(2hHK-t{9sUU@5`j|q8v%{y8cL7|Ox$CoP_X~bbdxWLw1 z%L0}4_43&r>#D97WRg@AqlHciz*yRCFt0AQJb4^gM=v8Ioke04lG$r??^G-?3fcc& zTQs#Q8wuZ-`^p92r)Yj4YBVA`xwHOGqjrRKxPET)~eW2SjV-13+bw&K_<`bHUFH-Xb(wZwP{N}2C=uBzIanlNwL zQxP0jL3iH&?c2qR7f&2HFn1gZ9Au>IEkiOZ&D z#)Wn>Q&aMu@u{i${*gt8$}*T?4i-YbiiWu_TE^?L!J$3*eRzVUTH@WtXyg3ut*z~i z&0pcVE=Lc!uSP%jzMP`O8qy!D!FtGXeJOKY3vBZ`!G^NlsM4s?1rV`HX)D{bbu!X| zo+(;C7SZ47H?qNzpu!IfpmU?HH@?t-3@!WjIBcf`=>Q-l&BsV3D9@&psoGi%8R&0` z;Yf8CBe*>N@7~@>$2Lu+s3$KTDsC=~BlJOMqBzV?j}uk-GgNn)U3@MSB0>ra=%%UO zq?Q=Al*QEXWPy9yubw+T<=pc+nHXZRNAbI-_`{k&9&GVSY|NZUEVlX1y^viup^6lUso42k2af|g9wf>*w5=9c zA?Ug>D=owzx~7hqvlsH54(T#$C{0pzagS5F=j^-*pYFj2;outYOKl_8J|8N`a=CIx zpkDV@%${c7{tY^5tMC`9y;}BR6%fBwBNu4+|2ZVPx03{xJRL8?h=11Rz;L{~ct zV2YGCAD2$-lHtQ7Z~&N@x4azEj|EW~AQIpU3W%(W&kC>W$pZL4H^^pF zzmB|z?Yf&SWTK=((DR`v z!pAh5_}O!n8I#b&xJ4-Q%gVq&+-ypBdo8@PtnzpsCI9sD`f;_y>rb=S>U5uFpj94l z{r>UUeU!zolfn&u>57Hp+;F4o=43~L`w6u~O?64r&sT;&u28up4xd!C+}7JJj@qA) z)#T}Uum>nE0iH_^9lEVze5t+Yp!09WX=04c%xw;LByP9w3}nwzg^in9*}3AR z0pXt|M6m5%KD8rui8~xFsgO%)Sj1h~Jw)&ysqQS!(%XYH^z!q>QF1ddh_;5#_miMnt2HLN{ zwLZ`S1ov4=>r{M5|4JpgIjQ`H^C*{;I_3DxrMt0HpWP2{2b@recr?G(HPuh%gH}mm zkI&C}g}?IjmHTkRAdRi$yM*OOv~>qZ553y|CLmJVzkK}n0iosJ7-G1S(@+Ns6kqRW zfA?59p(dm09!JzWrjD{f2lX|}C^+f_KLjanVbZRJk!Zy|9e(vPScKG#N18JArxIqZ z2n|ziVYX9a|B^%Tpsqmp*E>8B$%-+f$GHu_IJ{Wn1=liISKA8S0#?I3ItZ)p(`Cj$ zLsQsYS@b^qx?1AKoo)sp`!fvda2@1=zz)x3se&PE8O;tgx<~`A9@*2AgXJJuf&g;k z@~iWT(Zl_Qsn2ofP*<}!JoC~EIhOetm^$yBz$t_g$Uvov%-@0k|4oG01s=A|&07Q; z8FdW?saqc;C{yE`Gzrp(bX!xbLe(&|)*L??&nC=5@XU)w`A|$9fRB)+F2_QNSqk4 zP?>sr#+2LvUQYN|Ves|_V^4T0pGC0F(SZ@gOH*3KIx+P|*$=F;rbkpL$A9+R>8?V%456qU5YI6);*DsF;N z7$|cvon@*f_O2H=OE!_?250>9_@nW8f|Mh0y`#64Fe(p&(aqrCb&;m7pK815*nxeXAKr99QZ`)~ zI)5iBQaU4si^kbC>bheQr+{|m^9?$Z@Aq~2BSB;4O9x>QxcLHITf2Z$lVa1u=v603 z*q-~MRWJ$p1h@#t^?03{FQOVh(yY^w{ipp{GQcJyGyTTeOc3&bKbBHZYIrU$mSoe} zf__HhMXB*}f)ol|ARcl)J6I$@djQ{tPC;b-D9}iNPqTsho^0|4%w||+`6H2(zL7r* z|CKc65v;~ATuKmh3DrhjN>U_ho)^{7_u;XkIzNJVQc1Y7iNN zi|$p}_9Qd%Acg1Bu%ROzuAAv^U6s~o2+#-jas*Qm|R{z8;IQ!H{y$wM8R}= z5N*wv2lTs+rv7sW4%#u<>uFmj7Iw={nw*Xb-W!s7-ea$yT-g6o0K_J(7zpvKk#Gp zf3xuE_vmJj1a?errfSNeLmlRZeG1`=!bFyU;^QCy6wPOweJ+V&vZ!lpq$4_HWeBWy z-ScGQ)6-O6OG`Z=sQ0^W3zwec?%c~($N` zZ$H|RNq7awKrS(Y&*HP z5RL-lbfN9gbq1(XveHq|)f~Ahwkjtl2ap;v22fA$%!wnz)N4*9p#KKs7u=|4jZIA_ zVH0prUFV5#TKL(NL<}ExH@3IeFq;6|UqEw|T zN*D*Y@&P$^L12r)-0*kVP4}RzKNe$QX6AkBKaxui1DX%tIT@P4v%EiCW6)Nxr@OmC ze`#^ic`W5C`oFeYl#6oqZL+@SH`ciK^;;Fp;6p@?Tf|_uLr)+}fW^TvWAU}SUB%`6 z>;u0DBmYabjJRR1yF;fb~tq6kH*<-`=zuf<$|~$!Bvn8dKw&u zvILrCpd?&!g?XH8xIk#fRF-g(AfEFSCQ_c=-;$PfCVu{0f$(f6_#HU%B~`c=7e$|S z7GB10l+0a61%3fSXlM9k`nCkl0w}}IP^%^4r6`Z4@HiImZ@2fbRx!AqX; z^y&J3SnQWvBMXa*KCW}Qmd5w)oiNq;DAto*x+Q4E1X3uGm>HN>xazmDIu^5Pp#;dG ziyLTM#m<~rf^~WegCkRQ z1`Ae=g7BZhPhldw<9_n!rD+j}P<#nBLfpEax8(>Dp`b$`fDbhXLB-h(`Sx4^Wd9{0 zEg%?IXASY&NxyDzeH)!$g{)$03Fh;{wAwjmmgJUGK9bO~5EK-w2!jg2Iv<~xlLIp| zxh$q&;}6_lxqA9EcR;7YPBLZnL?;79=>;G4*ma;~X>*28D2HqPjW=*ZohnID9>S-i zuF(5OM>EWpL4!%4RB~~jIr8{!=oBQr?pBYfT^h1Y_x9CDAmR1G-VN2E7C1O-590K# zcQ6QQ`#l^cn9J(C#;aR<@;GqrE6bb`zKmGIc7?t;Rf1XDnS^BT_=PbRxA+2wqud>EWAZt)} zm8B%5GW^|w3;~;q21f8oQIn+@yd&}gY48SZPd=v>jlLrpHzY`bVtVIhh3Do>di!=B z%=JJhu>q3OuOqcwc^VfC29hno^5yG@HPFXhm#1M0Duv;O3zJ~f^i{P)C06hNyL*wQ z;z5NV%)?Njl<8f8;bm{S%(e!`Z8d-WZ1yk+K=Ky8sQ<}Z4E4(*ae-M^C*BZ{wbLz7=(>FwR#k5&Ge?YfUEkXnb>TFF@%f;0OTx z-eAV-d*1||h8^K5bdGN_5gs5$Hq+(*OvXUa0N5&sTD#0(9LFYz1vVdAAh9?6I=Lg5 zwcA*ECdbHLpURE}nX+^D1o1i<7{DEvE$6ZAoGnZROr{q7U8Wc!BWYlnNy{ zd|86uo2BK>a+XV62M$bb? zhTvDZUByr`CA;^)zY)ktUc;qsL_}&y3lkz6TXJt>|I2saw*vFh*o2oO*$}dpEZSEz zy!1Gwu^jsAF?-f0#17MLqP|6h{eZC+MdO;mgGN!6NQ6A3#DSv zdouMv>!Mx|nr|(EmoGHFH&$i=a)GH%CwwbiR-S+G{;c=qHcu+9){MXZ_9oA%^-)-8 zF;3W|N_PxCSlI+W5)tHbCKC(onV|}6`Fdj}e)l^(OcPAQ9`_N(DDT9UkY4ZedKMO; zB8S|LK^_R@m&(Iil*TqTEt88|I1U(YE&OPX*>xR4JQS=@O7#FH2=GTW06-TvGIe*DQkVC${J_lZlNXyaHxmzqBlYtqjB@Y^Nb1(B*@4c~5&^Ju|E#oPYhW-4 zFh7*N`}gmEEOw^!+t|GG>ReJu2?%9<6`{6DvqV=u(_27e40-$RUGfCn69>#kPX2Jar zXvau6NNB(*akiNoe;BN7C`CAeb0Hm1IRmGaKupG?0Hu1ogXw7Dhta8G1Ys z{w23=PeJkqH;REf^|H$%hd**>TDpZOUU}wx|318x*ukB1ZP7?Q&xSWJ;awzNy7h1~ zpYiW5GlAPjm}EWW`Wj6VA9>$56T8n-Z~n?}BHK=TmK9M?}FG z9@r5h;jkT{8MN+HQs#IXx%V6vSOJIQgB%9TDL{n zgA;+&hJ%jma7Azki2wO$ZMeW+2`vbEUCqi-y&|V|H(-IA00)O`=Zr>b$HIMSZeW0N z?J>z8O~*ncHSqM=_SGm=`gZhbd3ayomx22S+&^QP0ebIfYGQi#8*V_~sOWU95AVTG zgGWRpS{E+#XHp4`V=d#hp2bM>Sy2&ce4vv)($~M1-J$ymQbS$zJKGAF+;*8-=8A6&<7j?M^PL(SG_rNrd2Rs>aIN;$)c?A*7U0 zNC77x9VS?p)`OM{Am&?6U)nNneNV;4eKzEegi*Deb`Iik-{6Dl6g${0Os!dgCjNuH zBm&-F($+@O4ad4<`l{D}v_sat&rP--F!#`-O>LADTs{b~OuK zCz(3QMk^nBn!?Rd>Tl#4R{=pdsH0Y4`+@)z zV4@9i-%1pf%UT#DrP`d!P{BJ9-h+mA#s=g-9of1dh2?;$^H&81-+k6QF*#`~_~E+W8aniS zVdCzqJ=uA+G5UwJ@>RdpCD)i!WS34#XTNRC|A{w~o^jCqk8hQ__N?)#AIn>0XYu~o zS-3!O&~g8)xAg#W;xi{$dqeAuAjEQIY2VxSzReSH{T8DY9@olsMh&O!${scotk;(;xQVRQ-E7H1)a^NTuJ>(0ML$6s>6{AVL>?OZ#TX)kA;c zGA^)Iy}(f8Iot?%^>=ECAlLKrBc9F0(Or0iGjBTXn`(su z(cl0UL66biSud~tmi9TX=CL^HqL#s4FPi@*ujaeo7?_$PDeNV`o%A74O zL|!C*e5B}$wo}2!2F5?z6_TFh&*Q6MyXmm)Ug&doC+V`IRdMn)-lu;?2nv|_i41+b=*CI^qnA3q}5dXy8Y0#;PX8t3dnf|Y3%HuK#7lp3b*@q zYJpDar&JAX?ytipEnp_OmflwHA%LW3&AXXX)<`5H?WySpotf=RELI5*yd~5rlIojT zor6$BSFVoUJv=i8FE9!FognrlT;adY`bT_{J6B>%^=Ch|;V{Q6PP5~As1>ir42Qv` zCs@oB0!_@Lf8Js3aon}Y@;I?!_O~Qc6=ls zOjwYS{hLLZq@j)yzpj$$f3v`Ck!oq*+&zSBTeK2vTL@B-^(=pY)tQ=Khj?l0mqwo~ zNE8ZO?GYIf58D`OmPU_VIzWJfz2+u+vfuA|3?bPn`4)Gh#tq#LSI(05?q9@@k`&c6 z{~p(ylr_!zHVXB!j6-2{^_gccxgfi8-~S*e{g?QGebvJ0ytO_||C5Jifw~7f@FQ^{ zwRVY!kU%Xd4P|~ih_OoKQODh+ZAx;2v}S9EB8Yf=qUW5+u-t>RC$k@Fc5Bin_(z;m zJg4I{#Zo~^HduXhR-%&QUrL>pfo~n6pL{@copFfY$@J8$Lw~``Kv-Bb;Ixp+y>Gu} zB$M98++mc|=xWJXd`?A#7USdbItw&;5yqx%A^!7qp2_H4a$-%GjE-k_VGo5m#- z!(CsnMm@_J5vhqJa)P@Veiq-ArvC4D0!22QY46*PFA9e82T5HKJaU~eySL#4L7_rw z8WwfwXk&Hm8mVCn0YMV)+PF>27*`b+qt_k9Cs%pq#tmJaix=nb&QoI&oVc9>MT3~@ z``UX-&o!4q1_nF%6CW%E?^M8~A2B4=DsT+Az%%4x$Ekl%JhZ-7E+e8g8X%##APfR-M`x3)c_I z{F7fxk(|&VRiXl_@yo+KrV;=qtF0c1FW@3x$Vc@;IR~=OG>kbSn4H zXLORmpiNOuPBNh>I5?=-;rveB2G6eql|@Eg-b2CUQDk43K+T-G0rlke{Ri}!+Y43q zT8;B*-LJviLycM52@$vY!eF`n5Mzp)aM7H}Q`Oq+j@yjretji|w>rR%8?2k9d{*aA{JM!*v zpv8I)a@&*d{t?lPY==l%x&}p)0zc;AbgZtTAwE8lpE|KoRUJ-g?*TPt@gr<82lf#{ zYtQkRwK*V$pl|^Oe&L*E1n@|ZH%Lh&c(+F?3wE6 zQVC7twvb7KF~pK~;tF!Jz>gXI7__ z4z#o8IAk1-u21h8DXj^O*uZ-dE5pG!Q3PxffVZKZ4X*hTo%En(hYSuLRfSN1xVUgS z(p&fZ$Df^eL%K-h4tcDH6@T`%zJ@Ip7c(ldJ52~JJmrPuPBDbuj9 z)_m4QNNIswSWF(+I*_P#nMJ>N5xZsG0gQB@WPtPA$Ob+<1Oc((M@&skJ@8fV+pq=( zz%RAoQciEF_h?UoKvKIKNVW_cuC^K(uPxt{Tgw|MA9~Qeel;;bnFZM=^vFI5jMU~l z@92?MUsdJRGc`Rv&P_u!m~R1EH4v=>lJKkOOVr;jQ;Tj+v@H7E%If?q(uU)lt%C%s z9U7vGn%C}@VGR6+yD$+XVsi871%Iu~ddRth;l8x+N8abTNj)$}zFUCA{+5>3)hldW z>~~eR{$s!MQY)I~zyGo1vmj%UVrgG%@)jvW7NH>pyJSFC3AltWeb;E_J z*%JPI>9(7gG*(~Jb$t8<<|QNpTH9Un`4qSl)yD5STSSJq!VN?d;Ux=>6v}zTliCz%R#9H4N)Qi)4oRIv7EbXIRGU zSNR13hxfPX{(pqMWn7hM+dXWI9Vilt0*Z)&3Q9^lBBg}VjX`%xgN}iKfQX8;N{N(o zg8?EfEv=i9mX1yQ*T%SKoO#}NKHMMt-MaU_uJb%%9c!)QBi{Vew3R5E-M4Swz89=B z_5x$}3pq}kve7rJ#124_Rt^loG~U+?y#nG^jr@W4Cl5NE%6Ttd@}LJomzxJhQym3w z)|TA2yR+)_Nz%XeVHm-|)!F%&ljpcJlfOst71n^bq@-#CP8LtF_;kobU-JIEJ(S9R z3#IQ)k7J_R$#de1Y5FzNiAyInzD7BoX$1of{b_k8Ywhm&Oy2XIlB5@DL`W)LdDNMb zERUcWIYHwMrBRALa~)VOq8DlQ?xo%C-ZZGGstVNhp&&)<_|M~_s3B^UevO}$(Z(VU zimBPs-2NjHvlE zJZ&$4AIR!-#E@4|u)U>aoVeI*GChzFfLlN_MV~Ppv4r$~pT6{rlW=+GAL=60f7Q99 zw%1QAVoWTPbW+t;-iW$rnXZUhf}ZtNnR)x3io#Qk%{2@zTbU6XD7;BGcEJRocDhqk;Z&muC39n&&N%Tsir8Lmp58PyZ~L4`S*uaf&-5kzObwD>=vjtmjy1^xRnQiI zhCmc9QGFOz{vF~OS>%EP;9ws}jh0CcNfTtVR|{8d7PJJ}KIJl3pm6 zn&{hYp|osX9QIX%CaJq4$=;#1lUqsEXTZY(go6gQR0oF?t~3c{gFdk6!{;`5Dsl&V z7heHThU3$%^eZIidV*-PcZwAUzu}cra%_4+R>>VXeZhlf4Y}{BAE!qv^^0$KD)pme zT`LyhdL^&q>GF@bhc@)UEs7Lax(W)0u7p)WNJt3icd)#}`ooBtBU)1)zF=si^f7cN z{;bs$?I>eIR81Eb57re6UK2{5$r_0r&|LBsu{*-N_;uC8O)E7Jx$D!)TF$RSv*UB7 z?&)8;+?A)78d1&`jgKX>dXF!)w^A><97kcKUve?|Epdiz@4axIMuFOr-mRWztEFWuoHpW!jdYdj2zfrr$=VoQT zJ2{=GHL=jp8tqeHF>m5%m=M)!IduRK=UBfNB-sqcywc)E*&4By^;NTYh*I~oa z!E_K)MaKT18)3_LoZ{TK&sRJ~$kpD~d3Tkrh~JH|2oZ zzUk6zXZKB~j~{7esfEuC)N{Pcgdi4j*Gu*v8ZVpH9~qqp&rsb;m#NwIL081@<7{`< z@Z3bsT=VL&$)&lz(R@Ma@-L-HF(>=4qD;Gg7e_6Uik+KVkCP@{%~V#lx~)xv<^^$g*Y33EyU>JJs-Ec7VS=9}X!Z4xH@F&L={8$BbbhsCY z5yF*?VTCYj&|i6>5v0T#9u&tAN-G{!Ew8E}@AFtM;?HH~WkaXn%kLY)?`DV9FE+Ou zPG=Fh%80z9*RzghkI-7EbQCyfeLb$Y8L?-jB8;RW-@1O6M-f(54E^a!N(!Yq>^n9} z$;g1(5L@76%H+@7xBTeV4%MGG_yvPxO?`_{_xPeqL~3Dz-H}2I!$B42dY*K-MCNrh!o%^!7>LU_7`~5fZAtwX61izWw!*lr#w}5~NCk>@2N_wR@TMG*d zcy)jMDuEQEAoai3HB((^`SeKHLYYsN;E7=;<)lq9%UVkV?J>!72KeGPpI5WZT`@5l zq|IqLI-W2rJRc|H+;XYksQ=pCy*Db`v}MCQazRbO6r#qmczSXY< zNuz(GIL|cl;FG&2N$s5Y%l$Rq>I2xO@WaNw*8ArOOK%GoZYrd-={{#BZ-bGkBP(C}=OtQCM${1Z`UP_@Y2 zLCtrs_zDCRs2f_$VZoE)E?13m;`M8gc!OclBz%`<>-x9`n$5S7*(IcZHCDG-YG2ru zwzaXi+!HXjIUiB;2e&op6m9ucHT{5BxD-TFRA|~0SNFeDV8|~h>y-8ccYdXXVa^N* z3p3)RNo^joI0Y>tNw!^GeW(h{v0oJw&~5w!6V3*wVtdwrqr0~<`In;avy?d_ao}$! z4J8GI=-6ShC|QP6F?#B)xg_*OjKo;@_U(eRJ{7(0a^ts_`U{k$ji$}g(9}nhr*j5J zd|^hLHMIvR{4`iv47Wa%VVnD@eL`@^b_|$oVqDR#wqHp$NN-tiqQyr0Tn*N2`JHFX zcKj_;ezDx4+PxOZ~65N!hP_GO4n_FMe(ymo{SNW$ucX6LNsWePlfIAs>c? z@2@0ddjW_sBrwSda!kxw+m{&u*`U7~vvyRAfzu7GlE(!F*`#Zl^UOI!h+SUAZDup*xf+}llZusE@{h|zkDuY*DAH4b$#H!NKQV;VTkwD9LCz-6HGiQ~51u@EN@SG+vdOLc z>Uya4jEu_kQhSoJ8$W$A$ZUmS?x^+p%+^r1h=V6@HW;B#(UwqnHWTPLDC(=&= z>{aW+&Xs--L`08oeaSHk#j1)%<#9s!SpCB%JK1UlI*T8ZO6)5e-KkSnZp7}tcrI3CKy5Ah0 zgB(lmsnDscs(*;4;gQ?RLeFmX-Lw`q7dW%MLrueS&bh zv&o=QK(3Ihk!ZjfRH|cE*rUNp?OOtM2<+ccTp1f>4`r##)kc`lpPU2V*9dg zuf`3G(pJ@j$t4<77AMi1A63XklXF6r zsd`{cM%tV;oSBDYm4S@*si8raJ9uI*L_jINPj<+8{W_2@JvmQ&>8B5UoH7{rF-Iwe z#I1BA|KI0&ZHX8*m8Ot3ONvcCKxHdQ-{oU*XVsD8IHU@?z?Cs99nh4C;c!dmO8!e_ z_|D!1F!Y)HAyVD4{-&Qq_rPXc^zH6fjf{-IAJWxD&0!@c!lr;M@T&`shE6W*dUm#A zS>o{3k&HyG{%Qz^K*j&6;6NFh+e!%B6Nvzm+VwS{+MJC2d?@YlrQ(zQ6Ky-ufk%l_ zdfXpCHws5lScQMFK#=L}eE9YA=g*IWrK9A4J3>%Cw+*BOP*G!JnVa3uZ#>YsGIA%S z6?sL*P9w346rF0lOLpxh$=fkoM>4Ai(jyuZ6ep2=^S)R_WTp#+v}fw0kj2?A4Pcy2 z&#c+*)OW9y*(H+yEm}AdN+L%KipwjNWB4A!*Tfn86j-o}b2@Yi(|`1dyrfv5`j0FX(Ifx`3`a!fORg<>_z zSUZcn)M|%n{?I36JWpt%==rbHDBw%IdsoOGM2Hybq|pjCN@Q4EEcsmbsgOCSO{T!J zdur?SjA|5_wK$^N${j7^B|U`=Hn;9vOBd;xrsvkQ+xa3QY+KWGZH;eSCXF8ir?1H4QW9sb>iDD5KUVhuP4rX11b^Yih&{qn7$ zp~$zF+Dn{pZRw>Ecrwb$TAG@m2?0U@>5?*d+wC*BussX!1P|P5yZ961Py??Ty0^(Y zbB{uat%mb$2JEsGO=TQgpT_^}y5~qQ=_>o4sFiM?0r|Ik3ESEO3Wd}_ivi^w0jYDe zv$a)Tlh?l#@DZ$ABWj+wDZsk+!-u;=H}~JVKfdi}sxnq&Jn)b&zq-Bhy_~1mE|B?! zEmm>e{n3+tQ1+7x-a=5Q(*Bk>|MQ|gJOL}Lej7e2F9arEZ*Oe!!}?m=bi4%=TBtIV zv{Y5ch>Osi8yHBWUt)CS1Jd{Ak_CRfxw&uLOJH`tqH@C}%KorzGL>AJwi1BiOuutb zzHAuBDKL=;DceB&vwwp0$!;k1m=&Mb7%XXep#;Tz0|F=rd4^5sNS}|ADK{MR=b5 zKz7hoNQTKD^#f)osHRd=<)?27hgPameaN!9-Pahrs#{!4>#h#qnzX6gIW;) z4T2mH1avUAmEfLQDla)2sU=`{NwXelO+k2ASeejv%*t4PhW!@|?Xd;VJbXGq3b!p-%)Uv$!O_y7gRA@)MDtl`8M$rXL^pk{?AP5KP-_nHXauqZE~6xEMGf=pmT= zB`=Zy=(s`n9mUAS;zSXzV}!Nr_>@?&x-w;r3+94&5Z<0EX65LQ{m1(8op z>|2)F0nd_4AKe7KDIZ_pWcEm|(E@m8cLSHz5W^kC$FM*3@Yn=#Lb3`Jq0f)dc)wq9 zQqtDU25PLA>V5BCMMcFQYi^)FQ(#f73OLGiYloHp&y32YE}AlZ3Ix&-8|oEaCfdlG z>n@NKk)bT{4ARmqw9{2y=Z^$Eyrr-Av7_?!JXMh5Z~x_o)+CdQ zMw&e^FJ~O=b;QPwjt&ye@MaSRQNIe=RK>{C>@zd!G2)r$Qqlvz^_FMSLpXOL{L|56 zLtiZ?JI401t$sa20wWugMETmt0J%4B)6kH>$17do(DTO>=GUVqx5oGrjv`2?6F`98 zV=o)dH)eVy-crJzjej7WV6qK7y}XBfkd2KE4kMn&KL~BaQPcee$m+#O{1sK#yE~k@ zG`e7SP)K62xbNU*P{HS=%umQ{fJTQ2D&gNVTLw$**yY(;b%w>@=aL?{?FO7>O?6Me z74i9TFlO$Ph|&jp8(J>F{le;iV}1uJzUgT*Q`1tqK#bwn{c7HNz-B0K`c$j*b{pvY z4sZ8Dbf4wIuy|s1ead$%n#Ger=zB`dV0pme$#lritpo=4^Ni?b$}onL8wXbuM${P@ z-2SDJb^V*ABQBk=HNMMF z31P8STs|wIT4D8bzve&tUSHZ%1YaAvFB|sI(CphocZ%%#NaNUDfo4mP#Qg}@2~A8)7m|KN<81Ml8~YOfO?ImQ%lQa#K$YiSUq+R0i>#mq;m>C z+}wp1d7${$(TQbwaAH_{&lIbs-KGqWxx3GgcnvnIk3_YeJVuIP-&@2-{PX zX5G~0l~>FWc(1`oacNI#bI7@<#Ki2JoFJJPUwgNMcpB-y9{>6{i7)1+jNW3c94BZb zdN*&`z$D=LobySrS@u{qN!_vlb&LBZ(wAgeCVjJlA@0C?FYOdhG<`)7Jnyy(`?9b&_fQ(>80cG`pKzNQ$&D@+PP)aR< zyYnPiz5z-ILD<;*b#xRag)?)`u!b+y8NZvd6OVGd`e)EjhcN!V-c8TvUv1`h`U-~# zg?q(tsU;}Im>Z7f^gP}W)2H6T7fHW=)vZ{Dc7_IdQ7dV(Q$wNOzbUt3qypbmfBE51 z)lt>3Dbqg^$;*rL!yi?nl}Q;Ci6UJvJinY=&^9j|fqLk(RW`0D3TkyVHHf*Q8c^+j zIcLP=ADf>K_XjWWQo8URh!6EYzaDbZ=4I`@l4s3N3{2iB>@+WkjY3^rUYTLLB1564 z{q@BLMlvd@x5srT)`PG{SLc}57;s`GGnTO(D@^MxE%i`2B2*@H3@8m!t8?P=qsAPIepj%rLDQTKYD4H>y$WBIFK!_vKp@ z7g$VfW|`G}P|sbF#|(^+oZ@?r^t?QNJufX$axuMy+bj=i+D;9Px9b_#Em4Mc>^K)L zDa^WRqr7O2=*vjYwEXO+4MwP9Ue3VdJu-`>7O6Y6uUaoLo3&N(N+o!!UgDbV_5YS% zD9k)52QfHW$L>DLvLjo#R7A-;osw89^iuKUP+EOEqY&4?7g`k;2fs{@vH>Oy7c_>%w~nrk7vbwMdo*|K21jgVrOHrCXf{!5 z3kwZh?D7?cpeHHu@wuol0SAWMn`Z&+slFItsYQu`TEEzh9r>d8%E>?}0%f#JOm12W zrlxMo%DW@LVoJ}=bpix7J&VY?k(ud#h({iHtqbNi5mj4FN`91S`&NB8iV;vvjbEjn zyi3e=c1h?SZux6;=j5{F7|3Z*O4hL4$I;|ENP8yQ4IFQX_ux^{{v$6`ZBn~FB0iUhYC^0B!FXQA+{%oXiN zU%+o#)w_qZl9c1Wf!U6wuVaSCs8UI$Ceb+Yfc$oE?WI3Lr8T1@MZ$t&HYYH6m}g)k z1|fI?TVLz?DTLp^+ztDKrfQ8q!6pP}YbCL9dczxMX=$lpo9D6?DUZ90l3pbF&&-DI z=5t7hB1I*^G2`p;V~N8@JqhZS62es99}70p%-&)CQ8|RM)Y>T^&@HFhZP?+x>`Px# z-Bhhy)zri*%-eTV@C{iXgO zx4n#uE9UYzduAWi*7Z=}!0_aI*$asfeCFook<5lc2o#{Bo_Up*H#9>G7jgkHoY9PR z``4kNyewCTELh#(WS$gVbK&e}BTvR9T_FL1w&v!Y%QoH+M)2q*(JsX+#WQAOETITi%(apo*&9u9=mt={I zV9%S|I30HQ^ei!&aSAPUeA)WGN_Uxk?SATL35ODj*pEFtqVrG_KDJv*=9DuG%H2?@ z%2syG|CCl6%b+W+H;Nwr+4f8p#4)P^A8RXiRME+o&mYJ9#aS_0*Y$Jr}L zS2$fzW!A;Ws&0VKtC!pR!u+##PPthh9YSX}%#E`X^+Q1?L{Br8G=jDQ5Denfn>VXD z4l*z>Jh<|mf67bwmcr1aEfD&OStkY;o-z9A{CCX6fIX|SPl%TBM@!ruNe-io6^&_B z&m9S5=y6txgftPYL2wh~-9i+bQS*3udV&uUP~HIA6qo40=xCEsI)@we*7YDcW$kyt zdNoh=z{*}+rv4yMheBogW{7AqdT&6!(mISy=00Z`gy;J?kX@3*0~tKR=^m7Jqwg5j zKS6Smn6=}Ym^UEM%i?#ye-Nf|jeYMyP2Y4)Xq_Q5ip= z6VOGNX$$l68nOpMGtABrftQ8EZ~NrUrTaqI!Delbvxs%dKYNhMl ze>hsg)4bv&>)+oQYa#Q4x_(X-Z`Eoiov$2^1& zV3YN75z6`eBy?y&esG+yVh?=C3h?y85k^g=SOus7tt$lCwA7WA1E!vm-D4)d?)Jvf zps~w!G-P+mHgzJg@=l`9^tH;74V_JHGJeps;poc5)ERIF2G-!DA}pm8ETbj#7(~7< zD)hEmwm*$uFzNd<#;g+(o!>z`p+j#b$J5Z*RA4{$Jfk2{wcW(-(*$v?hT(gPO-FRs z@ry!!4gP=m9L=$Hay7Dk11|ZgpX0GRRWh35?n*I>o$`BbH)&Ao4{9&-imK(a?%VD~zY%3-l|C=-76Z$- zU%!W6^jv7^|(Kr}%~JNn(sKKXhP|GXFpLX10JQ`mt)J3EQRcWZ(36wb$Z>%c7Mq)@1g+sx;) z3_Ca9rnAzi!smW?a4+fX+mZCo_o@diDt-74-?`)NXhwGLueiGW#*r!+Q~em3yauCI zuAq$Db7QTtg>YZ$4Wpf^10H+kUE=c6JMg%EHFRfSp56%5MOFq4>?|xN1s4^w%kxMz_ zw%o#nn@+kFuYSFQQ>1r56TCNfgH)$W#+th8z~&zHiRB1KOPprD>iN)4R9F=6SInf5 z=*v9tth8DRUpIWOp^Z&ibVhVclD^~RM(Vi?NwvGjDp|AR7xPiI5hBWnn{G0bU#J%f zON#MPnz@DnvOp)dn3j9V)Y3G^ZrY;9ckVbAEeB7UUX3(f=H?iv_Z;0wn2$f$o;tc= z!?CN=u6;%Kvod=Q#bmWk)TL|BFrN@59iH386e|-qE0)*H4|Jc3N0={~! zV&AKb3=oqFoz6-5U6*@L!942e>Z!wSo3KuQbmchtvWv2$0{?zbXz_MjuF>a?x%SuXLe;K5;5D0~^Wn zwU)mXR>H7RJ)seH>Xwj{ekojDGzT!~f^BqIJikEcwhtT!!k_Xy4`h17tap@v#o{)D z#STAi3X#GvwXoCK=5yilD}*jo69=OuURM+C>4c=$kxf6oCOnIK8MhQ0Iy@90!7sTk zbeG6Lp|N?>*R)|hdF3Pb7Yn23vQLUAyO-8Ka(KpRDLvG9Me^hm1|&bfx4+7l77EEI zCEmPU%g!AKJ155b%oVrY5xQDAsAie5Mg2Sv*^^0W6z=+q3CB4^TiV+8(b96!puqAD zEUpD!4}&?7z`$Fn?Vkkn2>Yz!+ZC1Y-JX;>uPPoS6`hj84mze#_YASvU4f+mbh6$* zVIYYVJ#@`!|Bf9^SgC1wT3+72b+{@-3a&QazJ2@fLE;F-1G6KE8d_S|-d(sTE@J7A z4bYI2jx_5Lrx2{sO6 zHw#Ck+s8-BD%bY%P~o~g!jY(;C>@=Fqdk1{z=wydmG$^>G*c+{@7l@7KFs=piV%Zn zs0D_FKeY?7Q+X-ENii%8DN#{b*~@O1$E{OLySJ`ia|4hvcmQa3(I(@DpqNBqib09J z0IOL+UfwYolA@Tfuc)$kHL@CYQ_{8>+^qI?&9P@U_IemcPEa?wzx#sm!`NNI4fekM zRWWFcC(IgY(HBUDlk&|&c?67YMYzA1(3yfFb^_Ik!hXGViLV!dIsE zri8tzH|g3I76jxOkGwNvd2%j(WcZkgsbkY$spaRZ3>b;pz4bK%^^wE$Ya=qu*G3?l z#vgoVj0)8!jP9yixZxFNi(1~H3BHU7Eu1RRE1sley4j0)Wn9{=U?3maGC!m*YE%MN>{ zZR_-$wZs{Rs`!RZ?X-~Dsw;TY?^)bCp3x6{a&3(!BLkxx7LmLfU_0^?A`i+-8M3%Fzo>rqqm2eIw`j5?Nvr~un ze&r$CHYIhBEL%^^MXO|>>|vH$v#`9t4tY{HCa3bFONluX%+Op?GAY_8Dq!6+$_VX2$s>BdG{A+K)-Ll~Y?voDn5OhH;U;6xxHyu#T)XkdRHJE$yzLLwzTGU=$fDB% zVi?c-0MFae#AJxz2xNh5Y(cEnFEd7B%myMYR=N~1U#w;{Tv6l`B>&=@R zlg^^lZn6$VxnKZWLnr3I;Fq;XorB%Z{Zs8D!g7v2hx*U-n!ytg8HqJc;pOT5_j$!= zSbE~YydT_2uhP3Or6b?P{Z~O^?>X61*Xy1zS541T;G}brOd18*PI<<*E`o{6@l&}2 z;V3AhL*7l#EQ;KG9LTKRcv;-tHLl^vuxF8af9$=SN!m9J{qYBPxi3lfk;>O^-d;`} zDt4&L?gp1H1)dR;^1FNYuEb?jZK%Ajhio?WWTeU1mO0p($|TOa?#eSW`OL{QF*{qL z$6eQpxDYr6;B3Q(^x3m77_u*Q!QBA)7Pdq2yRrti2cq7HzZ4?@-Wm=IA)yW8(^Q5q zhS2E0jMC>3?sLVdras=!kzuAeKAk zC#Fxi)iE$fJe!z)EwQkrYl830jllHJLsc2(=K5bw?Kvf^>A%P!dhyb40q?qeB%}kj zaV?ye3f$b&Xtr$gMN(@Sp)|PAEcKtB_{xEb@M<^UW_>m1I z^6TJGrTi7jKmaw%to3&*#JY(}o2F-y^Db zp6qA#TjDu>?hhIHKgW)wxQ$$(`|aSZJ0+TvrPTm#(?E{RM5b2kup58m@afFh+=PVV zeS`5emA}fnF$%-0?G2{&!b2YzR=()DC^<}NRh2<@39q)RP5>nXn{e7Xv)nFA`RUl% z?%|bQ=j(st<|nyEcOgxacz3YJ2vIsj$g}K)hL)aqn02RVVFBN zbT%|JC;p(X|7wwdS%%u6oGCr)rf(N~O+O8U?ggbym)5~{Pqvi1?sD4=f#0k7Ps|S# z1nBJeLTS8v8{(ANwDMN^Z|^Lvk-}a|>AziSPJTi5lR~ri4|+KoSS?c+;?01@p|NrO zO~l3(ZaVBYVpQcxruvA56}bp}ASdDfL>iPFCL5FgFg~8rIZ8dVYr8x8pW-)!r49g232x$l*Eho9}I;^z5p z7i1fa!#&ut3xwnw{rX~zGcFIFE_5i;vz6JJttVdGF2*fjaY}*D8$gW^1So23O{I0j!{RMAUqpA;NRT z#@oKBYYcN)=qAoJJ&}rL2xTyh&*kGwtCQz0cD}u^Jo%-rSCajNFb_8uufWLe-E;#m zIG$43nsM29ltCw9Qc7Md9uoeN?BTv{RXA~%;P4v?f5Z=|;8 z@5t$PbSMzx1qS*h!g^7ZN0jgG@jcr@jnesy-4#w~9|(?#eR|(^u|Zeo_^H;W*7mxq zoV)p^AY#qXj4&Mh`caS8!7^iMCY8#il`=ikc+xSnwBDAO?Nj73;`(iP_!y(FQqe(I zfj-fX*TvsccpzX?Q2l^PJeVI4fUvHFEDyKP^}r<6vmr*+NAz;cdb>NQyZ;9K5U=Hu zyf$xxfsa9WJTrWm!9fhPGne(#U{%($k8BzozD6Y7D%o_k<0E|-oxFVc@@}+5-{EHk z9cG5n)=ut|U$qaQItz*ZOMJtUx2>IhWlykO{5t(Y*N%n0lRE)2D8=nuR^GKtori0r z9N-u4l&Z3IUMBR-$L6}LU<1=7tCrBNMRr!5VeZJ6;$T^`dCfno5)yXmOF|tsUn4mx zCo0AL_%Tv`o7>mMNPvix)C8yG`C(gCv}gU$3{>O-qtEPgby~u`lWA+abRrLSq_8N( zH9+l#dqX&b_1>ULUA@{xsxyo?r&dt(Pn6tUbkWx9u_mtjKsQTil~tIR_thr1Lzd6I zn07na)Z|nUIyKeQ17nF_<~;&Z*n@M0+}dVKV747?tf$b{@Fd@@%9OfMB-QT~|KN#L zqMpp;XoQ)MMX#_q@5|qi^2^goy6O5S9{JCUkH#klp-mOuq+<<_Xa4G&oa96}o2rpu zB;R~HvYh1+B-0=APvvkJdUEzhMghFHI29rj(<2nXzn7L)vCqh{Y+6M7z^eN^zkT(;fzP?Mp$wssYZMg$ z6I{3?*V@5Ily506}9uGRF1awt`*QVV0gtLDLxa~e;AhU`8aPYnFi*mq>G zud=5&d2kEi8BegHIe~b-WjZ>qNn%FUueLv}yk}mtQ;%pRB}`QcaCqhk1H<>}^WLnL#6yO%5WdIST$ic?_8UT#x9LWKq!KFN(;sA9J?Z>P8pt>Z}sF7DPFE%Y@JHb`C&d<>`PM{QQz2zbsoLWSqdzNXq8U2 zhcW0DU85E^X*AFonO5G20PTDGx2W(%*3WGDfGl>QI2SmXkwXO$7uU07t^Mq0=a;Aw zBVkRKyXexnmhABzrk-V&$M=YFEZTImtZs{a6p}dNw9Qv*90d>ZBqdLOZA%rY#lF$x zDxY3i>b-S z&w5a*gB{Bp!IeqI zyFV9ozXhz!XGoPJo>wYI#ICNJD@Q+%{-mmt&6o84?V;y19Iow)kGCJ2a*~gn%k~>A z?+oktU|FK4h5_%eMJt&}{R_J}FsgV>ECgj30qSSU_ z;rfGYrhVJ#A(P)gKEvkv_C->XFdtt_M~Cau!d#Nm@oduQ<39C2(`L-ua0blwG16au z^9?3e=)(;-=TSi-(Kl*uvX=3~xD@0;2xGd&X0Zwe!p<#yY&Q!ze->`Ve4Fh1^3<^QGKlV&-M#Z?J`P8ymB9aMia9i;}ws=5QfIaONe{83qe!Iy?ELmeaKkF*2eYh+g=V_Chf=qZcXk!?V0~e)1f!Lu;8uVQ4}=&CX1vM?;(sJ4@lV*P zR(n2tH2b1yurd!VHO3)0=x7=U;qr|AG#BCGYkvXgG@V4wd!uxAuqzmTFS~mwm?r{? zBp4YM4^*3Q>x-Si1l~c)uSgKandbvQ61U~k{MWBoI@H(LD^mRmld_q@)2%f%7ez&H z+c*Hh01>~SK+93tnMh6lAWS+LO!@_j{rT68u~nk!lL`0O+BcQCo+Vwi?vXI&11yg< zMU&v@C6(&r%vqqr@(>48ieQF~br`VYh#uMbdvv$#?V4IZ4Y)+h z=8s;+vxANF0_}wreC+L9`DZropH(0{CQhB2?J1{TDQa;}=MRVqa%Q`EG8P1Q(NmMg z;Q2>gkL}*O*PqqPe3{_1K!T=1XWtMAj;}P})@h>&KSu{;g0SB#Z&xoDXphxlf^qTj z;Lld{#Apj&DULSq+lLQy7l)0 zbUq^T%_V#I;e+CYlMEHm|1R3mPt3L8(?D_BR>+>mM&fcTF+X@XSuf5omLNEB^RLNa z?FE(lQcS7at8#k7AhQBR&aCs@T5~$Ebe`R}QH_!VX6@FezvjQb8-gd#c_#l_f9mdb z(#YSRmg24UdN;!#(+sSYp$(NeJd?C_)C=&U(+7%v!u(V{Vrqq+b#GAcd7W(X<+U$f zyx6^a_s790$R@ySS+45B@!OB@`VT;?m`hY{;P3yo0jJT4-Q7!%zPOO+9AqI3hsZrH z9I=_u)xe3sS!wGGEv$?KCIseRr8Thu8U0`XC~EvKKVuTYG`7^Gg5G`oEH8TL>C19n@S3TQVg9K-_MMocMOYx1Yp^#iQ?tERD=^zQq8xT|U3TAr9P~>5#9rK`;xg_EH_Jk zY;u8*51pn42fQAVRcL5vOfr@iSKDA!eIDY><;>Q#H?Ft2U9A9gxk4MsMI|>i6{xf* zPbQFsc!X4b7xM~--zr{~1!lQpRJg5BYuaXOU;;%5LdpQY0|PGzKSZ&#Yy>+higUpa zMhiihJr+RJ@ZoLMmBttgV@U@YxHBhDTEe+t;>&{TGH6;+xo8TJs2?T5 z;uj0P2{(!su_fZ~1F_{C>IcL?JjCy0gFy+>+z!QTE zB{qLS&+}n+G$=y?FN&}1BxBUPnyv=S_Xy{YV(guWpjW}z_Y?Si$afvYzJJb$dnx+$ zaO8j_*Y*I?0!0rG-D@!RewLQwn{<$`5Wd2zAt^#zk@a3L)j=REekJ>eNBy3C$^_a( zq9q7B8;sJksu9~?zGT5w+Pn?ZwWE#958O(#)8{^N9251-w$(%Lh_vB^uddonxUMB zAKdv99P26t)_0 zV&%Z`8Q;~*|9N#EGuIYR4aQ>X6IiL&-yU3jkx}_y^8=}liPAkQL*iZv67|w;u4l>R zi`^AWMnP}%2;ZHiX4=~h2E>>&%G!5kndjp!k8yw}fY=08`L5CLoPhqr_bMnPa~{39 zbRn*xmIB4h(&x{obE*-!FpP>X!8On4y;{aA`anxsY2kF&wcO%dNKIhdhxw(z<4V2U z!CUl4|0@Us1H+r8*c?@7*x!x=rgCihu3K1nk`6n>5;WW0ISwls{CvR}6ZZD?{rUk` zGJZv}>xIH0R?BxGDrxNKeD-geEtW+9{5>VNaOr#8D()fibNB3w0v4B&m?+c6LgOvu zG7rQFr9)%lR}$-^(VC^IqN2@Nt+-X5ME?Yb(NYjxPzq=lw>tYUg+C%7uT4gYYz;UDLN?>Zmz2vm7P7;dlh@JVU5=| zv2AvmBKGg)bwuZ=a;!ov7eNzNc*I-uWB*yt`~o)DgfMux1=Jf&!QXD>@~nV?+D|L5 zBtrlw5df>n@*c2Usp&)K_$|Ky#KacIp&OVoL-mgxWne&7iaTHr5Q2{fM7~82=mRtza3nXdi@_)?4+oi^GXqgxe|yE2}(z2*tQgEqh6@;x#{($MbNKv^0~ ze;ihHj~&hrF+2rJDY&VqYb=ZYSWN2F>YJD=a$f@18Wf~{AriAvI)zSRq2yjk7qUa);&kgoA?y_hX4}@kLzmf+ z=Kx1e){)_93g*5|pc6mc`djU`S;uqouA6Se3ZIV@j6^TusrvY@PFGtT6Ii%UO0(my znX%Dm)9o|0x{uCLZu}#%9I!65KfQi|fft!9U!!>HZO6Kk>}=lt@uH%l*;#wn&L_od zffvvVeh~-w_phqrCyutlARMIVuPeTzmLNl$Ak_UCQwsqL&ouNu7cZVXaRLj1z%D{Y z8eh5-B*hdJAhXL3>sg$gawd-m5;k=LKK=ju*Cjt1aplrGlkujY%pr5Tk?}KfK%^|| zDvQ`IBqW5@q;TFS9O0ii7-pgLAD4BPJY#Xm4|V`)TGrk^?`=CH;)Bdq`YuLdeEO^P z^YH)cw+?N0AJW!a)s2c})YBf;50|Fdc3^FAG0)d0rn8PT0CyEdfFk z<+fpw?XVSmk(lT@JrroKk=q_-YZkat;a+v^d-oqV1?2Vq#oa)WKfMS=1D69$0GM#L zj*cudNt@`^cFOuGE%>)y9R2>iaRRZ%I=p)3+vvlULRU`X6fZA4T@i>dJCo2nT%a?t zv>q*alxPi@=CA=>+yLJaFqP1`pYM}bSI^DLvY%*U_eg!Py&U8Je>ZG9q9v@4?gg#P zVL8fo#L&_ZD=_abmT$eTf$v7)Er!ip4GoXl3YncRtPD&@LtYM1@LIrcnHa%xO~|al z0%|uS`(q7(rm(ZKwicWoboNEL(0RqjSAaQEf(?yWRKRXM;pewPVWxv|0MAZy00~#G zMqr!ozQ?xV&vDmC0LEow=4<5?L+5$5Yt3j3u;V7KA%^2(`14 z8b4;3S3Nf5FP4(vjtjq6j2}Q%{dSb1iUNC9&&4&EnNwHN(+6Ul5+WaIamobjqX0FK zY_d*pbAunrftuT;9`{UxHMlZbDQ-pNvBOML&EO^70mDXI0u7uO!3v5R?3h z5X|<}HL_g7vB3^dXCOZ|A3f z1}HOx2oATy+Y10|Y;ys|xk8wNHv_VA?G&A_c#bnsC?Ft!!|PBDW>}fp`)1PU=EPM7vwUJHA(a z*pT4KcB((6x=%h1tBdDXCV*Je=wXcCm?h@OeGtjo#zx`dMN5>3zaS%;RUS8^nv@2I zhG25_nC{S^5Om`JWrDNak;N*E6D8pR)IW>jOu*n6m464Oa$Jz7o#}0(V;9Z&@3)O| zYSp@<_nB)UEdgpNKo{`$?-^H6P=I0_WgWP^4gz$RFt(s-;?xW{7ggJD1KGSDuMINh z%fv)FU_$F*#jHrOX0QP!R=XOL$HX~kg(8bd+P19acz%lIp5kRjGc67wp(Sj&!MIhD7cC&jdzi6&Jkj<{OAez@N=`n#jI8!WJm? zvS9$jY=pB7W*)97qpqYg{Ck~<)U)cs3DTUbO+NC%rT163e|*fsx)un7fVrI72knK! zkkbl{rmSsxcJ>sNm*BSy@J|Sz0Zh7GcAva2`}jX|pd*)iK+J+;|UvHka6;fBEJ`ZOYHmNSTMl~XQ=UHizMc{T~-u5x2X^uGu z=i96PH9L!bd=Q&?ELr1AU71#07w`MFzDRhSW9h4Ghb$e5M6!p};<2rpNcsvE^P7*| z%BO@k#Wom`{=qN*@ukvUIZR@yj;s6mX^z#a1o$3BPt2sGe120Bo)=CZ%23=jhFJr0 zceWez6E`Wp`=q#H0Xg2^K*HCWF9)>>U%arfU+eF6dDV6Hdrmhihv%_iQ$_r++Y)~< znY14mtgx&CXSA(w=R)igh6A#A-=A6kStRC45qZLw#`<=Fy{J^UtTk=wZZo;DliAe( z9z%|(G<$l;^8O!r?-^88wuJ%G?Ni&_O)v{e21OK5G8jOD1WBTjL_tIeN{$!16%-Kg z5=3$n6a)#PfFMCZBq$)Dl0kCL=@RdJ2Mle$H&Zn=RWm<^davp|yxgWm=f2F?>gfvY`T#eNbXY{|mnug{c(<9jy?zddNs9P{{Tuk!e6PRJxU#%Zev!}U~#4hQR{5y@$ z&tVt(qL3aiJ11<7m7SV)P711gDN*|2Khy1d|MRN-XTavi*Q=g4EWL2JaZ<&N{RI^uIe7A!gqe zzRP!kHK2oU*OdLPw!{IWvGIv89z(-Cm#o@*3ko!xD}K_Z#1D`b4VkA_hv%5*W@-ak5?XQ1dBa2qNL)tmF$yYJh*g07E>jQ8f%aIs}f>q zkE7XskAstV-kY z`lp;IvESgZcqFSolFcs~pDY`BGN4=#y|vWtbWEb6VDh_FUn+mra44VJq!we&W$kgR z$eKi}inI(~|5qAUx1YlQuW|_CWc&+?x$);rHU5?H*zLd-YA1Q!ra@borZ>J??MTXE zpEKqpinLy=`}g)SbyBaH2xwW)4te~^sTR`~j(HJp3tiXLDIXvGaU*C>UH z<+)Bm|E_dpS7rM@)1mni_bCS}>ZC@jqWRn4-!Equ7hMtU7ykM4;oFXK*`jng=RMRX z)M6&;K+NpdH<2S0mOuMXstB^a>7%GgjkQf#^03@6pY-hMr^@78mE>lW=F};#pg9%% zby4+7P9e;S=5RsrH~7gdc$u4#WqaS=mfM$faA$)Y3WCO#B(~>+uFeSqNjjP%Yrfvv zy#cun2X{VOSz$mGUm6#WUM$)ZmL;X+g$hjzs`s#-LXcWa!n-?4HH=>Lqp>tJoL^HJ z_Gt=*dZHUSSbBqcRxUsI#&EDl%%o+>mV$%RT|td`&2yS+UG#Ey2KQ+{Dp=Z>|157z z=j0yyv*RRc;kET&|FWa=%k9$KNym+GJGnAf~_giQBpFL=HIB{32TN&euUF9me-w?6}{(bBUYH4e_P~#0$d4k01Yf z5Ws6|&!EZ#^~|3%qvi z`mM513OemhZ}qcrAuQ+ePia4rkf4JNJ>#74mz1t=(NfqsYbd#)Q~!qv`1V;TsfR&h zv9c(|swC?|9_@j7z0z4aa^PWKe;6E*owA5Hrt%0W2fo~@`go-`nmAxrkUgDsZ`w+jFi?8agZOCy-qK&g8_{bfn2mXo&M9)J?zi)ej{Rvl?+lNfx7 zl-^J^SE>934|>TnW~Lgb?IU*i2-h;3`NuP~QRD-k7eFEA+1c4~PWRGZN0A-v2^j~D zx2y5a|LbpNoN`fx4Hk5!a!k+O0IsT|CdeBda!zN*d!WDmk}mkMw17(w`J`!;RFBqG z{6ceT?Z5Zt9AXau+DfsOD1I5TRIy+az!mw@vaGAVA9CF9MAaR_z>4l2Ten^|HT~2- zi6)WTy-J;R38vYY)ps}Mmm9|lmK5DT{uo|8Ja`1$7@dUViA98x9ASAyzPl>_{YO15 z18~_(39&}BqD?9ZIx8+LawUU&AzWc*avNVM`D*A zOya5vR1O)pWNABQ;QaZ*=TcHQ&B*Y@C3o-pX-@b#gP1Wga^Uyh-duRvz$7jzO1HY$ z+1o!_H54@E=U_^P2BpG_TlzjZ?o%3*s@kwMX@iQX5gau0bN@P;P8DYI$c&TC%O=c_ z{nQ7y4EZ6jq%$i#Nq-YxnM1k|1IVUe!LY6(#;z%pdC>Vmp2F>ETj$>DQ$Esj6UISl zGF#RvqzmNrrr8MOCNS+f=)91PN)qc@p5S!bn&A@*CpTU{@~qFRLX^8X?LmXG&x*GE z3H7|9L;Lo#ZVWUncndH5O_+edN9&&8X`8OXNwE40S)d;!q{LIHwlp^y{;?A0kA%LOUfyM2OY`jj?i)+U`o^TvEf!+9#*mYbrugBjm_g%U95GtCoo|$ujhe zea$KG*X7;>f*9q|fBesqSmk$zKO2=R|1~+O(I1Jn2Cqt`Olr1xz398L*H32APyI_- zfAE+1>IQ#A^yvQmG0D&JiOjIdcXVR);)2cYtgGp7kql+ z*HbtCwQfU5s5~nz02ArNQ~exEGHe_Xb#%efV3yw2nh(GZSBkz-x`z$`8RJ=ZtFv zK7Ur%4oyrG$g69aG<{O>4~AN=OZC%1MO4g8&N;5LwhjpFO&Or>qWQ9H^1jQNR1hk3 z&m|54-9@M$7PjF(zdk)P(AIWrXgo?!wO5IJEA7jZ(+!KGp?gj)DyjXMx5%yhb^)%A z&&Ir*FHL1!zUet~B2S80TVH%rv?k^X{GJ#jssv5&I9n+0fmX{CSwtMK)H;`<^^Mt;?f8F#+4UyiS@lg_ zA4P4-{PpN31Gb}6{TZ=muWTv0Tc1@ZV@Tztp*j2WIg@>H&RNc#7=DVX15~kK&Tx|% zvZIlwI5&G@ZLF4T64LqY*Sx279~D%qEyO;4g`1ythyjGlp$KO80)?hv!Q`vkn?LtH zSB%)7D=zqHzERd5Gr0FSZ=W5^Xo=df|N2bNwalcX)Wo!-I|o90z&0zK{LJw-Q8Vz5 z+fB@K-5Y5#mMuI{s!jYvwV{0n05df6#Avql>(>KW#f`_f^%e;}G+uX3RljynI-hrs zqGxVD-!7$yB=IlBWzX8bd@_`ps#r%uxOv}oA^r!09dLBKoN&ZYF?uDJbpgBt;F8zE zgb_vF-T*Y2r@7Rw6UO`->HNE{UYm%VCwdE^!vSb|D1fd+Va+>d-I(V@Bn6SI=>PqH z`V2jK{a{Z%R=3k_|Qc;7#nrM*9|B`-5`n@`}Us_g-cyo)QXI5+X`x9xMfz;C!2HKlVkTS!^edqy2r)BND0Hv)bQB7cQG$W z*$m5rsh}qbA1PyG+6&2Td92Ye2bNaTkdw+&c9TX#x1IATW;e@L*t}~rby?`oO$rz!JR87 z03Cxj)q2#)02d1-`d&K(<>lo;C0Nd~f%Cxc{GSxdIA)Y^m|Xw95(08;Y-~iGIw}?L zAM?ZwN*g@RPLQ)LtNdv-kj`HvR~Kp0a6kb7RWYL(mV+wCimX|P!I z4GGawSGVf_oQNKElwuzy$t>&bCg>Lr`P4#d%cYIo9m2D3ACjnOg?QJ;r$`SpUE&vV zs_N=!7qe^Wj1C-sFo*%{K@#leqt(fW{hzkh3~v_Y&t3n=A2aMx%e{SQ2E@>E3wcbO z0q8lSnrQMu9QgP+-OA_@6cjT{SYSu(?1cKZCO&y$Fq?9N=aI+r$VOhST+6xo+qO;H z^oT8?e%scTTat?umg_5{(J_f~1MEc9B>(l-Umc!X!M7-s$#tAgijC!X&o3^%tLyy) z`Il|c__l1lzO1v_>JHSd*nsf6yq7`@3^Nm+M)En8QRJnh(zU+s_l-xzY}qt4S3b%~ z9aUd{PFq|1%$cod+{?%~)Zg#9=|NZ+4>xxy2ERS`#JR|yJlW2odHOWc$pz}@6lCVV z%H$Sz)rn`%KU|;e>n!zfbRi9tQ7)t(zu(nRASv5=^{pl|b86GKoiT~*sh7t6$Ic+9 ziwy=II;yX)Ft$+n*Ivz}#+SCd5`+Vb9Dsg0^snZDU(-+9XplE|opXE{Ze#n|S+`k0 z68&k885@rpRiyY;%{~&DuV%=-h4)6B7f27*NZj zquap=fK-l>aXux3*u^L$W+gC9U1AIrDEGPOOs7BYah+9{j(1YecAOs42^lZgBssr0 zH@tciV`GPy=&R`#7pBFqb$a|c_rlt{tBdBcA`Is_q@@l+V=O(gSjlC6va%NpwC(f+t9V??z%E zk48-HkkLyUOk#?IfY>PD{46aj@@t^Oied&$tttLl2Yib+VusPuYnm{vOHj?lwa`IE zAvON828O+rl2xYT95mYFp+ppzw!O0P$F~lZXX(7OaZTQ)SK{r??W_FwPR!Rq$23lB zz|Vm12sv7ohu+d7wnL3v=ubdgyD`-KQHr7E0FQxypPrWHT8B%W|2{}U{)Tx7x3ANs z)AwI)-@#o_bEsjpgWklX*p6Ke_8rB00wc$`7)r{&ZOtmQaG9E(wnbx^j0_pg9S&n1 za;5bjpn7R@qM&Jdm`^ZCINk9X=<8cc=lujk8m%*DWQEt?;62J9vk{isu44*95CwMO7vOJl#!rWqa9A( z$)Hj%N*^4rRvzs6C>ePwyg8=GzneI^A_W~0%Ptv5wzn0oOH*c?pLr_Semj%=TtFk7 zx)2yTp69l@wYR$7cRsu2t&EC(EhTT0FBW*|w@+T3X%&N3&R(3kInsB&#WUr4O5S2sp`}rIXqIJvRh5%gE#JXe zk?h$=ncUe2-*1}=7_Cm-h31W^7r)-xSArc-dFv8jbC&fg`itbhr4-BfFfwe}lAJkR zS5>9zM0_TiQPpbh8E6}iLg;SnIzjB-p-NS|nG1!{FC$Jn@R4HrW0Skj3T>JmqL+$|Dh3+STe8BpO)bIaN3+?R}(gRQ-=eu zNSbc4P2R!ex>G1+xVb4mbUy?2d5kiGxsjO?eRaMDeF+J z&A>Q4Ef%rU+{5g{;aWW=4Knv$Gq=^2{N@EIk2D!nytv|-U1OrO@G<1!WC2}QN--ogJH{}){n_ETeo+aX}+syAO89QE8+)CHHHLTf=0ZqEoM!poh zOgix7*@E2jz&H>3oQw%lB|Ip8(xr5CTb8mRnpP4&zY&dOPh*xG_jThaf6X1-`}dd6 zy!70{`)J=*%prCoN?)RakPehNaYDk0aD&#$;B7crVD7cyh`#R z^BBX|`!d4g0De_|JghaM2#|sOYkr3zKu1E-n6>-bwQJcqINDjD`w9~9xpg;DEE9E- z4v|cej9Z3`Akjd#bi$bChN6meeIIpxK2orK^-Cp*+~Hgjk7U-Ws=8;ffn5#eP1%$t z+ZM0&IXf9me#3yT2%i)8n|aqZ6r`xddMhal6t&b=PIc5iy_hL%WCKlOR53kS!F00c`F@$h!&Pl-c8$38-k&TE~LE`96d)RH66l1|q5 zv^4E)t|bgM?dII1eYUQHRI21FmmV)=#t3;Qd1;$#U(a8n0OXe9_&3gFfU!|S*TzD) zmD<`nb;z9Pu+F{7Rq(CMXIQu=a3(z*WN~A;y4O4U?05I?t)`)AIjq4t^PV+ccFE=_ zKQ4yhS+ZL9*qZZPm_FHJ|FOvYYRUuTdgE58pFX`g?XM)gJc+}HH#40@cWiiVAd6bx z_3-p8_<})@lwv7OFyn;=U_%6+v@SV-Of)yRs{$*Erkj>D+p2W2XKLmJ!+#Mj51~4L z=uZ<%KzLl9qQLT?UMdIQ!~cz1Tlw*$x|6}#vuBmwzdGVU)p`E#A)5lq#C})Yp>Zew zS3c*BG&gvUG4y(N&s5*GHPdUc&*0DsHs6$Y%I%vm{r+Mj3@CJ&o}82}3y1POAsvA^ z%#>ne7o}>p$tP`aoqj8Wsnkz0!_I<@TPj@8Trkz+E4yFjKQydy%+8~56}cU+^#gK> zKAy}+VoW~}FDu%s#?7p?W7(@Y?}g}qoM$y}NFqr{zz+|EX8{4=nLUZBFO+pS8Nd?| z`0_bXOU0$9wcs|FxzT2u`cI!22F0@igBE|M8F9sf!X|Q`Bp1JX=UlDrH>&nj1M+=6 zLiLZj)MfkmN^lUN(XvCJVrz8yGN+Dd92n85si_^jDhK1x-&P_O{7W<+#r*E=mZ&Q^ z00>w}A{8uh5Z?N@FKR?#@`tso+!T0MLH0eIM`Gy=v<3_ltqhwGLD|ZzQwd64!x^32+UDwdCgK67a z)}aeFqiuz8V*VBewOgAuqhg5bdLm2O9-7@DkC~l6y;s7>?YVQ5;8D7ksh;;$JLrNJHb_Lw2tOW2wFc6@*Qd(>n<{NFpaF|*pwv_ z{?LibWK;4bC)XCM+(@`yPriDoHaDb9FFQGjU5`jzk`y>>afc^*Dx;-L;}A(mq@u-t z=3)P(py?w{kqMvL#_j}Dp}xamVb;3#WND5}|HqxM6=vuuZR3FwDFMveYlLG}7uu@&Mp|1V`@u>o=@lAG3D|$PL&Zn(FGT zBF7SQHQm%!O^~eMxT<4BhFtDfDH3GtuyK3dec^cOLyo=FTML0t>>w!v@NJ2 z54pS!hU~(bZP| zt@3wTkxESe;2(p{c3($7IXzt@1Ib~pKL)&^(%R>v8?vp3<7FF|*IAmfO?&X_arX=_ zv=}Ep-Va-Dff~1q#|_`sY*wCZpKDc4b(}VnatUFe*Y3IO7(A*mc*A5IB5ESGgmWDE z>;NwXiL{Q6j$%DnmDop*+E`%n6R4`_y`0ye6}1rTAQDyez8wB zxU#{4W7~|u)4T46jS=7XDi6ZqT-7k2CBn-)EvIN)olsPh#E{gyn#b1eT0;}X1YVe+ zV0SX3>LRM^BoikLoHRstx8%pFOC6?PQiA8TpsV3JIXlhe=O2T>wjGmL_7)MmjFR0w zDsmjBX>FM>l)~L-JwNLfx6j+^T3VPiTy?YU$S=HAN_RxKS}Q3kvL)U2A3K8_yXi3o zV@Ja)Q4c96R5Utspr)iMje3)JWSB6j8#lhx8ZUDAs9VHjZAHhzQWXf1WaW@sTn&*cF(V(7@(7KIkkyseFa8G28x-q$Ka?bZfxgz@b2T zDiM?HRVaV|F{CF-QUCE0=n)=99;9+HkT;xI67O7Nco`>cH$)=oBDEzJ=K4F)ZRyK#rZ*X%7Mm%4nCCi6`4?)2#OqlXmjppJXKYn}=^YW#r zL@Kc7n3xz$0OOP^<@HO|Oy(f4QBGi&;`j__OUC1ae$Fe_tSv?|3(FV?vtf%2pN!;m*C0l2J_(Gyk z5VdO3fepR={U_B;!R>2jL3Wc)bJU(PqOaZpDA?VHIHtfqVd&afUS?&r3DTbw?nXPz zwtQ**S0ep)%{VuoMakd9+0}TcqY54ho}z;0SxT42=|*Z4Va>yr_Pq8qt>NG2uU$L9 zU3b^Zt562M%4w<&9s$$tWB|||fCx|k-*-pAT)|_nUWKl_Fy>qYOCMxWo|2tXWK6K&b3-Ami~v8s-{qCK<@>9Rwqxtn$yG88?q!bJlV3a`f(DLD*WkQY2Os7Rewa1< z8}G{=_$T0uC85Xguc%AP@Ry_313IH)s)NPREk*ufXhjY*K7ri|JZ(Yf6Sw|jOH1D3 zA1gsdEIon;jl^A-upWP(rbB!e8Q=#ot9)d36N>$G$x(8N;om)s`b+zN1Y>j_ zD!xsS^;1(bs)%^Un>~`+t$_uJ={wp@PKUPO{_^W&?m48vN*=gN#2RNWmwmd9?S;RV zf0I&-GW@tRR@*y8CA|GWL@3GR$j&AGEeRu-ikFH zIZa&s_ovB20nNnj_=v}F>KpXltWwA@lRf(FkVd@ZZM70o4qks6flNc(W_~fTolL^~ z{B$}}-KZUO_bme|(ar+MAJ{0uv2drH2!xxIV)QVlOp+MI9q5^ag@m@UXW9&&8)I`p zw~1{`!otF5_4M+(y7b_xkZvkUOzqB^st^^V5~QWw8;HF+kAdMA+9>Yr8;pnEZC2iD zKmW#kB*}^nueDRxx}%t^7ONC+|32t==A-xETc^Tku1jDovIDTal$E9^!%)O!Ls z3HBN{)$(oL4@Dlq;&lGOrZ*f5MoQCD4m}2A^hv005x|OR&iHVv)*GaFsHT! zKOgf1{dr+fiT10{aKf_; z5rC}Y&ZwO}eLs%!v8D#Fgrpb}7;D$9^V=&yDKweCRRlQ( zswL_;HGmvy0ZOZyfk*0~9af+o>9O7<+Hb&?NlQn8T!Fzcizrygb)hbz0uFY}DlvK_ z3C>7cTN}SoxtQI^m!>NWyV9zubjH2;rCig3FX5UUS<*rF zdE?DXStoMdx^DSGm^)+;|6G<2tG-yZ$tEwCmpzq(QF`Tz+r>%ZwHAGWYGnoHpSAgW zVasv|Ab#c9Mbh^qhy;HU^OaGte-4MZVObB<#)^>!sX6yb(fl$CXMbbwyL zY$9ga^?rVS9)aoBc^eIkw}1I*@su52p@wS4V>@pLIYCRqS_@ve2L}|a2Wp=}VOe&U zi#yK>WIv6K?M5uAf6cC)%p#NjYkosPH8fyK@&V-s!uz6=lX(ZG(GLk-Qr9NCW$fFx zk98`1h|{;m@3HwqZeLj&!i-8BS0BajNNUjzYc_6)64wX*3=tz!aV_H`tp!pg?DN)i zOxvayCLIZ#MY_&0Z_kiS!8z{k3`LGoTWwS>b2_CQ4)Gy|2!9!RZ}EBO=Z`x14;=~^ zb0Sej_EfiM5SzKXKMsl=1HE88%ou02s(Z8-hil}`o@8=qS>#g)B>}Au9{^p`k!3T3 z*?8WG5iZaWrpCrIs0NPafqmT0#I$J>Qe)B24vLDRvm9nC)V?83Okk-v%bT0(y@nFX2vHFI^?xEggb?Qj;jj#!SGz>mDZHbvgni-*sZXGu_!^dpGo?8xPtRujjtY1%kz!~ z^Oca{UT!YL=DQBI+745S!N45RvYKo`>{=+}re_;{L6IVU_&QS(a6(G4-E|I~LL&f1 z*@QLoXYCAi&sp7{%)e8`B9lX8%760FA?di0z++jZ^u$ikh~8MfYW{%{nsp;Wna1j~ z9j_OerX@)_bc~Y2mnk`4oSbXkb^M~in?TnY@&bpAfE8KpDB6l=MisQfNMktxRT0sM zfnf^_2yl%B?g4$|0_-R+k=a!zLc}^A{Kut+ee?FUW3!V4Aw(wG%}aj~w~2?BS4MQh z_^lwHox-%uThq03oifwYZLVD5Hn9&>mp(`3gdXo8fR*B7SNlYFWFA&VGjEZ?FiL<4 z+O4#+pxadb@Zi-XZ#t6XT|!NJwvhV4G#DkTTsQLaXQ8uPxm5xyl>b+Z|^^2>-Fl6Y!=Ae=ieSBuID0Br-HjG0*g(uyR6(n*qnok6I(}bv=BXhFl#?o}v&Hl_moOjopVypMt#TZS#3KRQ9kNUN^dM zftgeo#(`@wL$~-P`T3ubhyPew!Ye@; zVi8)>!1>>oz00wtewLiNH{{=3h8%G_e^b^BGr;<&xfjmSpOcvvMk8(gz6N^rX4si3 z4RQ1Z5D*v7=@JnkNEhVMD7g>7k*>!&9k+aY3WDa z5>dgW1liZ1HRo%lC&?_-8OS$X@S8N+N_>yR?%uufByV!>5zI)OB@9Z5<^c zw&tI6N%ghWANi_g<1sp}-za0KS;T(Kz|J~WcayQv9Zm8hjuv&XEbuu+#cE&75P(y{ zzOoG7_vxu+F31M@#f5U@rQ&C1@A+EDYXU$E<0wiuW;Vr{);=eYPR<{nymQ; z_(KbC_#87e#$Vjp=%QANi!i4?ufTS;--+*3L6Z-uq6+eRKK9`HR^GezNLC#?ERDwWoq) z9j|(98ktwP3QY296$Me4g-xGnNlx3L$IiZO-2^%p*JEJ;H^ZuXYLZesTKR~cEu_p# z``I-7fhT8;qC5mdMYU8_$6!)uk!;BA7ev#iT;XeUl(trud1gqMOQl zIOoZH(QxGvy#h(y*PMnM_I4T>uX`3|{$bVf!bUpb{&;cIhG^=NhARuG0r%Ff9V@8G z&Tc%pW!>hkdezM=Z4p%+eeaDL_j4Qm$uht>v;A5PBUZIKi!nn9L~^C(+Q4<2*kXdTpYfYwO9kecKNTr)M0Q}^h6q2 zB5^`MP(v?QJ;fn5o!*oc*ugHkuOuRx-J;Mf#K{C|OUoPlozXfJ)<1gKm)rMy$`6Qb z7~q-{0H98umGxm8%8G+Hqa9OCx|YDqnnZN-$&CMFN@6O%9SfC+kqKV=es4{0})a4px{7sz;i6Ncv=+w{RNS15R4Nb`)TTlGOQeR)eMl}5L&Gv9z;1xid#yjuI~Z;2YcSz{4;2N{!_>dmK8Ey1?%-u4Qy4b`G8x2 z@pM&9&6cSh-f_q@XK!y#17mPdoJ6lOY*gl(e`93#sA}oOyTjslUB)^}u&4Pq5tX^E zz8d~Wj0brbY-}~M4d@#3=Ze)>_I-=4TSRcI>4hZ06c~>8mSaK?i+dWj@3MZrc#SbX zv~ruQefH=fDSkAJ^Ktj}gP7l6PU2z*m&BYdnHibdrY!4(zyJOMrb+8jc%a!HRtWZR zb&x+ft)an|q-lji2YiAePw{#S;8k(|QN!8Pz5}{|b5v9;8*MLwI;GW!LC_<;BA*rg zPPXD&wBBaw^TqgE*87Mcb592DcP%nBG;}{nkn*V6G?^e7h&2Dy6e-f zx@!eUJNht5tCyX976Twa>UMhh!MD?lI(ppeu^L@FOm5BYGA^yQ81E_@5pw87z*P}0 zeo}aR7LnVi+v`><*Fp|&ktdSZ4Wkjx1-E-8@40Rqhb9sbpBc5KG#Bymr=e8G?FBE3 zUid##+r%+_o@ zSf3W>;Xk&@^r)U*3&OH!?kZff=(#kucv;}X8{R3=KZr_T*2%PB+fH|BqYt9zHkm&( zEI;pi47x+SK%(W{(Hxlq$O$$EkgldR)s< z?d(!iD`N@R6!g-0a89Y|9`sYBzK@SlVbwyK8$`UHte59qFLt!H3h-QQt=&*#X3v%5 z`g!{C2W9{bMMYJ?MnD9R+|9o)^#=!uo-08Z*kT^qD&hMn%>_4+I>FU*TMe%fP@g^K zCy!N+93q{|7sK1=hxfQy>AP|nDM1%2XrCw8ajveGTg5kwBUBW>{&B|>=E9oabKuce z_8dC}L-Ml*kkgcwKP)8l0-AJ1!bX4=ouvT)Q&XJn?K8pd$DZZQkG+GshVDevVQpo# zQ`jN~vJTCuf5zT^2BH16-K%}2Rv=@9oYYQ6P{((QSaGg@iMIR|3Hc{0*Mk5mv>d92 z>5u&ss@5%qs?mh{Gd#p0CgemJkz{NS&XG9@J#TWD*pcd5Cn4r@vBpl zW@&Heyv3WU55+!2Tn7}}!3igd)3&~(+pUfUU&$^X`(-oT0C8w)XcVsgABHI;nc38` zq-L&;nB~zEZm8!_hODEE_s&dQPt*tm@f!h%&Z8LDzuNv6p)d@Ci6Y)R7HW&CI~m9GCu)Q6)aMv@rXOS{1d0FV{8f z1B#7e#^8_T&FxW@rmZ{pPlwaD*v9=Iw6tYcPAdH*!GZh6fn4&jSW|V{)VRMBahD## zN*<(*Q2m#$=x!27@%Q*nL|*bzeakAFxq%JjBT@Em#U#Yb(bnxB z{|Hf8t zy?`q>Fz66Gdh*MiCvq;smeYoGb#nh$D2f$Oc9?tY$8us&y~CVZ_~SD$q<8KVZ~n=Y z{<98c$x;5PCWi30|Nr(ST=B9l`!^<@ChHo^OLltPx#Q$Hb8*U^5;#%uuvLM&cPWvC z+_=WWc;>|S3&|b=DnX-E|Gmu2V?14IEt5WFk_$6MMgRQdpnT?}n#}9i&(B=mOUzwN z(#1&~4z4qz_U~Cpu57s1>(WMKSn-pa{!t@_+7zAG8R(-}BrPS?(eiMHLwvc!1Lrd+ zKUW$XAh~5V^~~1aG4XRlSH(m9X~ShWkS6tWQknACZ=#|3`)gGYzSiWIZvh^d3+t8% zfJ@KQI`Zw3($LiY`t5Rpx3u}xw?Gv%lpEg!7BKzFZvuvBjOYKeN9;~0D=(Ko{_K4a zKT#sH@+_AV1s)YT$L1iZ5fs7x{e^d*bcPR%d=tzSU|a|E7KcAdZ}4z&aS@pkB%2UJ z5ZZgH!vFhw-tz*sN4Vo~CqaNS%v$|xLm?s{qQ<7ejO8)&w_UF50safyAGDwJDt#Po z9SRAYo5SkA@6wGYpxh8%IDQl|B+iRwU28pNZ9Qo;Z}8(=&UPb54~rT!{-pfS13Sui?$n4QO0_ zj{nogzOrZeCI}|rJ#E!JAHq5b|?4~Xp@LDPe@F}ymo4lPHgCvF#Er*P32D= z&_bkiLP@>Uk8T&n;;CIIA98U zfRztWo-^_sm5pMgkuOItTzfE%oEaR3)=`8E$S~|+6mz0nE8CxcAIxDHb7AW~^{7qs z5!}RUZcqlRXB+!nT@mCLkn|PMD9r$T=oX7SvH0~isfj8?LQ(0bvc4`DZsv*ATQd<5 z&2n(lN($qxT3^4uxjOn6MA@yqll?WZi@Hk-|5jdxgV<1q(uU;ZVYrHLNLr8LCAVH{ zd*hxW?l2E`O)=5tCS=HR5JMcmRI=X! z_gB9b*Q9^~W{4CMXQ#TFiw5~Gq^i*%3QpSyCEsp`I-B1$LK%Z%pU?~k>3v1 z*rNvPtjASl{Au-^^%>@R58^?yz@Z=m*E)#P?p0E68)@e-V$q1Pk|{m?-5}A_-g9xh znGW0)asUJA`U`TJ9)|&RSYeAHn5_@vwhE*GIB|AI@alrG?R4YIFtIbfS!Z-MHS;bF(WE64N459>;U@adVfK*4m-cbHolgkU2l%N|_K+Qd(XFl)NAT4*8vM(BY+= z|MpjN&5>$~e#7_IflGMR-YLdrS`BhkR@Fzn&RFcV)k`J8PsrV=mHP>KAe~A1<&l`m z8WjevnT{uqnZa5{X7YcpG5*OD1p$*N4~3`|1ept$ADxcuu^{)CH^q@_x|?a;e|lnx z^S?FC)O(lc{nYW%zE24weFzYvV>dC1-I+bY6&pO;&nK&2Q(29}kqY2w(u(4)@{wHJ zYO1V$r)p57NxipAZf7B!=BPj-ccYW_vAH>XNQ&)^veNTFNV2pt5(iAl%nwCX(>=ax z)mLsvyouNjX)F20g(1-&1nr9I8g@N8&fjj@CIZ~_Okclo<51nR?%PbHI82A&;n~0o z_{;#lb{IECQ@?>4jLui9)v+`GxLRKLQ%8q^y7XESIa=G=r17KlBoNU!(oZp;7KFx z{5J&`NQZ&zdclj`92`a_Ci|M2W2?Zq3Hy5Q4}1YASV@7wz`$tMjR!j2Jz~U!_2&ug zP;hr>IQ@}_|B!4Q_YbSQ!#CCAxi{<a()pO6BEi6Ffbni& zt*uct-{XA=fCwdc7a5SmRN!m6!b?9~2b1Ei1fl?D^~RkDAg>u4_i7H^0o!q5>>V$P zjP>mEA~!*N6ds2Mh_*aj$!bt#KpGZ_DNWb*&1_AX_jbE5i4v5NX-C z_;_^wn91Om>h62ivM4>+l1D|}51bhAy^xCqr?&MxfQY;|xd3+mxmVWlvS7+#gJIKA z%?>mVe^Hr$n>#mgt<;UJki-P*wa^VurD5?w=lRJaX@^81)Raev9p9b=ZL@n>> z#F>zk0T_R+U6zFpLMzDAhA zo$dvhMdOET+`|SC2KN(z=zv-^gG*S@-s5%_0An-aZP?*t?M%J{2Yye#{R_&}+kuuu zX%@6Yp8#Su!(FeO^Z?9SKsg}yUYVFk%wXhA57eRe#FmYNz@FV#ecHnCdD})wvEPi? zLz^#Ov~okTf0vT%8D*h+C|I>lkIg=+H6I!?w|_owYKY1*yhvrDa>@BBSQNiEW)CW1 z^($eGegB>-HYh0cl2S>BznX8!-Jt4FUj;t?AXa`q1z!cd4sQig3wn z(yY@^#LyIFr%_Sh_?fGE<=y+Y_!V839P(*cc-LNQt~o7}s)ZhH%;5-^1bP~{5jVS0zZvZ=PRN3zUWwKS{N<+bP;qVL&Hw2VSpdwzFYHMFUA_R zk(H>mJo_+a14tCBHs*8O(s(oO*tU^6)nNWq3nB+2bS6ez5aTwK*NVFuB&Yf*0HJFY zdCzUrGdQ^6V&SfLb3KwR`#VpNZES4v1yHOoro=aJr7%J+UJ6}`(VbQue%q)l7 ze$c-4+#cL&5E=p4_TL66fCOc8a}zl_Mfj=K1N%^y$f-t7)kyJ@Ir@!b(&YU8u5UY% zgrr_P{SZxcda2HexA)a|X*{^(f_b4$m+s}&`?pF^i{-{vI2XxrX-v z!f{81Tab!t3)s+W&WrOiFz#E+Qpf4`tjgqrd=d7#dp%_Gl3L zq^DyVmOs*MVB7oUfCoLhq|0UW z!-3r3Dv#Rw+WM9t2h7Y+zxIc&@Uu--i9sRZhz5?Nr>?eENI*dS?AdKCg^28Q(6bt9 z6*of9W#P~XOCr-W2)$FXpSEbNU^p0Quv)^6oOa_~igBm3w03xC=aHOkZ3Vf;s+A=f zvPO$pk#nmE6WzkTEz4C7)f~G-4b(giyn)5_79dDC3S9d*<~OWc|&fbWa3}hm^bb z&XRGTK_`YN6k06*u>B!D>(SO_|D z;2KJXhJPf`;&?0hOHNSLc{*Tv0|AIeP}OXMwBp;)Um;u52$_|%ExZAJpP+y^5Q6* zH863Z|J{NNwr~jqAy6igoNHJ^)SN!N!JL>g(!J=fV;X7@SuXA2zxdIDsgWEzrLt+r zPI>U%Kh8|fjGiFe3*;h4BRhw}A}|(^Znl|aE%|^-_+&IF!lF?~wVnxy?+PTm5ftMc zF+*ePe%z+trqiE4M_KFw{?V0|2YJTzyGYO+Owb$8S}3ZDP~AVy;sw6(bm2C6hJ)6viC>d|C!+5Vd_ZQc3;pcw5HJX%;t zER(}LifukIAptaFcfF<@$BeWzYqYvY8J)iVp63R9L8qL+Qh%e8f9v?`S`PZ*o*voq zId>$Kj*Z$)lp|+~%(=Eo^X$PlMCPjSEp#fj=4r9-MJgQG!YP8|+)2Mp;13o()F6#s zM5C##ocm%=y*7c#&kP$+cNtK7N4jPoQUAI%@+_Og3m@kzdcOZKs%xdm-q{WVUq&Xi zA`yL!F-6Pbkq~Y=BBQ^%aSS=tt%4=u=3LV&O`$LF{@^VG;)7a@e3+QX)1{vOJM8g8 zpVaJZZ79e-G19!9sVR@($;q>qXMZrb(8FQBw=P=7r;Y2V z>#8!dHFNd`p7}3a*Ip8(l`b6&%~-e?ac+n>hli3cwp#67c5S*kS($8YY%Zw&*Ot46pX=R(-5QDxuMcGXE=i|+VSqoNH$!tX zDds6$Fo8$ay#p8jgeY4^6JZj7vXXb%;5j^sjh&vFl6Pk=WU=&(Qv>?8J`L5LZo$9_ z_ww@ka2yU0lIINHNu}GLPqd9LVHAIl{(t))!w3Cc(?5fxO>$~KGXjMI6nT2MWlOL^ zN9i!k_26s24vV(U&bgdbMG|QPt2KKb2P}TM@c94e?K;Dvy1KPVOcXSTFbGPE zNExaURHVnjp-T&*1`Hn}fLdBDbf^W z?t4IS{O4>l$5_?gMoJ{q$)tO zwr~uO*^-Fgk%*V&`{s^@^Ypc9D>OB);fdJ=A@mthZ95VR1~h$2e&Uf)kh^mKRE*gO zO6v%TpRA}S4WIU}cZ!Z9=EidR>%4z!#mC|2|QIL8qLO24x}eT%___4c>3K z%e=L&X+l|*tDcBPbpd)_oKI47&fy7?zeKHl#Yj*MH z?wvX=4C*)kbkih9CLNQ8f7F-_T`_~T{45TPlOL!aL=ORo8@P8!aE6)&8vQAxnGwxm zvyB{PA3`M}MkWYYto2OL<;@C}5ol5!`NfSaX zOBnQ=q#Nmo&RxMX0YQ(*G8&oXk3EE;$3oQ`cZF2`?HW|+Et=lEH?i%)<~>OqkDbtq zHBDBZs#$?h4*vyqI#u>X-mO~#dbrK&fNXM4Rft`{3ro!Hvn%zIEhE}g>=}#(LeB7R zi!*F~HW2Ct{`Dsop+xQWapLBb8vOm<$jK7Srt$QA?R4pHL7~a?;O(iLpeQWO01gTM z1A}RhnT@bmTwNe6-l}k&gf52YVu@F_YWVmJ>vA^LHM%xtSp%xy1g0#$1ljumt;zOw#R*OMm^)anLc12p)7xqN1- z5FtSf6Dl*H_rY@Pjp*&2ownWQ3{n1p{sfEylx+^iJ|q^Hb(pulE2x}fK&dvzyuG(Y zr5rWpwO)Jqk{5x9ow))nA=Y{LF#ekIOntF1@+CzYm{24-CR8Tb&o0=Z z`$IQSa~;|Lp|t{^TtPvBEo{Q}MWuk!G|E6cq6f#JouJgIB$`~*9rW+7KMTKsQ4XEw zPiGSdo@c`;Yr_SQ6g367xTy#ttkAdi()#Tm0MQLa`cHD_A$Qf;anIrWg$oT~wRZsJ z!1k;X_CxwaG~<>YcW~GgC3aX~oi~_2iblMz@hWO8Vns~=fqDw1qEa$WddTq3yg}*C z{vjps;t1q!v5xEf7uN}r%ks@rsj!HP9xq5E9K-QGnsVpBBocY z76gVcWDcMp7gdr7sBx;M-vu{R;&_`(cS4~CG2hd%&PzfP5vrh3fMvF+(!5~QZ5um< zGzRoyu>yS!D7m!BxQ!F+NqrliI@ps#;)(I1R5b14Cg%-eVolKtP$~+5*MA^k%VTEn zz@N!p>Ie4kSKGEtK!<1bYTIJ>jXxt}gtJ6$W0>RVdzqQ=9H}-J z^!;^5kOE@g>aEPXxoS_$V-)T|2fzGYS;53(bf*ix%os(!1b$j**7}Z`W)>Jt#-hDc zE_sE&4d1qr3C$SNu?qzakM6>*Jq{BgdSY(0s$mEg1l63>d+hU|rn-6n$YjX!JIN2A zyc>Rln1=I(SmHPQ&+XphJAj13Xqc}=kSaqr65tZcW#fgY>a*_orp3<;NUX*(1`m(j znFL{?1cUXsGz$i91>H{6^bk#pwM*kGIkyN;jku$PtkBS*E#{Ase9H__~j0JUeMuzedT3cbN5iOMKP(u`)kzp!2A8T!`=zZwE z0bDo9v>bnb9M}GulVpq0WCqWNb9#^@``j(=bt)fRYsqG3VKFhDOm(&r*H;)*6KV+% z0{#Tw1?nS0Kg{Pc^D4fJ=*}59dvvFeR`5NI-|!Bnxe3O_Gf1zOC3xu=ef@LMNry;O zpSEbcPteL_mNYi8`yUD}<&QfILFts1m4UOYz<482z+fa8TUUrjucR@~_hD-ScE`nF zR)WTDP#=A8dyKUjy=j34LzJ>DskWF>kA_%LV zLT*DQYGGrlw&KLr00CIi75@>YaudK)iuk_T-(i9vK+HF4cD4znxS7}qEWJGxjS>nf zEFHvM=&9Zv<=!Lzj1{>Q&yq4I78#&I-ss{0Vg)eKBM^gvqUu8393D73Ib{K|fvPk( zgSf{+{inCykJ*H5P3}A`w?{pe_$(|qU0K=#nMiT8F_}bF-x0Li=1S_WSkPhcfUaGo z0zG2_a)hL>Gp@WuVeRQ0G!~AGXzkFvHi#&LwD4O&gd(6XoG6|Vx@mFrn+*5+s6uYP z!91I}E0+7%5%5xEOUzv`JIpN##>@e?|1ICHs;V1CkTFki@DXBwhS%*s{9)IS;M^$q z$n}i%n*?tUyDAXy775zLOPBaQZJ|HnqfQW*l8%;^1dO_avqM3Sl8U?$d;#)x%QOQR z1>g%-5N8ZHj3CsfO{6^~EHWr{2ubpCa@9w1pn~5|wITz+f}W~rEs!GqQ8`;ZQS+x` zI6Qj%{TPg6{WhMv09V4#T>zY;$=Z6SM6HD1#hTn3Cj|B{^OxUYRUmXrou*K6xF(`snv(7uVLs= zeClqx%ZhM)|7Y6o$F}AkDEpLDx=#R)7{!`q#rxgS?;^Zm`P!Jhn(+*fvJL5{q7_N# zO^t|1>#I%)J#x-c#Y8sc_sKlfg#4EjjN+`JfE1=RYZ`tXcE{go%HpQt*1-lPHLq@P zHfXr$d&s7n?^`WJ&C__&@r=Gy`)^)E6yEZEv3Vy6-#j zZFept^7U|i3V?AU8Z<9JesOJFQ}68Jf-YpX$^PF_8`yFOwKt<-@#iY&I8KzVnbG6m zgHrjZxE>ZPKo=4@!~ruiGx8KtndaE9G2JW>f&^p-5$~z7J9ct>;u~ZQsGi@n0fvh= z%8a{^j(iGbMlGaH;GPOWl@Hc9^$tIgNH#OA{!7njY_ESVoAPKl8-#Imv zuN=g=;tXNx+p3`u(Kj<8-|Oq@fLsLSWUhFchDqf(M^M%vEy0-X8E_=bS>$6(I@l#F z4dxX<glfVB;5_#6`XSEHj1-&r6hz1_Q0Kildme7A7&3gp zG^?hLDD!A8mU)DzFIlXJ*X(w#^_9FFRuvTe2~l93!lzLm5t20Ou50-mRn+0hDypig zOR(Zp64`R%%Ue*_IEp);BLnty_U**$tro((1`rvcksk{&5L)xf@Xizb+b>QIvuPZM z)#Ic^1_(lP#dpvWxg9xT*zT~4klrX8OroyUCiWJ(g!&=(wc1!A47oz0ng)>zHUpzH z+(@$7ywbe!y7~i%)N;Y@LYhXImULZsW;7%@W27|_xL5DWz*TMu@W}c6FeUwql)aeK z156Ms!xR1v$iAz|cs43hG^e#;7}f@*&vIgY7slal+qTGNm}NbrJEEjfAZfgk|6)o? zbc05O#0fW!ubPnkPa$&lC6HThhmcD9lPBm!a&~o%9syyDx)-;qtD9SR0=CdfPN1jC zCyM+4TDhPE?Bx|dlBX47{kX&6QpF+Z0U8i6tn?4lENX0}?!O(|HR6w2S};8sNuyK` z+FD*Bv!|QE@_+ed2%vW-*FvoT5)dR6pq2m$v7+vQ((!6~BvyD;;F}3Ydf#}$?8Gfd z=Q0}6$b^vw6!r}oGX$B6gM%)KrNPM&0Z_)rnPwrUjXvIr$G-Rj=Y}J1uX)!m-!bdT zk@^kX1d^eM<<(GX5UMB>!(#w zOW^qjLu~*{FwJskLS?rJARE>V!{LILa36=U#at&O@LWyu_ENg$U^gugZ@Nn?&L(w z!hr#@yh{B7`G}W~5smAE!~pzSqKWkS;~#nRyl2qD0bbxG!RwR=Eau9Uw{D%+3TG;Aqacp0`av#SG7jOcn@7L@DocSgOsZ;eC`$2Ow1BcAFe<5rn z#X;G?3hz4XjeZwc>a{f=Eo7-inW$~>$DeV<;_)?hscCtD)wc2?wy-k@7?oK`g-66U&r ziw?rUdJmCI7xZ$$V;em%uT7-vdQMJ#h6PcQ#$}DT|KQm7<~}Ym3q|oT9_`y zsI$+v-`}7e&2dP8K!~z3;9T7bgaX`F0`j!}1kkcWk#raSui z6(hX89%qr<|(q7Hi>8s!6W398B!@8Ta}g9_G^zPJKhFijw*P!_?-^o zbx_{`d+8zc0Bvtj7HPVWy&}(o?4_m|5YAnUq!GIFrrBY2JyLe#AP_j9Ih^?_VK^>b zvA(X}G?F7KbHIBKhKmvRAfLJ0V!9i!fcvkpAFP7p!_JALQLu0CR z=3`YVD!Mv5MP3~PQJfA+V~@2pu3cf4AXDv}MULUP7s<|^EA9ZHOvNYxJvFi#ZL+2Z zwWj>W!Q0>>2DfX^v)%503>jN~BSMDkR@&!pNx&l9rk#omX|_7QZ-g4^3U{FYQ}sxR z7AE^*StqQ|{FcRU;Y>I(L@BW7n?~#h`SwE4%ZY3^)2t58Rkb7LK#zzWf~u@FWmRe< zhc%mO(nvjys8CoFe~B~{d>>r)ME>^qIS!;YP@8TrX2%Je{{t@9nT+W|&;ogguG1=? zl!1Zs`9brmI3LqykpWMF-64$pt-RYqf*K73kA_FBCzpaw zaYl*#=?3toAVlBPe%FYoi^kB(FbRE-r!t&>#^QB4(!`EJH!MnVCjiB$DKeV;#wO;V0GL!I0|~#vHyoW(?Q;x_?b%l zzEsh2!kaguQVLLDDcKn}Q$O6tkVG%nE6;4*kU#qtDR0Lk|GFT|)hUqah z)>p3MIKD)5N$Jp-aI<${8>C7uoMrP2{6uEvGboFD12B`gpa)}Lz)}IALvF@(wzmWB z01jR)SRh9iTNMaTq?t>OXM{G2vrC zSl0MkP*tGLiP23EC1`=<%8$I3oBN_dxy&zV06&1|`lBQBMga;u!d!AB>YcJu2-ydMKGLBFQ<0t3&x1!;Ig+P53FLPsYq`{@_1&DCg@p;K76p}+M7T7PijQ(T zV@xB(NJzz-WIY62Kouyj1Fi%TyXfH`h-mXTj4M2X7|A4ZI)^e2U`t6CNxat@-4`^< zVIUrs78wKH4gi)$(QF17Oh}^Vz(0Hj-q?LvfX4U2VHvmB6A|7r=#GUq~>`UfE11QXYZ$iMR%Vw(|PZeVJbxK^jj=14!Ea3MY>F< zV8_WGm>B?XJS-9BdT%%TUQ-h^pGx;sCRe|<{plz5{IBe<>b&bhfQ-DRg#OZ1T_`a| z#z@d6cdz|Kiq$u|OrRDkC_XQv;N;q%Y{bSg{@OnV9NVXVv+h_M)OiEg9 zHX_8nxpcCA9c==C0sDI;U-Rozpin3`HR{<4OCIlOpciT@Kqet0jVppYQ5;265jz$0 z7=+l@IUaMLm~M!mgj!DphD`k8r1R8<=mxNoyM?xpa<(!-&xD|6Y?iF<4SWu- z4~(3f{MB!{o03hdK&#+A&`+V`!^2{KTIEVlHpVy*(;BcFS(e%2Ps=b4s;9dfcOLkZ z&*V!jz0I;;`M+B_y33N5Eq%> #FFF9C4 + BorderColor<> #F9A825 + FontColor<> #5D4037 + FontStyle<> bold +} + +skinparam package { + BackgroundColor<> #C8E6C9 + BorderColor<> #388E3C + FontColor<> #1B5E20 + FontStyle<> bold +} + +skinparam package { + BackgroundColor<> #BBDEFB + BorderColor<> #1976D2 + FontColor<> #0D47A1 + FontStyle<> bold +} + +' ───────────────────────────────────────────────────────────────────────────── +' DOMAIN LAYER (innermost) +' ───────────────────────────────────────────────────────────────────────────── +package "Domain Layer" <> { + + package "model" { + class Person { + - id : Long + - firstName : String + - lastName : String + - age : int + - phoneNumber : String + - email : String + - category : Category + + getId() : Long + + getFirstName() : String + + getLastName() : String + + getAge() : int + + getPhoneNumber() : String + + getEmail() : String + + getCategory() : Category + } + + class Category { + - id : Long + - type : String + + getId() : Long + + getType() : String + } + } + + package "repository" { + interface PersonRepository { + + findById(id : Long) : Optional + + findByFirstName(firstName : String) : Optional + + findByLastName(lastName : String) : Optional + + findAll() : Optional> + + save(person : Person) : Person + + deleteById(id : Long) : boolean + } + } + + package "exception" { + class DomainException { + + DomainException(message : String) + } + } +} + +' ───────────────────────────────────────────────────────────────────────────── +' APPLICATION LAYER +' ───────────────────────────────────────────────────────────────────────────── +package "Application Layer" <> { + + package "usecase" { + class SavePersonUseCase { + - repository : PersonRepository + + SavePersonUseCase(repository : PersonRepository) + + execute(command : SavePersonCommand) : PersonResponse + } + + class GetPersonUseCase { + - repository : PersonRepository + + GetPersonUseCase(repository : PersonRepository) + + execute(id : Long) : PersonResponse + + executeAll() : List + } + } + + package "dto" { + class SavePersonCommand <> { + firstName : String + lastName : String + age : int + phoneNumber : String + email : String + address : String + categoryId : Long + categoryType : String + } + + class PersonResponse <> { + id : Long + firstName : String + lastName : String + age : int + phoneNumber : String + email : String + categoryId : Long + categoryType : String + } + } +} + +' ───────────────────────────────────────────────────────────────────────────── +' INFRASTRUCTURE LAYER (outermost) +' ───────────────────────────────────────────────────────────────────────────── +package "Infrastructure Layer" <> { + + package "web" { + class PersonController { + - savePersonUseCase : SavePersonUseCase + - getPersonUseCase : GetPersonUseCase + + getPerson(id : Long) : ResponseEntity + + getAllPersons() : ResponseEntity> + + savePerson(command : SavePersonCommand) : ResponseEntity + } + } + + package "persistence" { + class PersonRepositoryAdapter { + - repository : SpringDataPersonRepository + + findById(id : Long) : Optional + + findByFirstName(firstName : String) : Optional + + findByLastName(lastName : String) : Optional + + findAll() : Optional> + + save(person : Person) : Person + + deleteById(id : Long) : boolean + } + + interface SpringDataPersonRepository { + + findByFirstName(firstName : String) : Optional + + findByLastName(lastName : String) : Optional + } + + class JpaPersonEntity { + - id : Long + - firstName : String + - lastName : String + - age : int + - phoneNumber : String + - email : String + - category : JpaCategoryEntity + } + + class JpaCategoryEntity { + - id : Long + - type : String + } + } + + package "config" { + class ApplicationConfig <<@Configuration>> { + + savePersonUseCase(repo : PersonRepository) : SavePersonUseCase + + getPersonUseCase(repo : PersonRepository) : GetPersonUseCase + } + + class Application <<@SpringBootApplication>> { + + main(args : String[]) + } + } +} + +' ───────────────────────────────────────────────────────────────────────────── +' DOMAIN: internal relationships +' ───────────────────────────────────────────────────────────────────────────── +Person "1" *-- "1" Category : has +Person ..> DomainException : <> +Category ..> DomainException : <> + +' ───────────────────────────────────────────────────────────────────────────── +' APPLICATION → DOMAIN +' ───────────────────────────────────────────────────────────────────────────── +SavePersonUseCase --> PersonRepository : uses +SavePersonUseCase ..> Person : creates +SavePersonUseCase ..> Category : creates +SavePersonUseCase ..> PersonResponse : returns +SavePersonUseCase ..> SavePersonCommand : receives + +GetPersonUseCase --> PersonRepository : uses +GetPersonUseCase ..> PersonResponse : returns + +' ───────────────────────────────────────────────────────────────────────────── +' INFRASTRUCTURE → APPLICATION / DOMAIN +' ───────────────────────────────────────────────────────────────────────────── +PersonController --> SavePersonUseCase : delegates to +PersonController --> GetPersonUseCase : delegates to +PersonController ..> DomainException : catches + +PersonRepositoryAdapter ..|> PersonRepository : implements +PersonRepositoryAdapter --> SpringDataPersonRepository : uses +PersonRepositoryAdapter ..> JpaPersonEntity : maps +PersonRepositoryAdapter ..> JpaCategoryEntity : maps +PersonRepositoryAdapter ..> Person : maps to / from +PersonRepositoryAdapter ..> Category : maps to / from + +SpringDataPersonRepository --> JpaPersonEntity : manages +JpaPersonEntity "1" *-- "1" JpaCategoryEntity : contains + +ApplicationConfig ..> SavePersonUseCase : <<@Bean>> +ApplicationConfig ..> GetPersonUseCase : <<@Bean>> +ApplicationConfig --> PersonRepository : injects + +legend right + Layer dependency rule + Infrastructure → Application → Domain + Outer layers depend on inner layers. + Domain has NO external dependencies. + ---- + ■ Domain Layer + ■ Application Layer + ■ Infrastructure Layer +endlegend + +@enduml + diff --git a/onion-architecture/etc/postman/onion-architecture.postman_collection.json b/onion-architecture/etc/postman/onion-architecture.postman_collection.json new file mode 100644 index 000000000000..ed0cbd5c58f5 --- /dev/null +++ b/onion-architecture/etc/postman/onion-architecture.postman_collection.json @@ -0,0 +1,83 @@ +{ + "info": { + "_postman_id": "79aca374-7080-46c1-938f-89b327a84d6a", + "name": "onion-architecture", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "51221021", + "_collection_link": "https://go.postman.co/collection/51221021-79aca374-7080-46c1-938f-89b327a84d6a?source=collection_link" + }, + "item": [ + { + "name": "savePerson", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"number1\": 5,\r\n \"number2\": 3,\r\n \"operation\": \"multiply\"\r\n }" + }, + "url": { + "raw": "http://localhost:8080/api/persons", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "persons" + ] + } + }, + "response": [] + }, + { + "name": "getPersons", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"number1\": 5,\r\n \"number2\": 3,\r\n \"operation\": \"multiply\"\r\n }" + }, + "url": { + "raw": "http://localhost:8080/api/persons", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "persons" + ] + } + }, + "response": [] + }, + { + "name": "getPersonById", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/api/persons/1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "persons", + "1" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/onion-architecture/infrastructure/pom.xml b/onion-architecture/infrastructure/pom.xml new file mode 100644 index 000000000000..d26e2a7ef69d --- /dev/null +++ b/onion-architecture/infrastructure/pom.xml @@ -0,0 +1,87 @@ + + + + 4.0.0 + + com.iluwatar + onion-architecture + 1.26.0-SNAPSHOT + + infrastructure + Infrastructure + + + + com.iluwatar + domain + ${project.version} + + + + com.iluwatar + application + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + com.h2database + h2 + runtime + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java new file mode 100644 index 000000000000..87621179405a --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java @@ -0,0 +1,38 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java new file mode 100644 index 000000000000..0d971f5c2244 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java @@ -0,0 +1,47 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.config; + +import com.iluwatar.onion.application.usecase.GetPersonUseCase; +import com.iluwatar.onion.application.usecase.SavePersonUseCase; +import com.iluwatar.onion.domain.repository.PersonRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ApplicationConfig { + + @Bean + public SavePersonUseCase savePersonUseCase(PersonRepository repository) { + return new SavePersonUseCase(repository); + } + + @Bean + public GetPersonUseCase getPersonUseCase(PersonRepository repository) { + return new GetPersonUseCase(repository); + } +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java new file mode 100644 index 000000000000..812496c56328 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java @@ -0,0 +1,55 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.persistence; + +import jakarta.persistence.*; + +@Entity +@Table(name = "category") +public class JpaCategoryEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String type; + + public JpaCategoryEntity() {} + + public JpaCategoryEntity(Long id, String type) { + this.id = id; + this.type = type; + } + + public Long getId() { + return id; + } + + public String getType() { + return type; + } +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java new file mode 100644 index 000000000000..6a03762555d0 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java @@ -0,0 +1,87 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.persistence; + +import jakarta.persistence.*; + +@Entity +@Table(name = "person") +public class JpaPersonEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String firstName; + private String lastName; + private int age; + private String phoneNumber; + private String email; + + @ManyToOne + private JpaCategoryEntity category; + + public JpaPersonEntity() {} + + public JpaPersonEntity(Long id, String firstName, String lastName, int age, String phoneNumber, String email, JpaCategoryEntity category) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.phoneNumber = phoneNumber; + this.email = email; + this.category = category; + } + + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public int getAge() { + return age; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public String getEmail() { + return email; + } + + public JpaCategoryEntity getCategory() { + return category; + } +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java new file mode 100644 index 000000000000..8ef14d7b5dc2 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java @@ -0,0 +1,106 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.persistence; + +import com.iluwatar.onion.domain.model.Category; +import com.iluwatar.onion.domain.model.Person; +import com.iluwatar.onion.domain.repository.PersonRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Repository +public class PersonRepositoryAdapter implements PersonRepository { + + private final SpringDataPersonRepository repository; + + public PersonRepositoryAdapter(SpringDataPersonRepository repository) { + this.repository = repository; + } + + @Override + public Optional findById(Long id) { + return repository.findById(id).map(this::mapToDomain); + } + + @Override + public Optional findByFirstName(String firstName) { + return repository.findByFirstName(firstName).map(this::mapToDomain); + } + + @Override + public Optional findByLastName(String lastName) { + return repository.findByLastName(lastName).map(this::mapToDomain); + } + + @Override + public Optional> findAll() { + return repository.findAll() + .stream() + .map(this::mapToDomain) + .collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of)); + } + + @Override + public Person save(Person person) { + JpaPersonEntity entity = mapToEntity(person); + JpaPersonEntity savedEntity = repository.save(entity); + return mapToDomain(savedEntity); + } + + @Override + public boolean deleteById(Long id) { + repository.deleteById(id); + return repository.findById(id).isEmpty(); + } + + private JpaPersonEntity mapToEntity(Person person) { + return new JpaPersonEntity( + person.getId(), + person.getFirstName(), + person.getLastName(), + person.getAge(), + person.getPhoneNumber(), + person.getEmail(), + new JpaCategoryEntity(person.getCategory().getId(), person.getCategory().getType()) + ); + } + + private Person mapToDomain(JpaPersonEntity entity) { + return new Person( + entity.getId(), + entity.getFirstName(), + entity.getLastName(), + entity.getAge(), + entity.getPhoneNumber(), + entity.getEmail(), + new Category(entity.getCategory().getId(), entity.getCategory().getType()) + ); + } +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java new file mode 100644 index 000000000000..f920a4090e69 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java @@ -0,0 +1,37 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SpringDataPersonRepository extends JpaRepository { + + Optional findByFirstName(String firstName); + Optional findByLastName(String lastName); +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java new file mode 100644 index 000000000000..74878dfe21e0 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java @@ -0,0 +1,73 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.web; + +import com.iluwatar.onion.application.dto.PersonResponse; +import com.iluwatar.onion.application.dto.SavePersonCommand; +import com.iluwatar.onion.application.usecase.GetPersonUseCase; +import com.iluwatar.onion.application.usecase.SavePersonUseCase; +import com.iluwatar.onion.domain.exception.DomainException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api") +public class PersonController { + + private final SavePersonUseCase savePersonUseCase; + private final GetPersonUseCase getPersonUseCase; + + public PersonController(SavePersonUseCase savePersonUseCase, GetPersonUseCase getPersonUseCase) { + this.savePersonUseCase = savePersonUseCase; + this.getPersonUseCase = getPersonUseCase; + } + + @GetMapping("/persons/{id}") + public ResponseEntity getPerson(@PathVariable("id") Long id) { + var person = getPersonUseCase.execute(id); + return ResponseEntity.ok(person); + } + + @GetMapping("/persons") + public ResponseEntity> getAllPersons() { + var persons = getPersonUseCase.executeAll(); + return ResponseEntity.ok(persons); + } + + @PostMapping("/persons") + public ResponseEntity savePerson(@RequestBody SavePersonCommand command) { + try { + var savedPerson = savePersonUseCase.execute(command); + return ResponseEntity.status(HttpStatus.OK).body(savedPerson); + } catch (DomainException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } +} diff --git a/onion-architecture/infrastructure/src/main/resources/application.properties b/onion-architecture/infrastructure/src/main/resources/application.properties new file mode 100644 index 000000000000..6a3ed156f558 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/resources/application.properties @@ -0,0 +1,19 @@ +# You can also specify the server port in the application.properties file. +# Uncomment the following line and set the desired port number to change +# the default port (8080) to a different value. +#server.port=8080 + +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +# Force Spring Boot to always run SQL initialization scripts on startup +spring.sql.init.mode=always + +# If using Spring Data JPA / Hibernate, defer data insertion until AFTER entities are generated +spring.jpa.defer-datasource-initialization=true + +# Keep Hibernate's DDL generation active (or set to 'update') +spring.jpa.hibernate.ddl-auto=create-drop diff --git a/onion-architecture/infrastructure/src/main/resources/data.sql b/onion-architecture/infrastructure/src/main/resources/data.sql new file mode 100644 index 000000000000..6291dd984e31 --- /dev/null +++ b/onion-architecture/infrastructure/src/main/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO category (id, type) VALUES (1, 'Teenage'); +INSERT INTO category (id, type) VALUES (2, 'Young Adult'); +INSERT INTO category (id, type) VALUES (3, 'Adult'); +INSERT INTO category (id, type) VALUES (4, 'Senior'); \ No newline at end of file diff --git a/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java new file mode 100644 index 000000000000..571e8228b604 --- /dev/null +++ b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java @@ -0,0 +1,356 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.persistence; + +import com.iluwatar.onion.domain.model.Category; +import com.iluwatar.onion.domain.model.Person; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PersonRepositoryAdapterTest { + + @Mock + private SpringDataPersonRepository springDataRepository; + + @Captor + private ArgumentCaptor entityCaptor; + + private PersonRepositoryAdapter personRepositoryAdapter; + private JpaPersonEntity jpaPersonEntity; + + @BeforeEach + void setUp() { + personRepositoryAdapter = new PersonRepositoryAdapter(springDataRepository); + + // Setup test data + var jpaCategory = new JpaCategoryEntity(1L, "Professional"); + jpaPersonEntity = new JpaPersonEntity( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + jpaCategory + ); + } + + @Nested + @DisplayName("Find By ID") + class FindById { + + @Test + @DisplayName("Should return Person when entity exists") + void shouldReturnPersonWhenEntityExists() { + // Arrange + when(springDataRepository.findById(1L)) + .thenReturn(Optional.of(jpaPersonEntity)); + + // Act + var result = personRepositoryAdapter.findById(1L); + + // Assert + assertTrue(result.isPresent()); + Person person = result.get(); + assertEquals(1L, person.getId()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(25, person.getAge()); + assertEquals("+1234567890", person.getPhoneNumber()); + assertEquals("john.doe@example.com", person.getEmail()); + assertEquals(1L, person.getCategory().getId()); + assertEquals("Professional", person.getCategory().getType()); + + verify(springDataRepository, times(1)).findById(1L); + } + + @Test + @DisplayName("Should return empty Optional when entity not found") + void shouldReturnEmptyWhenEntityNotFound() { + // Arrange + when(springDataRepository.findById(999L)) + .thenReturn(Optional.empty()); + + // Act + var result = personRepositoryAdapter.findById(999L); + + // Assert + assertTrue(result.isEmpty()); + verify(springDataRepository, times(1)).findById(999L); + } + } + + @Nested + @DisplayName("Find By First Name") + class FindByFirstName { + + @Test + @DisplayName("Should return Person when entity with first name exists") + void shouldReturnPersonWhenEntityExists() { + // Arrange + when(springDataRepository.findByFirstName("John")) + .thenReturn(Optional.of(jpaPersonEntity)); + + // Act + var result = personRepositoryAdapter.findByFirstName("John"); + + // Assert + assertTrue(result.isPresent()); + assertEquals("John", result.get().getFirstName()); + verify(springDataRepository, times(1)).findByFirstName("John"); + } + + @Test + @DisplayName("Should return empty Optional when no entity found") + void shouldReturnEmptyWhenNotFound() { + // Arrange + when(springDataRepository.findByFirstName("Unknown")) + .thenReturn(Optional.empty()); + + // Act + var result = personRepositoryAdapter.findByFirstName("Unknown"); + + // Assert + assertTrue(result.isEmpty()); + verify(springDataRepository, times(1)).findByFirstName("Unknown"); + } + } + + @Nested + @DisplayName("Find By Last Name") + class FindByLastName { + + @Test + @DisplayName("Should return Person when entity with last name exists") + void shouldReturnPersonWhenEntityExists() { + // Arrange + when(springDataRepository.findByLastName("Doe")) + .thenReturn(Optional.of(jpaPersonEntity)); + + // Act + var result = personRepositoryAdapter.findByLastName("Doe"); + + // Assert + assertTrue(result.isPresent()); + assertEquals("Doe", result.get().getLastName()); + verify(springDataRepository, times(1)).findByLastName("Doe"); + } + } + + @Nested + @DisplayName("Find All") + class FindAll { + + @Test + @DisplayName("Should return list of Persons when entities exist") + void shouldReturnListOfPersons() { + // Arrange + var category2 = new JpaCategoryEntity(2L, "Personal"); + var person2 = new JpaPersonEntity( + 2L, + "Jane", + "Smith", + 30, + "+9876543210", + "jane@example.com", + category2 + ); + + when(springDataRepository.findAll()) + .thenReturn(List.of(jpaPersonEntity, person2)); + + // Act + var result = personRepositoryAdapter.findAll(); + + // Assert + assertTrue(result.isPresent()); + var persons = result.get(); + assertEquals(2, persons.size()); + assertEquals("John", persons.get(0).getFirstName()); + assertEquals("Jane", persons.get(1).getFirstName()); + + verify(springDataRepository, times(1)).findAll(); + } + + @Test + @DisplayName("Should return empty list when no entities exist") + void shouldReturnEmptyListWhenNoEntities() { + // Arrange + when(springDataRepository.findAll()).thenReturn(List.of()); + + // Act + var result = personRepositoryAdapter.findAll(); + + // Assert + assertTrue(result.isPresent()); + assertTrue(result.get().isEmpty()); + verify(springDataRepository, times(1)).findAll(); + } + } + + @Nested + @DisplayName("Save") + class Save { + + @Test + @DisplayName("Should save person and return domain model") + void shouldSavePersonAndReturnDomainModel() { + // Arrange + var personToSave = new Person( + null, // New person without ID + "Jane", + "Smith", + 28, + "+1111111111", + "jane@example.com", + new Category(2L, "Personal") + ); + + var savedCategory = new JpaCategoryEntity(2L, "Personal"); + var savedEntity = new JpaPersonEntity( + 100L, // ID assigned by database + "Jane", + "Smith", + 28, + "+1111111111", + "jane@example.com", + savedCategory + ); + + when(springDataRepository.save(any(JpaPersonEntity.class))) + .thenReturn(savedEntity); + + // Act + var result = personRepositoryAdapter.save(personToSave); + + // Assert + assertNotNull(result); + assertEquals(100L, result.getId()); + assertEquals("Jane", result.getFirstName()); + assertEquals("Smith", result.getLastName()); + assertEquals(28, result.getAge()); + + verify(springDataRepository, times(1)).save(any(JpaPersonEntity.class)); + } + + @Test + @DisplayName("Should correctly map domain model to JPA entity") + void shouldCorrectlyMapDomainToEntity() { + // Arrange + var personToSave = new Person( + null, + "Bob", + "Johnson", + 35, + "+2222222222", + "bob@example.com", + new Category(3L, "Business") + ); + + var savedEntity = new JpaPersonEntity( + 200L, + "Bob", + "Johnson", + 35, + "+2222222222", + "bob@example.com", + new JpaCategoryEntity(3L, "Business") + ); + + when(springDataRepository.save(any(JpaPersonEntity.class))) + .thenReturn(savedEntity); + + // Act + personRepositoryAdapter.save(personToSave); + + // Assert - Verify what was passed to Spring Data repository + verify(springDataRepository).save(entityCaptor.capture()); + JpaPersonEntity capturedEntity = entityCaptor.getValue(); + + assertEquals("Bob", capturedEntity.getFirstName()); + assertEquals("Johnson", capturedEntity.getLastName()); + assertEquals(35, capturedEntity.getAge()); + assertEquals("+2222222222", capturedEntity.getPhoneNumber()); + assertEquals("bob@example.com", capturedEntity.getEmail()); + assertEquals(3L, capturedEntity.getCategory().getId()); + assertEquals("Business", capturedEntity.getCategory().getType()); + } + } + + @Nested + @DisplayName("Delete By ID") + class DeleteById { + + @Test + @DisplayName("Should return true when person is successfully deleted") + void shouldReturnTrueWhenDeleted() { + // Arrange + doNothing().when(springDataRepository).deleteById(1L); + when(springDataRepository.findById(1L)).thenReturn(Optional.empty()); + + // Act + boolean result = personRepositoryAdapter.deleteById(1L); + + // Assert + assertTrue(result); + verify(springDataRepository, times(1)).deleteById(1L); + verify(springDataRepository, times(1)).findById(1L); + } + + @Test + @DisplayName("Should return false when person still exists after deletion") + void shouldReturnFalseWhenStillExists() { + // Arrange + doNothing().when(springDataRepository).deleteById(1L); + when(springDataRepository.findById(1L)).thenReturn(Optional.of(jpaPersonEntity)); + + // Act + boolean result = personRepositoryAdapter.deleteById(1L); + + // Assert + assertFalse(result); + verify(springDataRepository, times(1)).deleteById(1L); + verify(springDataRepository, times(1)).findById(1L); + } + } +} + diff --git a/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java new file mode 100644 index 000000000000..5ae2ebf9cb37 --- /dev/null +++ b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java @@ -0,0 +1,376 @@ +/* + * + * * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * * + * * The MIT License + * * Copyright © 2014-2022 Ilkka Seppälä + * * + * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * of this software and associated documentation files (the "Software"), to deal + * * in the Software without restriction, including without limitation the rights + * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * copies of the Software, and to permit persons to whom the Software is + * * furnished to do so, subject to the following conditions: + * * + * * The above copyright notice and this permission notice shall be included in + * * all copies or substantial portions of the Software. + * * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * * THE SOFTWARE. + * + */ +package com.iluwatar.onion.infrastructure.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.iluwatar.onion.application.dto.PersonResponse; +import com.iluwatar.onion.application.dto.SavePersonCommand; +import com.iluwatar.onion.application.usecase.GetPersonUseCase; +import com.iluwatar.onion.application.usecase.SavePersonUseCase; +import com.iluwatar.onion.domain.exception.DomainException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(PersonController.class) +class PersonControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private SavePersonUseCase savePersonUseCase; + + @MockitoBean + private GetPersonUseCase getPersonUseCase; + + @Nested + @DisplayName("GET /api/persons/{id}") + class GetPersonById { + + @Test + @DisplayName("Should return person when person exists") + void shouldReturnPersonWhenExists() throws Exception { + // Arrange + var response = new PersonResponse( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + 1L, + "Professional" + ); + + when(getPersonUseCase.execute(1L)).thenReturn(response); + + // Act & Assert + mockMvc.perform(get("/api/persons/1") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("John")) + .andExpect(jsonPath("$.lastName").value("Doe")) + .andExpect(jsonPath("$.age").value(25)) + .andExpect(jsonPath("$.phoneNumber").value("+1234567890")) + .andExpect(jsonPath("$.email").value("john.doe@example.com")) + .andExpect(jsonPath("$.categoryId").value(1)) + .andExpect(jsonPath("$.categoryType").value("Professional")); + + verify(getPersonUseCase, times(1)).execute(1L); + } + + @Test + @DisplayName("Should handle different person IDs") + void shouldHandleDifferentPersonIds() throws Exception { + // Arrange + var response = new PersonResponse( + 42L, + "Jane", + "Smith", + 30, + "+9876543210", + "jane@example.com", + 2L, + "Personal" + ); + + when(getPersonUseCase.execute(42L)).thenReturn(response); + + // Act & Assert + mockMvc.perform(get("/api/persons/42") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(42)) + .andExpect(jsonPath("$.firstName").value("Jane")); + + verify(getPersonUseCase, times(1)).execute(42L); + } + } + + @Nested + @DisplayName("GET /api/persons") + class GetAllPersons { + + @Test + @DisplayName("Should return list of persons") + void shouldReturnListOfPersons() throws Exception { + // Arrange + var person1 = new PersonResponse( + 1L, "John", "Doe", 25, "+1234567890", + "john@example.com", 1L, "Professional" + ); + var person2 = new PersonResponse( + 2L, "Jane", "Smith", 30, "+9876543210", + "jane@example.com", 2L, "Personal" + ); + + var persons = List.of(person1, person2); + when(getPersonUseCase.executeAll()).thenReturn(persons); + + // Act & Assert + mockMvc.perform(get("/api/persons") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].firstName").value("John")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].firstName").value("Jane")); + + verify(getPersonUseCase, times(1)).executeAll(); + } + + @Test + @DisplayName("Should return empty list when no persons exist") + void shouldReturnEmptyListWhenNoPersons() throws Exception { + // Arrange + when(getPersonUseCase.executeAll()).thenReturn(List.of()); + + // Act & Assert + mockMvc.perform(get("/api/persons") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(0))); + + verify(getPersonUseCase, times(1)).executeAll(); + } + + @Test + @DisplayName("Should handle large list of persons") + void shouldHandleLargeListOfPersons() throws Exception { + // Arrange + var persons = List.of( + new PersonResponse(1L, "Person1", "Last1", 25, "+1", "p1@example.com", 1L, "Cat1"), + new PersonResponse(2L, "Person2", "Last2", 26, "+2", "p2@example.com", 1L, "Cat1"), + new PersonResponse(3L, "Person3", "Last3", 27, "+3", "p3@example.com", 1L, "Cat1") + ); + + when(getPersonUseCase.executeAll()).thenReturn(persons); + + // Act & Assert + mockMvc.perform(get("/api/persons") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))); + + verify(getPersonUseCase, times(1)).executeAll(); + } + } + + @Nested + @DisplayName("POST /api/persons") + class SavePerson { + + @Test + @DisplayName("Should create person with valid data") + void shouldCreatePersonWithValidData() throws Exception { + // Arrange + var command = new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + "123 Main St", + 1L, + "Professional" + ); + + var response = new PersonResponse( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + 1L, + "Professional" + ); + + when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response); + + // Act & Assert + mockMvc.perform(post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(command))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("John")) + .andExpect(jsonPath("$.lastName").value("Doe")) + .andExpect(jsonPath("$.age").value(25)) + .andExpect(jsonPath("$.phoneNumber").value("+1234567890")) + .andExpect(jsonPath("$.email").value("john.doe@example.com")) + .andExpect(jsonPath("$.categoryId").value(1)) + .andExpect(jsonPath("$.categoryType").value("Professional")); + + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + + @Test + @DisplayName("Should handle validation errors from use case") + void shouldPropagateValidationErrors() throws Exception { + // Arrange + var command = new SavePersonCommand( + "Young", + "Person", + 17, // Invalid age + "+1234567890", + "young@example.com", + "123 Main St", + 1L, + "Student" + ); + + when(savePersonUseCase.execute(any(SavePersonCommand.class))) + .thenThrow(new DomainException("Age cannot be less than 18")); + + // Act & Assert + mockMvc.perform(post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(command))) + .andExpect(status().is4xxClientError()); + + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + + @Test + @DisplayName("Should handle malformed JSON") + void shouldHandleMalformedJson() throws Exception { + // Act & Assert + mockMvc.perform(post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content("{invalid json}")) + .andExpect(status().isBadRequest()); + + verify(savePersonUseCase, never()).execute(any(SavePersonCommand.class)); + } + + @Test + @DisplayName("Should accept all required fields in command") + void shouldAcceptAllRequiredFields() throws Exception { + // Arrange + var command = new SavePersonCommand( + "Complete", + "Person", + 30, + "+1111111111", + "complete@example.com", + "456 Oak Ave", + 5L, + "Business" + ); + + var response = new PersonResponse( + 10L, + "Complete", + "Person", + 30, + "+1111111111", + "complete@example.com", + 5L, + "Business" + ); + + when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response); + + // Act & Assert + mockMvc.perform(post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(command))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.categoryType").value("Business")); + + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + } + + @Nested + @DisplayName("Controller Integration") + class ControllerIntegration { + + @Test + @DisplayName("Should handle multiple requests sequentially") + void shouldHandleMultipleRequestsSequentially() throws Exception { + // Arrange + var getResponse = new PersonResponse( + 1L, "John", "Doe", 25, "+1234567890", + "john@example.com", 1L, "Professional" + ); + + var savePersonCommand = new SavePersonCommand( + "Jane", "Smith", 30, "+9876543210", + "jane@example.com", "456 Oak", 2L, "Personal" + ); + + var savePersonResponse = new PersonResponse( + 2L, "Jane", "Smith", 30, "+9876543210", + "jane@example.com", 2L, "Personal" + ); + + when(getPersonUseCase.execute(1L)).thenReturn(getResponse); + when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(savePersonResponse); + + // Act & Assert - GET request + mockMvc.perform(get("/api/persons/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.firstName").value("John")); + + // Act & Assert - POST request + mockMvc.perform(post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(savePersonCommand))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.firstName").value("Jane")); + + verify(getPersonUseCase, times(1)).execute(1L); + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + } +} + diff --git a/onion-architecture/pom.xml b/onion-architecture/pom.xml new file mode 100644 index 000000000000..07df2a1224c1 --- /dev/null +++ b/onion-architecture/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + onion-architecture + pom + + + 5.10.0 + 5.23.0 + 3.24.2 + + + + domain + application + infrastructure + + diff --git a/pom.xml b/pom.xml index 7835110c008e..36f3b56ff0c4 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ converter curiously-recurring-template-pattern currying - dao-factory + dao-factory data-access-object data-bus data-locality @@ -252,6 +252,7 @@ backpressure actor-model rate-limiting-pattern + onion-architecture @@ -346,6 +347,11 @@ mockito-core ${mockito.version} + + org.mockito + mockito-junit-jupiter + ${mockito.version} + org.mongodb bson From 49fa99df236ddf43ca8289f9855151cdf387279a Mon Sep 17 00:00:00 2001 From: HattoriHenzo Date: Mon, 29 Jun 2026 15:58:00 -0400 Subject: [PATCH 2/2] Execute maven command: mvn spotless:apply --- .../onion/application/dto/PersonResponse.java | 18 +- .../application/dto/SavePersonCommand.java | 18 +- .../application/usecase/GetPersonUseCase.java | 70 +- .../usecase/SavePersonUseCase.java | 52 +- .../usecase/GetPersonUseCaseTest.java | 279 ++++---- .../usecase/SavePersonUseCaseTest.java | 406 ++++++------ .../domain/exception/DomainException.java | 6 +- .../iluwatar/onion/domain/model/Category.java | 29 +- .../iluwatar/onion/domain/model/Person.java | 137 ++-- .../domain/repository/PersonRepository.java | 18 +- .../onion/domain/model/CategoryTest.java | 71 +- .../onion/domain/model/PersonTest.java | 322 ++++----- .../onion/infrastructure/Application.java | 6 +- .../config/ApplicationConfig.java | 16 +- .../persistence/JpaCategoryEntity.java | 30 +- .../persistence/JpaPersonEntity.java | 88 +-- .../persistence/PersonRepositoryAdapter.java | 112 ++-- .../SpringDataPersonRepository.java | 8 +- .../infrastructure/web/PersonController.java | 51 +- .../PersonRepositoryAdapterTest.java | 574 ++++++++-------- .../web/PersonControllerTest.java | 616 +++++++++--------- 21 files changed, 1416 insertions(+), 1511 deletions(-) diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java index b26d68b84459..1586f30e48a1 100644 --- a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/PersonResponse.java @@ -26,12 +26,12 @@ */ package com.iluwatar.onion.application.dto; -public record PersonResponse(Long id, - String firstName, - String lastName, - int age, - String phoneNumber, - String email, - Long categoryId, - String categoryType) { -} +public record PersonResponse( + Long id, + String firstName, + String lastName, + int age, + String phoneNumber, + String email, + Long categoryId, + String categoryType) {} diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java index cafec064747f..649db88a4ab5 100644 --- a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/dto/SavePersonCommand.java @@ -26,12 +26,12 @@ */ package com.iluwatar.onion.application.dto; -public record SavePersonCommand(String firstName, - String lastName, - int age, - String phoneNumber, - String email, - String address, - Long categoryId, - String categoryType) { -} +public record SavePersonCommand( + String firstName, + String lastName, + int age, + String phoneNumber, + String email, + String address, + Long categoryId, + String categoryType) {} diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java index 99e1d25533ec..abae6a32196a 100644 --- a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/GetPersonUseCase.java @@ -28,48 +28,48 @@ import com.iluwatar.onion.application.dto.PersonResponse; import com.iluwatar.onion.domain.repository.PersonRepository; - import java.util.Collection; import java.util.List; public class GetPersonUseCase { - private final PersonRepository repository; + private final PersonRepository repository; - public GetPersonUseCase(PersonRepository repository) { - this.repository = repository; - } + public GetPersonUseCase(PersonRepository repository) { + this.repository = repository; + } - public PersonResponse execute(Long id) { - var person = repository.findById(id) - .orElseThrow(() -> new RuntimeException("Person not found with id: " + id)); + public PersonResponse execute(Long id) { + var person = + repository + .findById(id) + .orElseThrow(() -> new RuntimeException("Person not found with id: " + id)); - return new PersonResponse( - person.getId(), - person.getFirstName(), - person.getLastName(), - person.getAge(), - person.getPhoneNumber(), - person.getEmail(), - person.getCategory().getId(), - person.getCategory().getType() - ); - } + return new PersonResponse( + person.getId(), + person.getFirstName(), + person.getLastName(), + person.getAge(), + person.getPhoneNumber(), + person.getEmail(), + person.getCategory().getId(), + person.getCategory().getType()); + } - public List executeAll() { - return repository.findAll() - .stream() - .flatMap(Collection::stream) - .map(person -> new PersonResponse( - person.getId(), - person.getFirstName(), - person.getLastName(), - person.getAge(), - person.getPhoneNumber(), - person.getEmail(), - person.getCategory().getId(), - person.getCategory().getType() - )) - .toList(); - } + public List executeAll() { + return repository.findAll().stream() + .flatMap(Collection::stream) + .map( + person -> + new PersonResponse( + person.getId(), + person.getFirstName(), + person.getLastName(), + person.getAge(), + person.getPhoneNumber(), + person.getEmail(), + person.getCategory().getId(), + person.getCategory().getType())) + .toList(); + } } diff --git a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java index 2d72d378de0f..8d63e2612fab 100644 --- a/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java +++ b/onion-architecture/application/src/main/java/com/iluwatar/onion/application/usecase/SavePersonUseCase.java @@ -34,34 +34,34 @@ public class SavePersonUseCase { - private final PersonRepository repository; + private final PersonRepository repository; - public SavePersonUseCase(PersonRepository repository) { - this.repository = repository; - } + public SavePersonUseCase(PersonRepository repository) { + this.repository = repository; + } - public PersonResponse execute(SavePersonCommand command) { - var category = new Category(command.categoryId(), command.categoryType()); - var person = new Person( - null, - command.firstName(), - command.lastName(), - command.age(), - command.phoneNumber(), - command.email(), - category); + public PersonResponse execute(SavePersonCommand command) { + var category = new Category(command.categoryId(), command.categoryType()); + var person = + new Person( + null, + command.firstName(), + command.lastName(), + command.age(), + command.phoneNumber(), + command.email(), + category); - var savedPerson = repository.save(person); + var savedPerson = repository.save(person); - return new PersonResponse( - savedPerson.getId(), - savedPerson.getFirstName(), - savedPerson.getLastName(), - savedPerson.getAge(), - savedPerson.getPhoneNumber(), - savedPerson.getEmail(), - savedPerson.getCategory().getId(), - savedPerson.getCategory().getType() - ); - } + return new PersonResponse( + savedPerson.getId(), + savedPerson.getFirstName(), + savedPerson.getLastName(), + savedPerson.getAge(), + savedPerson.getPhoneNumber(), + savedPerson.getEmail(), + savedPerson.getCategory().getId(), + savedPerson.getCategory().getType()); + } } diff --git a/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java index e4c39d238bde..f9de119c2309 100644 --- a/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java +++ b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/GetPersonUseCaseTest.java @@ -26,10 +26,15 @@ */ package com.iluwatar.onion.application.usecase; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import com.iluwatar.onion.application.dto.PersonResponse; import com.iluwatar.onion.domain.model.Category; import com.iluwatar.onion.domain.model.Person; import com.iluwatar.onion.domain.repository.PersonRepository; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -38,161 +43,151 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class GetPersonUseCaseTest { - @Mock - private PersonRepository personRepository; + @Mock private PersonRepository personRepository; + + private GetPersonUseCase getPersonUseCase; + + @BeforeEach + void setUp() { + getPersonUseCase = new GetPersonUseCase(personRepository); + } + + @Nested + @DisplayName("Execute by ID - Happy Path") + class ExecuteByIdHappyPath { + + @Test + @DisplayName("Should return PersonResponse when person exists") + void shouldReturnPersonResponseWhenPersonExists() { + // Arrange + var person = + new Person( + 1L, + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + new Category(1L, "Professional")); + + when(personRepository.findById(1L)).thenReturn(Optional.of(person)); + + // Act + var response = getPersonUseCase.execute(1L); + + // Assert + assertNotNull(response); + assertEquals(1L, response.id()); + assertEquals("John", response.firstName()); + assertEquals("Doe", response.lastName()); + assertEquals(25, response.age()); + assertEquals("+1234567890", response.phoneNumber()); + assertEquals("john.doe@example.com", response.email()); + assertEquals(1L, response.categoryId()); + assertEquals("Professional", response.categoryType()); + + verify(personRepository, times(1)).findById(1L); + } + } - private GetPersonUseCase getPersonUseCase; + @Nested + @DisplayName("Execute by ID - Error Cases") + class ExecuteByIdErrorCases { - @BeforeEach - void setUp() { - getPersonUseCase = new GetPersonUseCase(personRepository); - } + @Test + @DisplayName("Should throw RuntimeException when person not found") + void shouldThrowExceptionWhenPersonNotFound() { + // Arrange + when(personRepository.findById(999L)).thenReturn(Optional.empty()); - @Nested - @DisplayName("Execute by ID - Happy Path") - class ExecuteByIdHappyPath { - - @Test - @DisplayName("Should return PersonResponse when person exists") - void shouldReturnPersonResponseWhenPersonExists() { - // Arrange - var person = new Person( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - new Category(1L, "Professional") - ); - - when(personRepository.findById(1L)).thenReturn(Optional.of(person)); - - // Act - var response = getPersonUseCase.execute(1L); - - // Assert - assertNotNull(response); - assertEquals(1L, response.id()); - assertEquals("John", response.firstName()); - assertEquals("Doe", response.lastName()); - assertEquals(25, response.age()); - assertEquals("+1234567890", response.phoneNumber()); - assertEquals("john.doe@example.com", response.email()); - assertEquals(1L, response.categoryId()); - assertEquals("Professional", response.categoryType()); - - verify(personRepository, times(1)).findById(1L); - } - } + // Act & Assert + var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(999L)); + assertTrue(exception.getMessage().contains("Person not found with id: 999")); - @Nested - @DisplayName("Execute by ID - Error Cases") - class ExecuteByIdErrorCases { + verify(personRepository, times(1)).findById(999L); + } - @Test - @DisplayName("Should throw RuntimeException when person not found") - void shouldThrowExceptionWhenPersonNotFound() { - // Arrange - when(personRepository.findById(999L)).thenReturn(Optional.empty()); + @Test + @DisplayName("Should propagate exception when repository throws exception") + void shouldPropagateExceptionWhenRepositoryFails() { + // Arrange + when(personRepository.findById(anyLong())).thenThrow(new RuntimeException("Database error")); - // Act & Assert - var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(999L)); - assertTrue(exception.getMessage().contains("Person not found with id: 999")); + // Act & Assert + var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(1L)); + assertTrue(exception.getMessage().contains("Database error")); - verify(personRepository, times(1)).findById(999L); - } + verify(personRepository, times(1)).findById(1L); + } + } + + @Nested + @DisplayName("Execute All - Happy Path") + class ExecuteAllHappyPath { + + @Test + @DisplayName("Should return list of PersonResponses") + void shouldReturnListOfPersonResponses() { + // Arrange + var person1 = + new Person( + 1L, + "John", + "Doe", + 30, + "+9876543255", + "john.doe@example.com", + new Category(2L, "Personal")); + + var person2 = + new Person( + 2L, + "Jane", + "Smith", + 25, + "+9876543210", + "jane.smith@example.com", + new Category(2L, "Personal")); + + when(personRepository.findAll()).thenReturn(Optional.of(List.of(person1, person2))); + + // Act + var responses = getPersonUseCase.executeAll(); + + // Assert + assertNotNull(responses); + assertEquals(2, responses.size()); + + PersonResponse response1 = responses.get(0); + assertEquals(1L, response1.id()); + assertEquals("John", response1.firstName()); + assertEquals("Doe", response1.lastName()); + + PersonResponse response2 = responses.get(1); + assertEquals(2L, response2.id()); + assertEquals("Jane", response2.firstName()); + assertEquals("Smith", response2.lastName()); + + verify(personRepository, times(1)).findAll(); + } - @Test - @DisplayName("Should propagate exception when repository throws exception") - void shouldPropagateExceptionWhenRepositoryFails() { - // Arrange - when(personRepository.findById(anyLong())) - .thenThrow(new RuntimeException("Database error")); + @Test + @DisplayName("Should return empty list when no persons found") + void shouldReturnEmptyListWhenNoPersonsFound() { + // Arrange + when(personRepository.findAll()).thenReturn(Optional.of(List.of())); - // Act & Assert - var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(1L)); - assertTrue(exception.getMessage().contains("Database error")); + // Act + List responses = getPersonUseCase.executeAll(); - verify(personRepository, times(1)).findById(1L); - } - } + // Assert + assertNotNull(responses); + assertTrue(responses.isEmpty()); - @Nested - @DisplayName("Execute All - Happy Path") - class ExecuteAllHappyPath { - - @Test - @DisplayName("Should return list of PersonResponses") - void shouldReturnListOfPersonResponses() { - // Arrange - var person1 = new Person( - 1L, - "John", - "Doe", - 30, - "+9876543255", - "john.doe@example.com", - new Category(2L, "Personal") - ); - - var person2 = new Person( - 2L, - "Jane", - "Smith", - 25, - "+9876543210", - "jane.smith@example.com", - new Category(2L, "Personal") - ); - - when(personRepository.findAll()) - .thenReturn(Optional.of(List.of(person1, person2))); - - // Act - var responses = getPersonUseCase.executeAll(); - - // Assert - assertNotNull(responses); - assertEquals(2, responses.size()); - - PersonResponse response1 = responses.get(0); - assertEquals(1L, response1.id()); - assertEquals("John", response1.firstName()); - assertEquals("Doe", response1.lastName()); - - PersonResponse response2 = responses.get(1); - assertEquals(2L, response2.id()); - assertEquals("Jane", response2.firstName()); - assertEquals("Smith", response2.lastName()); - - verify(personRepository, times(1)).findAll(); - } - - @Test - @DisplayName("Should return empty list when no persons found") - void shouldReturnEmptyListWhenNoPersonsFound() { - // Arrange - when(personRepository.findAll()).thenReturn(Optional.of(List.of())); - - // Act - List responses = getPersonUseCase.executeAll(); - - // Assert - assertNotNull(responses); - assertTrue(responses.isEmpty()); - - verify(personRepository, times(1)).findAll(); - } + verify(personRepository, times(1)).findAll(); } + } } - diff --git a/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java index 751f1445d73f..434147ae6495 100644 --- a/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java +++ b/onion-architecture/application/src/test/java/com/iluwatar/onion/application/usecase/SavePersonUseCaseTest.java @@ -26,6 +26,10 @@ */ package com.iluwatar.onion.application.usecase; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.iluwatar.onion.application.dto.SavePersonCommand; import com.iluwatar.onion.domain.exception.DomainException; import com.iluwatar.onion.domain.model.Category; @@ -41,221 +45,209 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class SavePersonUseCaseTest { - @Mock - private PersonRepository personRepository; - - @Captor - private ArgumentCaptor personCaptor; - - private SavePersonUseCase savePersonUseCase; - - @BeforeEach - void setUp() { - savePersonUseCase = new SavePersonUseCase(personRepository); + @Mock private PersonRepository personRepository; + + @Captor private ArgumentCaptor personCaptor; + + private SavePersonUseCase savePersonUseCase; + + @BeforeEach + void setUp() { + savePersonUseCase = new SavePersonUseCase(personRepository); + } + + @Nested + @DisplayName("Execute - Happy Path") + class ExecuteHappyPath { + + @Test + @DisplayName("Should save person and return PersonResponse") + void shouldSavePersonAndReturnResponse() { + // Arrange + var command = + new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + "123 Main St", + 1L, + "Professional"); + + var category = new Category(1L, "Professional"); + var savedPerson = + new Person( + 100L, // ID generated by database + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + category); + + when(personRepository.save(any(Person.class))).thenReturn(savedPerson); + + // Act + var response = savePersonUseCase.execute(command); + + // Assert + assertNotNull(response); + assertEquals(100L, response.id()); + assertEquals("John", response.firstName()); + assertEquals("Doe", response.lastName()); + assertEquals(25, response.age()); + assertEquals("+1234567890", response.phoneNumber()); + assertEquals("john.doe@example.com", response.email()); + assertEquals(1L, response.categoryId()); + assertEquals("Professional", response.categoryType()); + + // Verify repository interaction + verify(personRepository, times(1)).save(any(Person.class)); } - @Nested - @DisplayName("Execute - Happy Path") - class ExecuteHappyPath { - - @Test - @DisplayName("Should save person and return PersonResponse") - void shouldSavePersonAndReturnResponse() { - // Arrange - var command = new SavePersonCommand( - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - "123 Main St", - 1L, - "Professional" - ); - - var category = new Category(1L, "Professional"); - var savedPerson = new Person( - 100L, // ID generated by database - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - category - ); - - when(personRepository.save(any(Person.class))).thenReturn(savedPerson); - - // Act - var response = savePersonUseCase.execute(command); - - // Assert - assertNotNull(response); - assertEquals(100L, response.id()); - assertEquals("John", response.firstName()); - assertEquals("Doe", response.lastName()); - assertEquals(25, response.age()); - assertEquals("+1234567890", response.phoneNumber()); - assertEquals("john.doe@example.com", response.email()); - assertEquals(1L, response.categoryId()); - assertEquals("Professional", response.categoryType()); - - // Verify repository interaction - verify(personRepository, times(1)).save(any(Person.class)); - } - - @Test - @DisplayName("Should pass correct Person object to repository") - void shouldPassCorrectPersonToRepository() { - // Arrange - var command = new SavePersonCommand( - "Jane", - "Smith", - 30, - "+9876543210", - "jane.smith@example.com", - "456 Oak Ave", - 2L, - "Personal" - ); - - var category = new Category(2L, "Personal"); - var savedPerson = new Person( - 200L, - "Jane", - "Smith", - 30, - "+9876543210", - "jane.smith@example.com", - category - ); - - when(personRepository.save(any(Person.class))).thenReturn(savedPerson); - - // Act - savePersonUseCase.execute(command); - - // Assert - Verify what was passed to repository - verify(personRepository).save(personCaptor.capture()); - var capturedPerson = personCaptor.getValue(); - - assertNull(capturedPerson.getId()); // New person should have null ID - assertEquals("Jane", capturedPerson.getFirstName()); - assertEquals("Smith", capturedPerson.getLastName()); - assertEquals(30, capturedPerson.getAge()); - assertEquals("+9876543210", capturedPerson.getPhoneNumber()); - assertEquals("jane.smith@example.com", capturedPerson.getEmail()); - assertEquals("Personal", capturedPerson.getCategory().getType()); - } + @Test + @DisplayName("Should pass correct Person object to repository") + void shouldPassCorrectPersonToRepository() { + // Arrange + var command = + new SavePersonCommand( + "Jane", + "Smith", + 30, + "+9876543210", + "jane.smith@example.com", + "456 Oak Ave", + 2L, + "Personal"); + + var category = new Category(2L, "Personal"); + var savedPerson = + new Person(200L, "Jane", "Smith", 30, "+9876543210", "jane.smith@example.com", category); + + when(personRepository.save(any(Person.class))).thenReturn(savedPerson); + + // Act + savePersonUseCase.execute(command); + + // Assert - Verify what was passed to repository + verify(personRepository).save(personCaptor.capture()); + var capturedPerson = personCaptor.getValue(); + + assertNull(capturedPerson.getId()); // New person should have null ID + assertEquals("Jane", capturedPerson.getFirstName()); + assertEquals("Smith", capturedPerson.getLastName()); + assertEquals(30, capturedPerson.getAge()); + assertEquals("+9876543210", capturedPerson.getPhoneNumber()); + assertEquals("jane.smith@example.com", capturedPerson.getEmail()); + assertEquals("Personal", capturedPerson.getCategory().getType()); + } + } + + @Nested + @DisplayName("Execute - Validation Failures") + class ExecuteValidationFailures { + + @Test + @DisplayName("Should throw DomainException when age is less than 18") + void shouldThrowExceptionWhenAgeIsInvalid() { + // Arrange + var command = + new SavePersonCommand( + "Young", + "Person", + 17, // Invalid age + "+1234567890", + "young@example.com", + "123 Main St", + 1L, + "Student"); + + // Act & Assert + var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Age cannot be less than 18")); + + // Verify repository was never called + verify(personRepository, never()).save(any(Person.class)); } - @Nested - @DisplayName("Execute - Validation Failures") - class ExecuteValidationFailures { - - @Test - @DisplayName("Should throw DomainException when age is less than 18") - void shouldThrowExceptionWhenAgeIsInvalid() { - // Arrange - var command = new SavePersonCommand( - "Young", - "Person", - 17, // Invalid age - "+1234567890", - "young@example.com", - "123 Main St", - 1L, - "Student" - ); - - // Act & Assert - var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); - assertTrue(exception.getMessage().contains("Age cannot be less than 18")); - - // Verify repository was never called - verify(personRepository, never()).save(any(Person.class)); - } - - @Test - @DisplayName("Should throw DomainException when email is empty") - void shouldThrowExceptionWhenEmailIsEmpty() { - // Arrange - var command = new SavePersonCommand( - "John", - "Doe", - 25, - "+1234567890", - "", // Empty email - "123 Main St", - 1L, - "Professional" - ); - - // Act & Assert - var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); - assertTrue(exception.getMessage().contains("Email cannot be null or empty")); - - verify(personRepository, never()).save(any(Person.class)); - } - - @Test - @DisplayName("Should throw DomainException when category type is invalid") - void shouldThrowExceptionWhenCategoryTypeIsInvalid() { - // Arrange - var command = new SavePersonCommand( - "John", - "Doe", - 25, - "+1234567890", - "john@example.com", - "123 Main St", - 1L, - "" // Empty category type - ); - - // Act & Assert - var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); - assertTrue(exception.getMessage().contains("Type is null or empty")); - - verify(personRepository, never()).save(any(Person.class)); - } + @Test + @DisplayName("Should throw DomainException when email is empty") + void shouldThrowExceptionWhenEmailIsEmpty() { + // Arrange + var command = + new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "", // Empty email + "123 Main St", + 1L, + "Professional"); + + // Act & Assert + var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Email cannot be null or empty")); + + verify(personRepository, never()).save(any(Person.class)); } - @Nested - @DisplayName("Execute - Repository Exceptions") - class ExecuteRepositoryExceptions { - - @Test - @DisplayName("Should propagate exception when repository fails") - void shouldPropagateExceptionWhenRepositoryFails() { - // Arrange - var command = new SavePersonCommand( - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - "123 Main St", - 1L, - "Professional" - ); - - when(personRepository.save(any(Person.class))) - .thenThrow(new RuntimeException("Database connection failed")); - - // Act & Assert - var exception = assertThrows(RuntimeException.class, () -> savePersonUseCase.execute(command)); - assertTrue(exception.getMessage().contains("Database connection failed")); - - verify(personRepository, times(1)).save(any(Person.class)); - } + @Test + @DisplayName("Should throw DomainException when category type is invalid") + void shouldThrowExceptionWhenCategoryTypeIsInvalid() { + // Arrange + var command = + new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john@example.com", + "123 Main St", + 1L, + "" // Empty category type + ); + + // Act & Assert + var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Type is null or empty")); + + verify(personRepository, never()).save(any(Person.class)); + } + } + + @Nested + @DisplayName("Execute - Repository Exceptions") + class ExecuteRepositoryExceptions { + + @Test + @DisplayName("Should propagate exception when repository fails") + void shouldPropagateExceptionWhenRepositoryFails() { + // Arrange + var command = + new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + "123 Main St", + 1L, + "Professional"); + + when(personRepository.save(any(Person.class))) + .thenThrow(new RuntimeException("Database connection failed")); + + // Act & Assert + var exception = + assertThrows(RuntimeException.class, () -> savePersonUseCase.execute(command)); + assertTrue(exception.getMessage().contains("Database connection failed")); + + verify(personRepository, times(1)).save(any(Person.class)); } + } } - diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java index af7da5647abc..5e0831a57c02 100644 --- a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/exception/DomainException.java @@ -28,7 +28,7 @@ public class DomainException extends RuntimeException { - public DomainException(String message) { - super(message); - } + public DomainException(String message) { + super(message); + } } diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java index 60adc5b5e1f6..8380f4e849ee 100644 --- a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Category.java @@ -30,21 +30,22 @@ public class Category { - private final Long id; - private final String type; - public Category(Long id, String type) { - if (type == null || type.isEmpty()) { - throw new DomainException("Type is null or empty. Category type is required."); - } - this.id = id; - this.type = type; - } + private final Long id; + private final String type; - public Long getId() { - return id; + public Category(Long id, String type) { + if (type == null || type.isEmpty()) { + throw new DomainException("Type is null or empty. Category type is required."); } + this.id = id; + this.type = type; + } - public String getType() { - return type; - } + public Long getId() { + return id; + } + + public String getType() { + return type; + } } diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java index f0608c1f9716..55eced0be29a 100644 --- a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/model/Person.java @@ -30,85 +30,92 @@ public class Person { - private final Long id; - private final String firstName; - private final String lastName; - private final int age; - private final String phoneNumber; - private final String email; - private final Category category; - - public Person(Long id, String firstName, String lastName, int age, String phoneNumber, String email, Category category) { - validateNames(firstName, lastName); - validateAge(age); - validatePhone(phoneNumber); - validateEmail(email); - validateCategory(category); - - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - this.age = age; - this.phoneNumber = phoneNumber; - this.email = email; - this.category = category; + private final Long id; + private final String firstName; + private final String lastName; + private final int age; + private final String phoneNumber; + private final String email; + private final Category category; + + public Person( + Long id, + String firstName, + String lastName, + int age, + String phoneNumber, + String email, + Category category) { + validateNames(firstName, lastName); + validateAge(age); + validatePhone(phoneNumber); + validateEmail(email); + validateCategory(category); + + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.phoneNumber = phoneNumber; + this.email = email; + this.category = category; + } + + private void validateNames(String firstName, String lastName) { + if (firstName == null || lastName == null) { + throw new DomainException("First name and last name cannot be null."); } + } - private void validateNames(String firstName, String lastName) { - if (firstName == null || lastName == null) { - throw new DomainException("First name and last name cannot be null."); - } + private void validateAge(int age) { + if (age < 18) { + throw new DomainException("Age cannot be less than 18."); } + } - private void validateAge(int age) { - if (age < 18) { - throw new DomainException("Age cannot be less than 18."); - } + private void validatePhone(String phone) { + if (phone == null || phone.isEmpty()) { + throw new DomainException("Phone number cannot be null or empty."); } + } - private void validatePhone(String phone) { - if (phone == null || phone.isEmpty()) { - throw new DomainException("Phone number cannot be null or empty."); - } + private void validateEmail(String email) { + if (email == null || email.isEmpty()) { + throw new DomainException("Email cannot be null or empty."); } + } - private void validateEmail(String email) { - if (email == null || email.isEmpty()) { - throw new DomainException("Email cannot be null or empty."); - } + private void validateCategory(Category category) { + if (category == null || category.getType().isEmpty()) { + throw new DomainException("Category cannot be null or empty."); } + } - private void validateCategory(Category category) { - if (category == null || category.getType().isEmpty()) { - throw new DomainException("Category cannot be null or empty."); - } - } + public Long getId() { + return id; + } - public Long getId() { - return id; - } + public String getFirstName() { + return firstName; + } - public String getFirstName() { - return firstName; - } + public String getLastName() { + return lastName; + } - public String getLastName() { - return lastName; - } + public int getAge() { + return age; + } - public int getAge() { - return age; - } + public String getPhoneNumber() { + return phoneNumber; + } - public String getPhoneNumber() { - return phoneNumber; - } - - public String getEmail() { - return email; - } + public String getEmail() { + return email; + } - public Category getCategory() { - return category; - } + public Category getCategory() { + return category; + } } diff --git a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java index e755f4a76438..c14aa24a8ef6 100644 --- a/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java +++ b/onion-architecture/domain/src/main/java/com/iluwatar/onion/domain/repository/PersonRepository.java @@ -27,15 +27,19 @@ package com.iluwatar.onion.domain.repository; import com.iluwatar.onion.domain.model.Person; - import java.util.List; import java.util.Optional; public interface PersonRepository { - Optional findById(Long id); - Optional findByFirstName(String firstName); - Optional findByLastName(String lastName); - Optional> findAll(); - Person save(Person person); - boolean deleteById(Long id); + Optional findById(Long id); + + Optional findByFirstName(String firstName); + + Optional findByLastName(String lastName); + + Optional> findAll(); + + Person save(Person person); + + boolean deleteById(Long id); } diff --git a/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java index 74622a5b6bb3..82e88285c35b 100644 --- a/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java +++ b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/CategoryTest.java @@ -26,51 +26,50 @@ */ package com.iluwatar.onion.domain.model; +import static org.junit.jupiter.api.Assertions.*; + import com.iluwatar.onion.domain.exception.DomainException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - class CategoryTest { - @Test - @DisplayName("Should create category with valid data") - void shouldCreateCategoryWithValidData() { - // Arrange & Act - var category = new Category(1L, "Professional"); + @Test + @DisplayName("Should create category with valid data") + void shouldCreateCategoryWithValidData() { + // Arrange & Act + var category = new Category(1L, "Professional"); - // Assert - assertNotNull(category); - assertEquals(1L, category.getId()); - assertEquals("Professional", category.getType()); - } + // Assert + assertNotNull(category); + assertEquals(1L, category.getId()); + assertEquals("Professional", category.getType()); + } - @Test - @DisplayName("Should create category with null id") - void shouldCreateCategoryWithNullId() { - // Arrange & Act - var category = new Category(null, "Personal"); + @Test + @DisplayName("Should create category with null id") + void shouldCreateCategoryWithNullId() { + // Arrange & Act + var category = new Category(null, "Personal"); - // Assert - assertNull(category.getId()); - assertEquals("Personal", category.getType()); - } + // Assert + assertNull(category.getId()); + assertEquals("Personal", category.getType()); + } - @Test - @DisplayName("Should throw exception when type is null") - void shouldThrowExceptionWhenTypeIsNull() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Category(1L, null)); - assertTrue(exception.getMessage().contains("Type is null or empty")); - } + @Test + @DisplayName("Should throw exception when type is null") + void shouldThrowExceptionWhenTypeIsNull() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Category(1L, null)); + assertTrue(exception.getMessage().contains("Type is null or empty")); + } - @Test - @DisplayName("Should throw exception when type is empty") - void shouldThrowExceptionWhenTypeIsEmpty() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Category(1L, "")); - assertTrue(exception.getMessage().contains("Type is null or empty")); - } + @Test + @DisplayName("Should throw exception when type is empty") + void shouldThrowExceptionWhenTypeIsEmpty() { + // Act & Assert + var exception = assertThrows(DomainException.class, () -> new Category(1L, "")); + assertTrue(exception.getMessage().contains("Type is null or empty")); + } } - diff --git a/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java index b4baa03314e1..8f5bef0da1a6 100644 --- a/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java +++ b/onion-architecture/domain/src/test/java/com/iluwatar/onion/domain/model/PersonTest.java @@ -26,196 +26,154 @@ */ package com.iluwatar.onion.domain.model; +import static org.junit.jupiter.api.Assertions.*; + import com.iluwatar.onion.domain.exception.DomainException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - class PersonTest { - private final Category validCategory = new Category(1L, "Professional"); - - @Nested - @DisplayName("Person Creation - Valid Cases") - class ValidPersonCreation { - - @Test - @DisplayName("Should create person with valid data") - void shouldCreatePersonWithValidData() { - // Arrange & Act - var person = new Person( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - validCategory - ); - - // Assert - assertNotNull(person); - assertEquals(1L, person.getId()); - assertEquals("John", person.getFirstName()); - assertEquals("Doe", person.getLastName()); - assertEquals(25, person.getAge()); - assertEquals("+1234567890", person.getPhoneNumber()); - assertEquals("john.doe@example.com", person.getEmail()); - assertEquals(validCategory, person.getCategory()); - } - - @Test - @DisplayName("Should create person with minimum valid age (18)") - void shouldCreatePersonWithMinimumValidAge() { - // Arrange & Act - var person = new Person( - null, - "Jane", - "Smith", - 18, // minimum valid age - "+9876543210", - "jane@example.com", - validCategory - ); - - // Assert - assertEquals(18, person.getAge()); - } + private final Category validCategory = new Category(1L, "Professional"); + + @Nested + @DisplayName("Person Creation - Valid Cases") + class ValidPersonCreation { + + @Test + @DisplayName("Should create person with valid data") + void shouldCreatePersonWithValidData() { + // Arrange & Act + var person = + new Person(1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", validCategory); + + // Assert + assertNotNull(person); + assertEquals(1L, person.getId()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(25, person.getAge()); + assertEquals("+1234567890", person.getPhoneNumber()); + assertEquals("john.doe@example.com", person.getEmail()); + assertEquals(validCategory, person.getCategory()); } - @Nested - @DisplayName("Person Creation - Invalid Cases") - class InvalidPersonCreation { - - @Test - @DisplayName("Should throw exception when first name is null") - void shouldThrowExceptionWhenFirstNameIsNull() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - null, - "Doe", - 25, - "+1234567890", - "john@example.com", - validCategory - )); - assertTrue(exception.getMessage().contains("First name and last name cannot be null")); - } - - @Test - @DisplayName("Should throw exception when last name is null") - void shouldThrowExceptionWhenLastNameIsNull() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - null, - 25, - "+1234567890", - "john@example.com", - validCategory - )); - assertTrue(exception.getMessage().contains("First name and last name cannot be null")); - } - - @Test - @DisplayName("Should throw exception when age is less than 18") - void shouldThrowExceptionWhenAgeIsLessThan18() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - "Doe", - 17, - "+1234567890", - "john@example.com", - validCategory - )); - assertTrue(exception.getMessage().contains("Age cannot be less than 18")); - } - - @Test - @DisplayName("Should throw exception when phone number is null") - void shouldThrowExceptionWhenPhoneIsNull() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - "Doe", - 25, - null, - "john@example.com", - validCategory - )); - assertTrue(exception.getMessage().contains("Phone number cannot be null or empty")); - } - - @Test - @DisplayName("Should throw exception when phone number is empty") - void shouldThrowExceptionWhenPhoneIsEmpty() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - "Doe", - 25, - "", - "john@example.com", - validCategory - )); - assertTrue(exception.getMessage().contains("Phone number cannot be null or empty")); - } - - @Test - @DisplayName("Should throw exception when email is null") - void shouldThrowExceptionWhenEmailIsNull() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - "Doe", - 25, - "+1234567890", - null, - validCategory - )); - assertTrue(exception.getMessage().contains("Email cannot be null or empty")); - } - - @Test - @DisplayName("Should throw exception when email is empty") - void shouldThrowExceptionWhenEmailIsEmpty() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "", - validCategory - )); - assertTrue(exception.getMessage().contains("Email cannot be null or empty")); - } - - @Test - @DisplayName("Should throw exception when category is null") - void shouldThrowExceptionWhenCategoryIsNull() { - // Act & Assert - var exception = assertThrows(DomainException.class, () -> new Person( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "john@example.com", - null - )); - assertTrue(exception.getMessage().contains("Category cannot be null or empty")); - } + @Test + @DisplayName("Should create person with minimum valid age (18)") + void shouldCreatePersonWithMinimumValidAge() { + // Arrange & Act + var person = + new Person( + null, + "Jane", + "Smith", + 18, // minimum valid age + "+9876543210", + "jane@example.com", + validCategory); + + // Assert + assertEquals(18, person.getAge()); + } + } + + @Nested + @DisplayName("Person Creation - Invalid Cases") + class InvalidPersonCreation { + + @Test + @DisplayName("Should throw exception when first name is null") + void shouldThrowExceptionWhenFirstNameIsNull() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> + new Person( + 1L, null, "Doe", 25, "+1234567890", "john@example.com", validCategory)); + assertTrue(exception.getMessage().contains("First name and last name cannot be null")); + } + + @Test + @DisplayName("Should throw exception when last name is null") + void shouldThrowExceptionWhenLastNameIsNull() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> + new Person( + 1L, "John", null, 25, "+1234567890", "john@example.com", validCategory)); + assertTrue(exception.getMessage().contains("First name and last name cannot be null")); + } + + @Test + @DisplayName("Should throw exception when age is less than 18") + void shouldThrowExceptionWhenAgeIsLessThan18() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> + new Person( + 1L, "John", "Doe", 17, "+1234567890", "john@example.com", validCategory)); + assertTrue(exception.getMessage().contains("Age cannot be less than 18")); } -} + @Test + @DisplayName("Should throw exception when phone number is null") + void shouldThrowExceptionWhenPhoneIsNull() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> new Person(1L, "John", "Doe", 25, null, "john@example.com", validCategory)); + assertTrue(exception.getMessage().contains("Phone number cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when phone number is empty") + void shouldThrowExceptionWhenPhoneIsEmpty() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> new Person(1L, "John", "Doe", 25, "", "john@example.com", validCategory)); + assertTrue(exception.getMessage().contains("Phone number cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when email is null") + void shouldThrowExceptionWhenEmailIsNull() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> new Person(1L, "John", "Doe", 25, "+1234567890", null, validCategory)); + assertTrue(exception.getMessage().contains("Email cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when email is empty") + void shouldThrowExceptionWhenEmailIsEmpty() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> new Person(1L, "John", "Doe", 25, "+1234567890", "", validCategory)); + assertTrue(exception.getMessage().contains("Email cannot be null or empty")); + } + + @Test + @DisplayName("Should throw exception when category is null") + void shouldThrowExceptionWhenCategoryIsNull() { + // Act & Assert + var exception = + assertThrows( + DomainException.class, + () -> new Person(1L, "John", "Doe", 25, "+1234567890", "john@example.com", null)); + assertTrue(exception.getMessage().contains("Category cannot be null or empty")); + } + } +} diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java index 87621179405a..c86c95740599 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/Application.java @@ -32,7 +32,7 @@ @SpringBootApplication public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } } diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java index 0d971f5c2244..7794c91e3bcf 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/config/ApplicationConfig.java @@ -35,13 +35,13 @@ @Configuration public class ApplicationConfig { - @Bean - public SavePersonUseCase savePersonUseCase(PersonRepository repository) { - return new SavePersonUseCase(repository); - } + @Bean + public SavePersonUseCase savePersonUseCase(PersonRepository repository) { + return new SavePersonUseCase(repository); + } - @Bean - public GetPersonUseCase getPersonUseCase(PersonRepository repository) { - return new GetPersonUseCase(repository); - } + @Bean + public GetPersonUseCase getPersonUseCase(PersonRepository repository) { + return new GetPersonUseCase(repository); + } } diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java index 812496c56328..aafd242992be 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaCategoryEntity.java @@ -32,24 +32,24 @@ @Table(name = "category") public class JpaCategoryEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - private String type; + private String type; - public JpaCategoryEntity() {} + public JpaCategoryEntity() {} - public JpaCategoryEntity(Long id, String type) { - this.id = id; - this.type = type; - } + public JpaCategoryEntity(Long id, String type) { + this.id = id; + this.type = type; + } - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public String getType() { - return type; - } + public String getType() { + return type; + } } diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java index 6a03762555d0..bf71b5306c92 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/JpaPersonEntity.java @@ -32,56 +32,62 @@ @Table(name = "person") public class JpaPersonEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - private String firstName; - private String lastName; - private int age; - private String phoneNumber; - private String email; + private String firstName; + private String lastName; + private int age; + private String phoneNumber; + private String email; - @ManyToOne - private JpaCategoryEntity category; + @ManyToOne private JpaCategoryEntity category; - public JpaPersonEntity() {} + public JpaPersonEntity() {} - public JpaPersonEntity(Long id, String firstName, String lastName, int age, String phoneNumber, String email, JpaCategoryEntity category) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - this.age = age; - this.phoneNumber = phoneNumber; - this.email = email; - this.category = category; - } + public JpaPersonEntity( + Long id, + String firstName, + String lastName, + int age, + String phoneNumber, + String email, + JpaCategoryEntity category) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.phoneNumber = phoneNumber; + this.email = email; + this.category = category; + } - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public String getFirstName() { - return firstName; - } + public String getFirstName() { + return firstName; + } - public String getLastName() { - return lastName; - } + public String getLastName() { + return lastName; + } - public int getAge() { - return age; - } + public int getAge() { + return age; + } - public String getPhoneNumber() { - return phoneNumber; - } + public String getPhoneNumber() { + return phoneNumber; + } - public String getEmail() { - return email; - } + public String getEmail() { + return email; + } - public JpaCategoryEntity getCategory() { - return category; - } + public JpaCategoryEntity getCategory() { + return category; + } } diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java index 8ef14d7b5dc2..4c4aa267493a 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapter.java @@ -29,78 +29,74 @@ import com.iluwatar.onion.domain.model.Category; import com.iluwatar.onion.domain.model.Person; import com.iluwatar.onion.domain.repository.PersonRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import org.springframework.stereotype.Repository; @Repository public class PersonRepositoryAdapter implements PersonRepository { - private final SpringDataPersonRepository repository; + private final SpringDataPersonRepository repository; - public PersonRepositoryAdapter(SpringDataPersonRepository repository) { - this.repository = repository; - } + public PersonRepositoryAdapter(SpringDataPersonRepository repository) { + this.repository = repository; + } - @Override - public Optional findById(Long id) { - return repository.findById(id).map(this::mapToDomain); - } + @Override + public Optional findById(Long id) { + return repository.findById(id).map(this::mapToDomain); + } - @Override - public Optional findByFirstName(String firstName) { - return repository.findByFirstName(firstName).map(this::mapToDomain); - } + @Override + public Optional findByFirstName(String firstName) { + return repository.findByFirstName(firstName).map(this::mapToDomain); + } - @Override - public Optional findByLastName(String lastName) { - return repository.findByLastName(lastName).map(this::mapToDomain); - } + @Override + public Optional findByLastName(String lastName) { + return repository.findByLastName(lastName).map(this::mapToDomain); + } - @Override - public Optional> findAll() { - return repository.findAll() - .stream() - .map(this::mapToDomain) - .collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of)); - } + @Override + public Optional> findAll() { + return repository.findAll().stream() + .map(this::mapToDomain) + .collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of)); + } - @Override - public Person save(Person person) { - JpaPersonEntity entity = mapToEntity(person); - JpaPersonEntity savedEntity = repository.save(entity); - return mapToDomain(savedEntity); - } + @Override + public Person save(Person person) { + JpaPersonEntity entity = mapToEntity(person); + JpaPersonEntity savedEntity = repository.save(entity); + return mapToDomain(savedEntity); + } - @Override - public boolean deleteById(Long id) { - repository.deleteById(id); - return repository.findById(id).isEmpty(); - } + @Override + public boolean deleteById(Long id) { + repository.deleteById(id); + return repository.findById(id).isEmpty(); + } - private JpaPersonEntity mapToEntity(Person person) { - return new JpaPersonEntity( - person.getId(), - person.getFirstName(), - person.getLastName(), - person.getAge(), - person.getPhoneNumber(), - person.getEmail(), - new JpaCategoryEntity(person.getCategory().getId(), person.getCategory().getType()) - ); - } + private JpaPersonEntity mapToEntity(Person person) { + return new JpaPersonEntity( + person.getId(), + person.getFirstName(), + person.getLastName(), + person.getAge(), + person.getPhoneNumber(), + person.getEmail(), + new JpaCategoryEntity(person.getCategory().getId(), person.getCategory().getType())); + } - private Person mapToDomain(JpaPersonEntity entity) { - return new Person( - entity.getId(), - entity.getFirstName(), - entity.getLastName(), - entity.getAge(), - entity.getPhoneNumber(), - entity.getEmail(), - new Category(entity.getCategory().getId(), entity.getCategory().getType()) - ); - } + private Person mapToDomain(JpaPersonEntity entity) { + return new Person( + entity.getId(), + entity.getFirstName(), + entity.getLastName(), + entity.getAge(), + entity.getPhoneNumber(), + entity.getEmail(), + new Category(entity.getCategory().getId(), entity.getCategory().getType())); + } } diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java index f920a4090e69..35f86515c70b 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/persistence/SpringDataPersonRepository.java @@ -26,12 +26,12 @@ */ package com.iluwatar.onion.infrastructure.persistence; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface SpringDataPersonRepository extends JpaRepository { - Optional findByFirstName(String firstName); - Optional findByLastName(String lastName); + Optional findByFirstName(String firstName); + + Optional findByLastName(String lastName); } diff --git a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java index 74878dfe21e0..417e749f41a6 100644 --- a/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java +++ b/onion-architecture/infrastructure/src/main/java/com/iluwatar/onion/infrastructure/web/PersonController.java @@ -31,43 +31,42 @@ import com.iluwatar.onion.application.usecase.GetPersonUseCase; import com.iluwatar.onion.application.usecase.SavePersonUseCase; import com.iluwatar.onion.domain.exception.DomainException; +import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/api") public class PersonController { - private final SavePersonUseCase savePersonUseCase; - private final GetPersonUseCase getPersonUseCase; + private final SavePersonUseCase savePersonUseCase; + private final GetPersonUseCase getPersonUseCase; - public PersonController(SavePersonUseCase savePersonUseCase, GetPersonUseCase getPersonUseCase) { - this.savePersonUseCase = savePersonUseCase; - this.getPersonUseCase = getPersonUseCase; - } + public PersonController(SavePersonUseCase savePersonUseCase, GetPersonUseCase getPersonUseCase) { + this.savePersonUseCase = savePersonUseCase; + this.getPersonUseCase = getPersonUseCase; + } - @GetMapping("/persons/{id}") - public ResponseEntity getPerson(@PathVariable("id") Long id) { - var person = getPersonUseCase.execute(id); - return ResponseEntity.ok(person); - } + @GetMapping("/persons/{id}") + public ResponseEntity getPerson(@PathVariable("id") Long id) { + var person = getPersonUseCase.execute(id); + return ResponseEntity.ok(person); + } - @GetMapping("/persons") - public ResponseEntity> getAllPersons() { - var persons = getPersonUseCase.executeAll(); - return ResponseEntity.ok(persons); - } + @GetMapping("/persons") + public ResponseEntity> getAllPersons() { + var persons = getPersonUseCase.executeAll(); + return ResponseEntity.ok(persons); + } - @PostMapping("/persons") - public ResponseEntity savePerson(@RequestBody SavePersonCommand command) { - try { - var savedPerson = savePersonUseCase.execute(command); - return ResponseEntity.status(HttpStatus.OK).body(savedPerson); - } catch (DomainException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); - } + @PostMapping("/persons") + public ResponseEntity savePerson(@RequestBody SavePersonCommand command) { + try { + var savedPerson = savePersonUseCase.execute(command); + return ResponseEntity.status(HttpStatus.OK).body(savedPerson); + } catch (DomainException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } + } } diff --git a/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java index 571e8228b604..49fd2816f172 100644 --- a/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java +++ b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/persistence/PersonRepositoryAdapterTest.java @@ -26,8 +26,14 @@ */ package com.iluwatar.onion.infrastructure.persistence; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.iluwatar.onion.domain.model.Category; import com.iluwatar.onion.domain.model.Person; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -38,319 +44,289 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class PersonRepositoryAdapterTest { - @Mock - private SpringDataPersonRepository springDataRepository; - - @Captor - private ArgumentCaptor entityCaptor; - - private PersonRepositoryAdapter personRepositoryAdapter; - private JpaPersonEntity jpaPersonEntity; - - @BeforeEach - void setUp() { - personRepositoryAdapter = new PersonRepositoryAdapter(springDataRepository); - - // Setup test data - var jpaCategory = new JpaCategoryEntity(1L, "Professional"); - jpaPersonEntity = new JpaPersonEntity( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - jpaCategory - ); + @Mock private SpringDataPersonRepository springDataRepository; + + @Captor private ArgumentCaptor entityCaptor; + + private PersonRepositoryAdapter personRepositoryAdapter; + private JpaPersonEntity jpaPersonEntity; + + @BeforeEach + void setUp() { + personRepositoryAdapter = new PersonRepositoryAdapter(springDataRepository); + + // Setup test data + var jpaCategory = new JpaCategoryEntity(1L, "Professional"); + jpaPersonEntity = + new JpaPersonEntity( + 1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", jpaCategory); + } + + @Nested + @DisplayName("Find By ID") + class FindById { + + @Test + @DisplayName("Should return Person when entity exists") + void shouldReturnPersonWhenEntityExists() { + // Arrange + when(springDataRepository.findById(1L)).thenReturn(Optional.of(jpaPersonEntity)); + + // Act + var result = personRepositoryAdapter.findById(1L); + + // Assert + assertTrue(result.isPresent()); + Person person = result.get(); + assertEquals(1L, person.getId()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(25, person.getAge()); + assertEquals("+1234567890", person.getPhoneNumber()); + assertEquals("john.doe@example.com", person.getEmail()); + assertEquals(1L, person.getCategory().getId()); + assertEquals("Professional", person.getCategory().getType()); + + verify(springDataRepository, times(1)).findById(1L); } - @Nested - @DisplayName("Find By ID") - class FindById { - - @Test - @DisplayName("Should return Person when entity exists") - void shouldReturnPersonWhenEntityExists() { - // Arrange - when(springDataRepository.findById(1L)) - .thenReturn(Optional.of(jpaPersonEntity)); - - // Act - var result = personRepositoryAdapter.findById(1L); - - // Assert - assertTrue(result.isPresent()); - Person person = result.get(); - assertEquals(1L, person.getId()); - assertEquals("John", person.getFirstName()); - assertEquals("Doe", person.getLastName()); - assertEquals(25, person.getAge()); - assertEquals("+1234567890", person.getPhoneNumber()); - assertEquals("john.doe@example.com", person.getEmail()); - assertEquals(1L, person.getCategory().getId()); - assertEquals("Professional", person.getCategory().getType()); - - verify(springDataRepository, times(1)).findById(1L); - } - - @Test - @DisplayName("Should return empty Optional when entity not found") - void shouldReturnEmptyWhenEntityNotFound() { - // Arrange - when(springDataRepository.findById(999L)) - .thenReturn(Optional.empty()); - - // Act - var result = personRepositoryAdapter.findById(999L); - - // Assert - assertTrue(result.isEmpty()); - verify(springDataRepository, times(1)).findById(999L); - } + @Test + @DisplayName("Should return empty Optional when entity not found") + void shouldReturnEmptyWhenEntityNotFound() { + // Arrange + when(springDataRepository.findById(999L)).thenReturn(Optional.empty()); + + // Act + var result = personRepositoryAdapter.findById(999L); + + // Assert + assertTrue(result.isEmpty()); + verify(springDataRepository, times(1)).findById(999L); } + } + + @Nested + @DisplayName("Find By First Name") + class FindByFirstName { + + @Test + @DisplayName("Should return Person when entity with first name exists") + void shouldReturnPersonWhenEntityExists() { + // Arrange + when(springDataRepository.findByFirstName("John")).thenReturn(Optional.of(jpaPersonEntity)); - @Nested - @DisplayName("Find By First Name") - class FindByFirstName { - - @Test - @DisplayName("Should return Person when entity with first name exists") - void shouldReturnPersonWhenEntityExists() { - // Arrange - when(springDataRepository.findByFirstName("John")) - .thenReturn(Optional.of(jpaPersonEntity)); - - // Act - var result = personRepositoryAdapter.findByFirstName("John"); - - // Assert - assertTrue(result.isPresent()); - assertEquals("John", result.get().getFirstName()); - verify(springDataRepository, times(1)).findByFirstName("John"); - } - - @Test - @DisplayName("Should return empty Optional when no entity found") - void shouldReturnEmptyWhenNotFound() { - // Arrange - when(springDataRepository.findByFirstName("Unknown")) - .thenReturn(Optional.empty()); - - // Act - var result = personRepositoryAdapter.findByFirstName("Unknown"); - - // Assert - assertTrue(result.isEmpty()); - verify(springDataRepository, times(1)).findByFirstName("Unknown"); - } + // Act + var result = personRepositoryAdapter.findByFirstName("John"); + + // Assert + assertTrue(result.isPresent()); + assertEquals("John", result.get().getFirstName()); + verify(springDataRepository, times(1)).findByFirstName("John"); + } + + @Test + @DisplayName("Should return empty Optional when no entity found") + void shouldReturnEmptyWhenNotFound() { + // Arrange + when(springDataRepository.findByFirstName("Unknown")).thenReturn(Optional.empty()); + + // Act + var result = personRepositoryAdapter.findByFirstName("Unknown"); + + // Assert + assertTrue(result.isEmpty()); + verify(springDataRepository, times(1)).findByFirstName("Unknown"); } + } + + @Nested + @DisplayName("Find By Last Name") + class FindByLastName { + + @Test + @DisplayName("Should return Person when entity with last name exists") + void shouldReturnPersonWhenEntityExists() { + // Arrange + when(springDataRepository.findByLastName("Doe")).thenReturn(Optional.of(jpaPersonEntity)); + + // Act + var result = personRepositoryAdapter.findByLastName("Doe"); - @Nested - @DisplayName("Find By Last Name") - class FindByLastName { - - @Test - @DisplayName("Should return Person when entity with last name exists") - void shouldReturnPersonWhenEntityExists() { - // Arrange - when(springDataRepository.findByLastName("Doe")) - .thenReturn(Optional.of(jpaPersonEntity)); - - // Act - var result = personRepositoryAdapter.findByLastName("Doe"); - - // Assert - assertTrue(result.isPresent()); - assertEquals("Doe", result.get().getLastName()); - verify(springDataRepository, times(1)).findByLastName("Doe"); - } + // Assert + assertTrue(result.isPresent()); + assertEquals("Doe", result.get().getLastName()); + verify(springDataRepository, times(1)).findByLastName("Doe"); } + } + + @Nested + @DisplayName("Find All") + class FindAll { + + @Test + @DisplayName("Should return list of Persons when entities exist") + void shouldReturnListOfPersons() { + // Arrange + var category2 = new JpaCategoryEntity(2L, "Personal"); + var person2 = + new JpaPersonEntity( + 2L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", category2); + + when(springDataRepository.findAll()).thenReturn(List.of(jpaPersonEntity, person2)); + + // Act + var result = personRepositoryAdapter.findAll(); + + // Assert + assertTrue(result.isPresent()); + var persons = result.get(); + assertEquals(2, persons.size()); + assertEquals("John", persons.get(0).getFirstName()); + assertEquals("Jane", persons.get(1).getFirstName()); + + verify(springDataRepository, times(1)).findAll(); + } + + @Test + @DisplayName("Should return empty list when no entities exist") + void shouldReturnEmptyListWhenNoEntities() { + // Arrange + when(springDataRepository.findAll()).thenReturn(List.of()); - @Nested - @DisplayName("Find All") - class FindAll { - - @Test - @DisplayName("Should return list of Persons when entities exist") - void shouldReturnListOfPersons() { - // Arrange - var category2 = new JpaCategoryEntity(2L, "Personal"); - var person2 = new JpaPersonEntity( - 2L, - "Jane", - "Smith", - 30, - "+9876543210", - "jane@example.com", - category2 - ); - - when(springDataRepository.findAll()) - .thenReturn(List.of(jpaPersonEntity, person2)); - - // Act - var result = personRepositoryAdapter.findAll(); - - // Assert - assertTrue(result.isPresent()); - var persons = result.get(); - assertEquals(2, persons.size()); - assertEquals("John", persons.get(0).getFirstName()); - assertEquals("Jane", persons.get(1).getFirstName()); - - verify(springDataRepository, times(1)).findAll(); - } - - @Test - @DisplayName("Should return empty list when no entities exist") - void shouldReturnEmptyListWhenNoEntities() { - // Arrange - when(springDataRepository.findAll()).thenReturn(List.of()); - - // Act - var result = personRepositoryAdapter.findAll(); - - // Assert - assertTrue(result.isPresent()); - assertTrue(result.get().isEmpty()); - verify(springDataRepository, times(1)).findAll(); - } + // Act + var result = personRepositoryAdapter.findAll(); + + // Assert + assertTrue(result.isPresent()); + assertTrue(result.get().isEmpty()); + verify(springDataRepository, times(1)).findAll(); + } + } + + @Nested + @DisplayName("Save") + class Save { + + @Test + @DisplayName("Should save person and return domain model") + void shouldSavePersonAndReturnDomainModel() { + // Arrange + var personToSave = + new Person( + null, // New person without ID + "Jane", + "Smith", + 28, + "+1111111111", + "jane@example.com", + new Category(2L, "Personal")); + + var savedCategory = new JpaCategoryEntity(2L, "Personal"); + var savedEntity = + new JpaPersonEntity( + 100L, // ID assigned by database + "Jane", + "Smith", + 28, + "+1111111111", + "jane@example.com", + savedCategory); + + when(springDataRepository.save(any(JpaPersonEntity.class))).thenReturn(savedEntity); + + // Act + var result = personRepositoryAdapter.save(personToSave); + + // Assert + assertNotNull(result); + assertEquals(100L, result.getId()); + assertEquals("Jane", result.getFirstName()); + assertEquals("Smith", result.getLastName()); + assertEquals(28, result.getAge()); + + verify(springDataRepository, times(1)).save(any(JpaPersonEntity.class)); } - @Nested - @DisplayName("Save") - class Save { - - @Test - @DisplayName("Should save person and return domain model") - void shouldSavePersonAndReturnDomainModel() { - // Arrange - var personToSave = new Person( - null, // New person without ID - "Jane", - "Smith", - 28, - "+1111111111", - "jane@example.com", - new Category(2L, "Personal") - ); - - var savedCategory = new JpaCategoryEntity(2L, "Personal"); - var savedEntity = new JpaPersonEntity( - 100L, // ID assigned by database - "Jane", - "Smith", - 28, - "+1111111111", - "jane@example.com", - savedCategory - ); - - when(springDataRepository.save(any(JpaPersonEntity.class))) - .thenReturn(savedEntity); - - // Act - var result = personRepositoryAdapter.save(personToSave); - - // Assert - assertNotNull(result); - assertEquals(100L, result.getId()); - assertEquals("Jane", result.getFirstName()); - assertEquals("Smith", result.getLastName()); - assertEquals(28, result.getAge()); - - verify(springDataRepository, times(1)).save(any(JpaPersonEntity.class)); - } - - @Test - @DisplayName("Should correctly map domain model to JPA entity") - void shouldCorrectlyMapDomainToEntity() { - // Arrange - var personToSave = new Person( - null, - "Bob", - "Johnson", - 35, - "+2222222222", - "bob@example.com", - new Category(3L, "Business") - ); - - var savedEntity = new JpaPersonEntity( - 200L, - "Bob", - "Johnson", - 35, - "+2222222222", - "bob@example.com", - new JpaCategoryEntity(3L, "Business") - ); - - when(springDataRepository.save(any(JpaPersonEntity.class))) - .thenReturn(savedEntity); - - // Act - personRepositoryAdapter.save(personToSave); - - // Assert - Verify what was passed to Spring Data repository - verify(springDataRepository).save(entityCaptor.capture()); - JpaPersonEntity capturedEntity = entityCaptor.getValue(); - - assertEquals("Bob", capturedEntity.getFirstName()); - assertEquals("Johnson", capturedEntity.getLastName()); - assertEquals(35, capturedEntity.getAge()); - assertEquals("+2222222222", capturedEntity.getPhoneNumber()); - assertEquals("bob@example.com", capturedEntity.getEmail()); - assertEquals(3L, capturedEntity.getCategory().getId()); - assertEquals("Business", capturedEntity.getCategory().getType()); - } + @Test + @DisplayName("Should correctly map domain model to JPA entity") + void shouldCorrectlyMapDomainToEntity() { + // Arrange + var personToSave = + new Person( + null, + "Bob", + "Johnson", + 35, + "+2222222222", + "bob@example.com", + new Category(3L, "Business")); + + var savedEntity = + new JpaPersonEntity( + 200L, + "Bob", + "Johnson", + 35, + "+2222222222", + "bob@example.com", + new JpaCategoryEntity(3L, "Business")); + + when(springDataRepository.save(any(JpaPersonEntity.class))).thenReturn(savedEntity); + + // Act + personRepositoryAdapter.save(personToSave); + + // Assert - Verify what was passed to Spring Data repository + verify(springDataRepository).save(entityCaptor.capture()); + JpaPersonEntity capturedEntity = entityCaptor.getValue(); + + assertEquals("Bob", capturedEntity.getFirstName()); + assertEquals("Johnson", capturedEntity.getLastName()); + assertEquals(35, capturedEntity.getAge()); + assertEquals("+2222222222", capturedEntity.getPhoneNumber()); + assertEquals("bob@example.com", capturedEntity.getEmail()); + assertEquals(3L, capturedEntity.getCategory().getId()); + assertEquals("Business", capturedEntity.getCategory().getType()); } + } + + @Nested + @DisplayName("Delete By ID") + class DeleteById { + + @Test + @DisplayName("Should return true when person is successfully deleted") + void shouldReturnTrueWhenDeleted() { + // Arrange + doNothing().when(springDataRepository).deleteById(1L); + when(springDataRepository.findById(1L)).thenReturn(Optional.empty()); + + // Act + boolean result = personRepositoryAdapter.deleteById(1L); + + // Assert + assertTrue(result); + verify(springDataRepository, times(1)).deleteById(1L); + verify(springDataRepository, times(1)).findById(1L); + } + + @Test + @DisplayName("Should return false when person still exists after deletion") + void shouldReturnFalseWhenStillExists() { + // Arrange + doNothing().when(springDataRepository).deleteById(1L); + when(springDataRepository.findById(1L)).thenReturn(Optional.of(jpaPersonEntity)); - @Nested - @DisplayName("Delete By ID") - class DeleteById { - - @Test - @DisplayName("Should return true when person is successfully deleted") - void shouldReturnTrueWhenDeleted() { - // Arrange - doNothing().when(springDataRepository).deleteById(1L); - when(springDataRepository.findById(1L)).thenReturn(Optional.empty()); - - // Act - boolean result = personRepositoryAdapter.deleteById(1L); - - // Assert - assertTrue(result); - verify(springDataRepository, times(1)).deleteById(1L); - verify(springDataRepository, times(1)).findById(1L); - } - - @Test - @DisplayName("Should return false when person still exists after deletion") - void shouldReturnFalseWhenStillExists() { - // Arrange - doNothing().when(springDataRepository).deleteById(1L); - when(springDataRepository.findById(1L)).thenReturn(Optional.of(jpaPersonEntity)); - - // Act - boolean result = personRepositoryAdapter.deleteById(1L); - - // Assert - assertFalse(result); - verify(springDataRepository, times(1)).deleteById(1L); - verify(springDataRepository, times(1)).findById(1L); - } + // Act + boolean result = personRepositoryAdapter.deleteById(1L); + + // Assert + assertFalse(result); + verify(springDataRepository, times(1)).deleteById(1L); + verify(springDataRepository, times(1)).findById(1L); } + } } - diff --git a/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java index 5ae2ebf9cb37..9e0369b985c8 100644 --- a/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java +++ b/onion-architecture/infrastructure/src/test/java/com/iluwatar/onion/infrastructure/web/PersonControllerTest.java @@ -26,12 +26,19 @@ */ package com.iluwatar.onion.infrastructure.web; +import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + import com.fasterxml.jackson.databind.ObjectMapper; import com.iluwatar.onion.application.dto.PersonResponse; import com.iluwatar.onion.application.dto.SavePersonCommand; import com.iluwatar.onion.application.usecase.GetPersonUseCase; import com.iluwatar.onion.application.usecase.SavePersonUseCase; import com.iluwatar.onion.domain.exception.DomainException; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -41,336 +48,301 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import java.util.List; - -import static org.hamcrest.Matchers.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - @WebMvcTest(PersonController.class) class PersonControllerTest { - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @MockitoBean - private SavePersonUseCase savePersonUseCase; - - @MockitoBean - private GetPersonUseCase getPersonUseCase; - - @Nested - @DisplayName("GET /api/persons/{id}") - class GetPersonById { - - @Test - @DisplayName("Should return person when person exists") - void shouldReturnPersonWhenExists() throws Exception { - // Arrange - var response = new PersonResponse( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - 1L, - "Professional" - ); - - when(getPersonUseCase.execute(1L)).thenReturn(response); - - // Act & Assert - mockMvc.perform(get("/api/persons/1") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.id").value(1)) - .andExpect(jsonPath("$.firstName").value("John")) - .andExpect(jsonPath("$.lastName").value("Doe")) - .andExpect(jsonPath("$.age").value(25)) - .andExpect(jsonPath("$.phoneNumber").value("+1234567890")) - .andExpect(jsonPath("$.email").value("john.doe@example.com")) - .andExpect(jsonPath("$.categoryId").value(1)) - .andExpect(jsonPath("$.categoryType").value("Professional")); - - verify(getPersonUseCase, times(1)).execute(1L); - } - - @Test - @DisplayName("Should handle different person IDs") - void shouldHandleDifferentPersonIds() throws Exception { - // Arrange - var response = new PersonResponse( - 42L, - "Jane", - "Smith", - 30, - "+9876543210", - "jane@example.com", - 2L, - "Personal" - ); - - when(getPersonUseCase.execute(42L)).thenReturn(response); - - // Act & Assert - mockMvc.perform(get("/api/persons/42") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(42)) - .andExpect(jsonPath("$.firstName").value("Jane")); - - verify(getPersonUseCase, times(1)).execute(42L); - } + @Autowired private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + + @MockitoBean private SavePersonUseCase savePersonUseCase; + + @MockitoBean private GetPersonUseCase getPersonUseCase; + + @Nested + @DisplayName("GET /api/persons/{id}") + class GetPersonById { + + @Test + @DisplayName("Should return person when person exists") + void shouldReturnPersonWhenExists() throws Exception { + // Arrange + var response = + new PersonResponse( + 1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", 1L, "Professional"); + + when(getPersonUseCase.execute(1L)).thenReturn(response); + + // Act & Assert + mockMvc + .perform(get("/api/persons/1").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("John")) + .andExpect(jsonPath("$.lastName").value("Doe")) + .andExpect(jsonPath("$.age").value(25)) + .andExpect(jsonPath("$.phoneNumber").value("+1234567890")) + .andExpect(jsonPath("$.email").value("john.doe@example.com")) + .andExpect(jsonPath("$.categoryId").value(1)) + .andExpect(jsonPath("$.categoryType").value("Professional")); + + verify(getPersonUseCase, times(1)).execute(1L); } - @Nested - @DisplayName("GET /api/persons") - class GetAllPersons { - - @Test - @DisplayName("Should return list of persons") - void shouldReturnListOfPersons() throws Exception { - // Arrange - var person1 = new PersonResponse( - 1L, "John", "Doe", 25, "+1234567890", - "john@example.com", 1L, "Professional" - ); - var person2 = new PersonResponse( - 2L, "Jane", "Smith", 30, "+9876543210", - "jane@example.com", 2L, "Personal" - ); - - var persons = List.of(person1, person2); - when(getPersonUseCase.executeAll()).thenReturn(persons); - - // Act & Assert - mockMvc.perform(get("/api/persons") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[0].id").value(1)) - .andExpect(jsonPath("$[0].firstName").value("John")) - .andExpect(jsonPath("$[1].id").value(2)) - .andExpect(jsonPath("$[1].firstName").value("Jane")); - - verify(getPersonUseCase, times(1)).executeAll(); - } - - @Test - @DisplayName("Should return empty list when no persons exist") - void shouldReturnEmptyListWhenNoPersons() throws Exception { - // Arrange - when(getPersonUseCase.executeAll()).thenReturn(List.of()); - - // Act & Assert - mockMvc.perform(get("/api/persons") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$", hasSize(0))); - - verify(getPersonUseCase, times(1)).executeAll(); - } - - @Test - @DisplayName("Should handle large list of persons") - void shouldHandleLargeListOfPersons() throws Exception { - // Arrange - var persons = List.of( - new PersonResponse(1L, "Person1", "Last1", 25, "+1", "p1@example.com", 1L, "Cat1"), - new PersonResponse(2L, "Person2", "Last2", 26, "+2", "p2@example.com", 1L, "Cat1"), - new PersonResponse(3L, "Person3", "Last3", 27, "+3", "p3@example.com", 1L, "Cat1") - ); - - when(getPersonUseCase.executeAll()).thenReturn(persons); - - // Act & Assert - mockMvc.perform(get("/api/persons") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(3))); - - verify(getPersonUseCase, times(1)).executeAll(); - } + @Test + @DisplayName("Should handle different person IDs") + void shouldHandleDifferentPersonIds() throws Exception { + // Arrange + var response = + new PersonResponse( + 42L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", 2L, "Personal"); + + when(getPersonUseCase.execute(42L)).thenReturn(response); + + // Act & Assert + mockMvc + .perform(get("/api/persons/42").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(42)) + .andExpect(jsonPath("$.firstName").value("Jane")); + + verify(getPersonUseCase, times(1)).execute(42L); + } + } + + @Nested + @DisplayName("GET /api/persons") + class GetAllPersons { + + @Test + @DisplayName("Should return list of persons") + void shouldReturnListOfPersons() throws Exception { + // Arrange + var person1 = + new PersonResponse( + 1L, "John", "Doe", 25, "+1234567890", "john@example.com", 1L, "Professional"); + var person2 = + new PersonResponse( + 2L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", 2L, "Personal"); + + var persons = List.of(person1, person2); + when(getPersonUseCase.executeAll()).thenReturn(persons); + + // Act & Assert + mockMvc + .perform(get("/api/persons").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].firstName").value("John")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].firstName").value("Jane")); + + verify(getPersonUseCase, times(1)).executeAll(); } - @Nested - @DisplayName("POST /api/persons") - class SavePerson { - - @Test - @DisplayName("Should create person with valid data") - void shouldCreatePersonWithValidData() throws Exception { - // Arrange - var command = new SavePersonCommand( - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - "123 Main St", - 1L, - "Professional" - ); - - var response = new PersonResponse( - 1L, - "John", - "Doe", - 25, - "+1234567890", - "john.doe@example.com", - 1L, - "Professional" - ); - - when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response); - - // Act & Assert - mockMvc.perform(post("/api/persons") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(command))) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.id").value(1)) - .andExpect(jsonPath("$.firstName").value("John")) - .andExpect(jsonPath("$.lastName").value("Doe")) - .andExpect(jsonPath("$.age").value(25)) - .andExpect(jsonPath("$.phoneNumber").value("+1234567890")) - .andExpect(jsonPath("$.email").value("john.doe@example.com")) - .andExpect(jsonPath("$.categoryId").value(1)) - .andExpect(jsonPath("$.categoryType").value("Professional")); - - verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); - } - - @Test - @DisplayName("Should handle validation errors from use case") - void shouldPropagateValidationErrors() throws Exception { - // Arrange - var command = new SavePersonCommand( - "Young", - "Person", - 17, // Invalid age - "+1234567890", - "young@example.com", - "123 Main St", - 1L, - "Student" - ); - - when(savePersonUseCase.execute(any(SavePersonCommand.class))) - .thenThrow(new DomainException("Age cannot be less than 18")); - - // Act & Assert - mockMvc.perform(post("/api/persons") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(command))) - .andExpect(status().is4xxClientError()); - - verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); - } - - @Test - @DisplayName("Should handle malformed JSON") - void shouldHandleMalformedJson() throws Exception { - // Act & Assert - mockMvc.perform(post("/api/persons") - .contentType(MediaType.APPLICATION_JSON) - .content("{invalid json}")) - .andExpect(status().isBadRequest()); - - verify(savePersonUseCase, never()).execute(any(SavePersonCommand.class)); - } - - @Test - @DisplayName("Should accept all required fields in command") - void shouldAcceptAllRequiredFields() throws Exception { - // Arrange - var command = new SavePersonCommand( - "Complete", - "Person", - 30, - "+1111111111", - "complete@example.com", - "456 Oak Ave", - 5L, - "Business" - ); - - var response = new PersonResponse( - 10L, - "Complete", - "Person", - 30, - "+1111111111", - "complete@example.com", - 5L, - "Business" - ); - - when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response); - - // Act & Assert - mockMvc.perform(post("/api/persons") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(command))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.categoryType").value("Business")); - - verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); - } + @Test + @DisplayName("Should return empty list when no persons exist") + void shouldReturnEmptyListWhenNoPersons() throws Exception { + // Arrange + when(getPersonUseCase.executeAll()).thenReturn(List.of()); + + // Act & Assert + mockMvc + .perform(get("/api/persons").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(0))); + + verify(getPersonUseCase, times(1)).executeAll(); } - @Nested - @DisplayName("Controller Integration") - class ControllerIntegration { - - @Test - @DisplayName("Should handle multiple requests sequentially") - void shouldHandleMultipleRequestsSequentially() throws Exception { - // Arrange - var getResponse = new PersonResponse( - 1L, "John", "Doe", 25, "+1234567890", - "john@example.com", 1L, "Professional" - ); - - var savePersonCommand = new SavePersonCommand( - "Jane", "Smith", 30, "+9876543210", - "jane@example.com", "456 Oak", 2L, "Personal" - ); - - var savePersonResponse = new PersonResponse( - 2L, "Jane", "Smith", 30, "+9876543210", - "jane@example.com", 2L, "Personal" - ); - - when(getPersonUseCase.execute(1L)).thenReturn(getResponse); - when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(savePersonResponse); - - // Act & Assert - GET request - mockMvc.perform(get("/api/persons/1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.firstName").value("John")); - - // Act & Assert - POST request - mockMvc.perform(post("/api/persons") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(savePersonCommand))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.firstName").value("Jane")); - - verify(getPersonUseCase, times(1)).execute(1L); - verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); - } + @Test + @DisplayName("Should handle large list of persons") + void shouldHandleLargeListOfPersons() throws Exception { + // Arrange + var persons = + List.of( + new PersonResponse(1L, "Person1", "Last1", 25, "+1", "p1@example.com", 1L, "Cat1"), + new PersonResponse(2L, "Person2", "Last2", 26, "+2", "p2@example.com", 1L, "Cat1"), + new PersonResponse(3L, "Person3", "Last3", 27, "+3", "p3@example.com", 1L, "Cat1")); + + when(getPersonUseCase.executeAll()).thenReturn(persons); + + // Act & Assert + mockMvc + .perform(get("/api/persons").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))); + + verify(getPersonUseCase, times(1)).executeAll(); + } + } + + @Nested + @DisplayName("POST /api/persons") + class SavePerson { + + @Test + @DisplayName("Should create person with valid data") + void shouldCreatePersonWithValidData() throws Exception { + // Arrange + var command = + new SavePersonCommand( + "John", + "Doe", + 25, + "+1234567890", + "john.doe@example.com", + "123 Main St", + 1L, + "Professional"); + + var response = + new PersonResponse( + 1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", 1L, "Professional"); + + when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response); + + // Act & Assert + mockMvc + .perform( + post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(command))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("John")) + .andExpect(jsonPath("$.lastName").value("Doe")) + .andExpect(jsonPath("$.age").value(25)) + .andExpect(jsonPath("$.phoneNumber").value("+1234567890")) + .andExpect(jsonPath("$.email").value("john.doe@example.com")) + .andExpect(jsonPath("$.categoryId").value(1)) + .andExpect(jsonPath("$.categoryType").value("Professional")); + + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); } -} + @Test + @DisplayName("Should handle validation errors from use case") + void shouldPropagateValidationErrors() throws Exception { + // Arrange + var command = + new SavePersonCommand( + "Young", + "Person", + 17, // Invalid age + "+1234567890", + "young@example.com", + "123 Main St", + 1L, + "Student"); + + when(savePersonUseCase.execute(any(SavePersonCommand.class))) + .thenThrow(new DomainException("Age cannot be less than 18")); + + // Act & Assert + mockMvc + .perform( + post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(command))) + .andExpect(status().is4xxClientError()); + + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + + @Test + @DisplayName("Should handle malformed JSON") + void shouldHandleMalformedJson() throws Exception { + // Act & Assert + mockMvc + .perform( + post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content("{invalid json}")) + .andExpect(status().isBadRequest()); + + verify(savePersonUseCase, never()).execute(any(SavePersonCommand.class)); + } + + @Test + @DisplayName("Should accept all required fields in command") + void shouldAcceptAllRequiredFields() throws Exception { + // Arrange + var command = + new SavePersonCommand( + "Complete", + "Person", + 30, + "+1111111111", + "complete@example.com", + "456 Oak Ave", + 5L, + "Business"); + + var response = + new PersonResponse( + 10L, "Complete", "Person", 30, "+1111111111", "complete@example.com", 5L, "Business"); + + when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response); + + // Act & Assert + mockMvc + .perform( + post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(command))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.categoryType").value("Business")); + + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + } + + @Nested + @DisplayName("Controller Integration") + class ControllerIntegration { + + @Test + @DisplayName("Should handle multiple requests sequentially") + void shouldHandleMultipleRequestsSequentially() throws Exception { + // Arrange + var getResponse = + new PersonResponse( + 1L, "John", "Doe", 25, "+1234567890", "john@example.com", 1L, "Professional"); + + var savePersonCommand = + new SavePersonCommand( + "Jane", "Smith", 30, "+9876543210", "jane@example.com", "456 Oak", 2L, "Personal"); + + var savePersonResponse = + new PersonResponse( + 2L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", 2L, "Personal"); + + when(getPersonUseCase.execute(1L)).thenReturn(getResponse); + when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(savePersonResponse); + + // Act & Assert - GET request + mockMvc + .perform(get("/api/persons/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.firstName").value("John")); + + // Act & Assert - POST request + mockMvc + .perform( + post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(savePersonCommand))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.firstName").value("Jane")); + + verify(getPersonUseCase, times(1)).execute(1L); + verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class)); + } + } +}