├── .github ├── ISSUE_TEMPLATE │ ├── бъг.md │ ├── желание-или-идея.md │ └── проблем-с-проект.md └── workflows │ └── thanks.yaml ├── README.md ├── aop ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── aop │ │ │ ├── AopApplication.java │ │ │ ├── IncredibleMachine.java │ │ │ └── example │ │ │ ├── Example1Aspect.java │ │ │ └── Example1Demo.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── bg │ └── softuni │ └── aop │ └── AopApplicationTests.java ├── di-ioc ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── bg │ │ └── softuni │ │ ├── Main.java │ │ └── student │ │ ├── StudentService.java │ │ ├── StudentServiceImpl.java │ │ ├── config │ │ └── AppConfig.java │ │ ├── model │ │ └── Student.java │ │ ├── repository │ │ ├── CompountStudentRepository.java │ │ ├── FileStudentRepository.java │ │ ├── InMemoryStudentRepository.java │ │ └── StudentRepository.java │ │ └── scopes │ │ ├── Counter.java │ │ ├── CounterService.java │ │ └── CounterServiceImpl.java │ └── resources │ ├── beans-unused.xml.txt │ └── students.csv ├── events-scheduling-caches ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── events_scheduling_caches │ │ │ ├── EventsSchedulingCachesApplication.java │ │ │ ├── cache │ │ │ ├── StudentDTO.java │ │ │ ├── StudentService.java │ │ │ └── StudentsController.java │ │ │ ├── events │ │ │ ├── EventConfig.java │ │ │ ├── EventController.java │ │ │ ├── EventService.java │ │ │ ├── HelloWorldEvent.java │ │ │ └── HelloWorldEventListener.java │ │ │ └── scheduling │ │ │ ├── CronScheduler.java │ │ │ ├── FixRateScheduler.java │ │ │ └── FixedDelayScheduler.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── bg │ └── softuni │ └── events_scheduling_caches │ └── EventsSchedulingCachesApplicationTests.java ├── hateoas ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── hateoas │ │ │ ├── HateoasApplication.java │ │ │ ├── dto │ │ │ ├── OrderDTO.java │ │ │ └── StudentDTO.java │ │ │ ├── entity │ │ │ ├── CourseEntity.java │ │ │ ├── OrderEntity.java │ │ │ └── StudentEntity.java │ │ │ ├── repository │ │ │ └── StudentRepository.java │ │ │ ├── service │ │ │ └── StudentService.java │ │ │ └── web │ │ │ └── StudentController.java │ └── resources │ │ ├── application.yaml │ │ └── data.sql │ └── test │ └── java │ └── bg │ └── softuni │ └── hateoas │ └── HateoasApplicationTests.java ├── interview-questions ├── 01-spring-mvc.md ├── 02-thymeleaf.md ├── 03-state.md ├── 04-scheduler-events-cache.md ├── 05-rest-api.md ├── 06-spring-security.md ├── 07-error-handling.md ├── 08-unit-tests.md ├── 09-integration-tests.md ├── 10-aop.md └── 11-docker.md ├── kafka-consumer ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ ├── kafka │ │ │ └── consumer │ │ │ │ ├── ExRatesConsumer.java │ │ │ │ └── KafkaConsumerApplication.java │ │ │ └── mobilele │ │ │ └── model │ │ │ └── dto │ │ │ └── ExRateDTO.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── bg │ └── softuni │ └── kafka │ └── consumer │ └── KafkaConsumerApplicationTests.java ├── local-docker ├── docker-compose.yaml └── prometheus.yaml ├── mobilele-offers ├── .gitignore ├── build.gradle ├── docker │ └── Dockerfile ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── mobilele │ │ │ └── offers │ │ │ ├── MobileleOffersApplication.java │ │ │ ├── config │ │ │ └── OpenAPIConfig.java │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── AddOfferDTO.java │ │ │ │ └── OfferDTO.java │ │ │ ├── entity │ │ │ │ └── OfferEntity.java │ │ │ └── enums │ │ │ │ └── EngineTypeEnum.java │ │ │ ├── repository │ │ │ └── OfferRepository.java │ │ │ ├── security │ │ │ ├── JwtAuthenticationFilter.java │ │ │ └── SecurityConfig.java │ │ │ ├── service │ │ │ ├── JwtService.java │ │ │ ├── MonitoringService.java │ │ │ ├── OfferService.java │ │ │ ├── RetentionScheduler.java │ │ │ ├── exception │ │ │ │ └── ObjectNotFoundException.java │ │ │ └── impl │ │ │ │ ├── JwtServiceImpl.java │ │ │ │ ├── MonitoringServiceImpl.java │ │ │ │ └── OfferServiceImpl.java │ │ │ └── web │ │ │ └── OfferController.java │ └── resources │ │ └── application.yaml │ └── test │ ├── java │ └── bg │ │ └── softuni │ │ └── mobilele │ │ └── offers │ │ ├── MobileleOffersApplicationTests.java │ │ └── web │ │ └── OfferControllerIT.java │ ├── requests.http │ └── resources │ ├── application.yaml │ └── sample-create-offer.json ├── mobilele ├── .gitignore ├── build.gradle ├── docker │ ├── Dockerfile │ └── docker-compose.yaml ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── mobilele │ │ │ ├── MobileleApplication.java │ │ │ ├── config │ │ │ ├── AppConfig.java │ │ │ ├── ForexApiConfig.java │ │ │ ├── I18NConfig.java │ │ │ ├── KafkaConfig.java │ │ │ ├── OfferApiConfig.java │ │ │ ├── RestConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── WebConfig.java │ │ │ ├── init │ │ │ ├── ExRatesPublisher.java │ │ │ └── ExchangeRateInitializer.java │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── AddOfferDTO.java │ │ │ │ ├── ConversionResultDTO.java │ │ │ │ ├── ExRateDTO.java │ │ │ │ ├── ExRatesDTO.java │ │ │ │ ├── OfferDetailsDTO.java │ │ │ │ ├── OfferSummaryDTO.java │ │ │ │ ├── UserLoginDTO.java │ │ │ │ └── UserRegistrationDTO.java │ │ │ ├── entity │ │ │ │ ├── BaseEntity.java │ │ │ │ ├── ExRateEntity.java │ │ │ │ ├── OfferEntity.java │ │ │ │ ├── UUIDSequence.java │ │ │ │ ├── UUIDSequenceGenerator.java │ │ │ │ ├── UserEntity.java │ │ │ │ └── UserRoleEntity.java │ │ │ ├── enums │ │ │ │ ├── EngineTypeEnum.java │ │ │ │ └── UserRoleEnum.java │ │ │ └── user │ │ │ │ └── MobileleUserDetails.java │ │ │ ├── repository │ │ │ ├── ExRateRepository.java │ │ │ ├── OfferRepository.java │ │ │ ├── UserRepository.java │ │ │ └── UserRoleRepository.java │ │ │ ├── service │ │ │ ├── ExRateService.java │ │ │ ├── JwtService.java │ │ │ ├── KafkaPublicationService.java │ │ │ ├── OfferService.java │ │ │ ├── UserService.java │ │ │ ├── exception │ │ │ │ ├── ApiObjectNotFoundException.java │ │ │ │ └── ObjectNotFoundException.java │ │ │ └── impl │ │ │ │ ├── ExRateServiceImpl.java │ │ │ │ ├── JwtServiceImpl.java │ │ │ │ ├── KafkaPublicationServiceImpl.java │ │ │ │ ├── MobileleUserDetailsService.java │ │ │ │ ├── OfferServiceImpl.java │ │ │ │ └── UserServiceImpl.java │ │ │ └── web │ │ │ ├── CurrencyController.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── HomeController.java │ │ │ ├── LoginController.java │ │ │ ├── OfferController.java │ │ │ ├── OffersController.java │ │ │ ├── RegistrationController.java │ │ │ └── aop │ │ │ ├── MonitoringAspect.java │ │ │ ├── Pointcuts.java │ │ │ └── WarnIfExecutionExceeds.java │ └── resources │ │ ├── application.yaml │ │ ├── data.sql │ │ ├── i18n │ │ ├── messages_bg.properties │ │ └── messages_en.properties │ │ ├── static │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ ├── main.css │ │ │ └── reset-css.css │ │ ├── favicon.ico │ │ ├── images │ │ │ ├── 1.jpg │ │ │ ├── car-dealership-car-car-showroom.jpg │ │ │ ├── car.png │ │ │ └── user-avatar.svg │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ ├── currency.js │ │ │ └── jquery-3.5.1.slim.min.js │ │ └── templates │ │ ├── auth-login.html │ │ ├── auth-register.html │ │ ├── brands.html │ │ ├── details.html │ │ ├── fragments │ │ └── navbar.html │ │ ├── index.html │ │ ├── object-not-found.html │ │ ├── offer-add.html │ │ ├── offer-not-found.html │ │ ├── offers.html │ │ └── update.html │ └── test │ ├── java │ └── bg │ │ └── softuni │ │ └── mobilele │ │ ├── MobileleApplicationTests.java │ │ ├── service │ │ └── impl │ │ │ ├── ExRateServiceImplIT.java │ │ │ ├── ExRateServiceImplTest.java │ │ │ ├── MobileleUserDetailsServiceTest.java │ │ │ └── UserServiceImplTest.java │ │ └── web │ │ ├── CurrencyControllerIT.java │ │ └── RegistrationControllerIT.java │ └── resources │ ├── application.yaml │ └── currency-api.http ├── proxies ├── .gitignore ├── .idea │ └── misc.xml ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ └── java │ └── org │ └── example │ ├── Main.java │ └── proxydemo │ ├── Cacheable.java │ ├── CacheableInvocationHandler.java │ ├── StudentDTO.java │ ├── StudentService.java │ └── StudentServiceImpl.java ├── state ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── state │ │ │ ├── StateApplication.java │ │ │ └── web │ │ │ ├── CookieController.java │ │ │ └── SessionController.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ ├── cookies.html │ │ └── session.html │ └── test │ └── java │ └── bg │ └── softuni │ └── state │ └── StateApplicationTests.java ├── thymeleaf-pure ├── .gitignore ├── .idea │ └── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── org │ │ └── example │ │ └── Main.java │ └── resources │ └── test.html └── virt-threads-demo ├── api ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── eu │ │ │ └── balev │ │ │ └── virtual │ │ │ └── api │ │ │ ├── ApiApplication.java │ │ │ └── web │ │ │ └── RandomController.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── eu │ └── balev │ └── virtual │ └── api │ └── ApiApplicationTests.java └── server ├── .gitignore ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── eu │ │ └── balev │ │ └── virtual │ │ └── server │ │ ├── ServerApplication.java │ │ ├── config │ │ └── RestConfig.java │ │ └── web │ │ └── RandomController.java └── resources │ └── application.yaml └── test └── java └── eu └── balev └── virtual └── server └── ServerApplicationTests.java /.github/ISSUE_TEMPLATE/бъг.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Бъг 3 | about: Съобщете за бъг в нашите проекти 4 | title: '' 5 | labels: bug 6 | assignees: luchob 7 | 8 | --- 9 | 10 | 11 | 12 | **Опишете грешката** 13 | 14 | Опишете къде сме допуснали грешка. Линк към [кода](https://github.com/) е желателен. 15 | Какво очаквате да се случи, и какво се случва реално. 16 | 17 | **Стъпки за да се репродуцира** 18 | 19 | Ако описанието е недостатъчно, моля добавете тук. 20 | 21 | **Допълнителен контекст** 22 | Всякаква полезна информация - например OS, браузър и т.н. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/желание-или-идея.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Желание или идея 3 | about: Предложете нещо свежо за нашия курс. 4 | title: '' 5 | labels: suggestion 6 | assignees: luchob 7 | 8 | --- 9 | 10 | _Попълнете отделните секции отдолу и изтрийте упътванията._ 11 | 12 | Опишете накратко какво бихте искали да видите или да чуете в нашия курс. 13 | Не обещаваме, че това ще се случи. Но все пак значителна част от желанията са се сбъдвали. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/проблем-с-проект.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Проблем с проект" 3 | about: Моля, използвайте този template при въпроси за вашите проекти. 4 | title: '' 5 | labels: project-help 6 | assignees: luchob 7 | 8 | --- 9 | 10 | _Попълнете отделните секции отдолу и изтрийте упътванията._ 11 | 12 | *Връзка към проекта:* 13 | 14 | _Поставете [връзка](https://github.com/) към проекта си по подобен начин._ 15 | 16 | *Кратко описание:* 17 | 18 | Възможно най-накратко опишете: 19 | 20 | 1. Какво очаквате да се случи 21 | 2. Какво всъщност се случва 22 | 3. (Желателно) Какво опитахте 23 | 24 | *Стъпки за репродуциране:* 25 | 26 | Опишете възможно най-лесните стъпки, с помощта на които бързо може да се репродуцира проблемът. 27 | -------------------------------------------------------------------------------- /.github/workflows/thanks.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: [opened] 4 | 5 | jobs: 6 | thanks: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: EddieHubCommunity/gh-action-community/src/welcome@main 10 | with: 11 | github-token: ${{ secrets.ISSUES_TOKEN }} 12 | issue-message: '

🎉Благодаря, че сте тук и се интересувате от Spring Framework!🎉

🌴 За съжаление обаче, аз не съм тук (това е автоматичен отговор) и ще бъда много ограничено достъпен чак до 10-ти август.
🥲Така че, не обещавам да ви отговоря или да ви пиша.
🥹Извинете ме!' 13 | footer: ':rocket: Успешно и приятно програмиране! :rocket:' 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # softuni-spring-may-2024 2 | Our common projects 3 | -------------------------------------------------------------------------------- /aop/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /aop/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.1' 4 | id 'io.spring.dependency-management' version '1.1.5' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | toolchain { 12 | languageVersion = JavaLanguageVersion.of(21) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation 'org.springframework.boot:spring-boot-starter-web' 22 | implementation 'org.springframework.boot:spring-boot-starter-aop' 23 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 24 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 25 | } 26 | 27 | tasks.named('test') { 28 | useJUnitPlatform() 29 | } 30 | -------------------------------------------------------------------------------- /aop/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/aop/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /aop/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /aop/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'aop' 2 | -------------------------------------------------------------------------------- /aop/src/main/java/bg/softuni/aop/AopApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.aop; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AopApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AopApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /aop/src/main/java/bg/softuni/aop/IncredibleMachine.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.aop; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class IncredibleMachine { 9 | 10 | private static final Logger LOGGER = LoggerFactory.getLogger(IncredibleMachine.class); 11 | 12 | public void boom() { 13 | throw new NullPointerException("I made a bug"); 14 | } 15 | 16 | public void echo(String echo) { 17 | LOGGER.info("Echo {}", echo); 18 | } 19 | 20 | public String concat(String a, String b) { 21 | return a + "~" + b; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /aop/src/main/java/bg/softuni/aop/example/Example1Aspect.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.aop.example; 2 | 3 | import bg.softuni.aop.IncredibleMachine; 4 | import org.aspectj.lang.JoinPoint; 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.After; 7 | import org.aspectj.lang.annotation.AfterThrowing; 8 | import org.aspectj.lang.annotation.Around; 9 | import org.aspectj.lang.annotation.Aspect; 10 | import org.aspectj.lang.annotation.Before; 11 | import org.aspectj.lang.annotation.Pointcut; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.stereotype.Component; 15 | 16 | @Aspect 17 | @Component 18 | public class Example1Aspect { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(IncredibleMachine.class); 21 | 22 | @Pointcut("execution(* bg.softuni.aop.IncredibleMachine.echo(..))") 23 | void trackEchoCalled(){} 24 | 25 | @Pointcut("execution(* bg.softuni.aop.IncredibleMachine.concat(..))") 26 | void trackConcatCalled(){} 27 | 28 | @Before("trackEchoCalled()") 29 | public void beforeEchoCalled() { 30 | LOGGER.info("Before echo called..."); 31 | } 32 | 33 | @After("trackEchoCalled()") 34 | public void afterEchoCalled() { 35 | LOGGER.info("After echo called..."); 36 | } 37 | 38 | // the pointcut expressions can be inside the advice annotation 39 | @AfterThrowing(pointcut = "execution(* bg.softuni.aop.IncredibleMachine.boom())", 40 | throwing = "error") 41 | public void onError(JoinPoint joinPoint, 42 | Throwable error) { 43 | LOGGER.info("We have thrown an error in the method {}", 44 | joinPoint.getSignature(), 45 | error); 46 | } 47 | 48 | @Around(value = "trackConcatCalled() && args(a, b)", 49 | argNames = "pjp,a,b") 50 | public String onConcat( 51 | ProceedingJoinPoint pjp, 52 | String a, 53 | String b) throws Throwable { 54 | 55 | // before 56 | LOGGER.info("Method concat was called with argumets {} and {}", a, b); 57 | String modifiedA = "(" + a + ")"; 58 | String modifiedB = "(" + b + ")"; 59 | 60 | //(Hello) (world) 61 | // execute 62 | String result = (String)pjp.proceed(new Object[]{modifiedA, modifiedB}); 63 | // (Hello)~(world) 64 | 65 | // after 66 | return "[" + result + "]"; 67 | //[(Hello)~(world)] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /aop/src/main/java/bg/softuni/aop/example/Example1Demo.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.aop.example; 2 | 3 | import bg.softuni.aop.IncredibleMachine; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class Example1Demo implements CommandLineRunner { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(IncredibleMachine.class); 13 | 14 | private final IncredibleMachine incredibleMachine; 15 | 16 | public Example1Demo(IncredibleMachine incredibleMachine) { 17 | this.incredibleMachine = incredibleMachine; 18 | } 19 | 20 | 21 | @Override 22 | public void run(String... args) throws Exception { 23 | // this.incredibleMachine.echo("Hello, world!"); 24 | // try { 25 | // this.incredibleMachine.boom(); 26 | // } catch (Exception e) { 27 | // // do not log 28 | // } 29 | // 30 | String result = this.incredibleMachine.concat("Hello", "world"); 31 | 32 | LOGGER.info("The result is: {}", result); 33 | //The result is: [(Hello)~(world)] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /aop/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=aop 2 | -------------------------------------------------------------------------------- /aop/src/test/java/bg/softuni/aop/AopApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.aop; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class AopApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /di-ioc/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea 9 | .idea/modules.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /di-ioc/README.md: -------------------------------------------------------------------------------- 1 | In this example we explained what is dependency injection and how the Spring IoC works. -------------------------------------------------------------------------------- /di-ioc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'org.example' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | 14 | implementation group: 'org.springframework', name: 'spring-context', version: '6.1.2' 15 | implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.1.1' 16 | 17 | 18 | testImplementation platform('org.junit:junit-bom:5.9.1') 19 | testImplementation 'org.junit.jupiter:junit-jupiter' 20 | } 21 | 22 | test { 23 | useJUnitPlatform() 24 | } -------------------------------------------------------------------------------- /di-ioc/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/di-ioc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /di-ioc/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 05 19:42:49 EEST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /di-ioc/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /di-ioc/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'di-ioc' 2 | 3 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/Main.java: -------------------------------------------------------------------------------- 1 | package bg.softuni; 2 | 3 | import bg.softuni.student.StudentService; 4 | import bg.softuni.student.StudentServiceImpl; 5 | import bg.softuni.student.config.AppConfig; 6 | import bg.softuni.student.repository.InMemoryStudentRepository; 7 | import bg.softuni.student.scopes.CounterService; 8 | import java.util.stream.Collectors; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ConfigurableApplicationContext; 11 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 12 | import org.springframework.context.support.ClassPathXmlApplicationContext; 13 | 14 | public class Main { 15 | 16 | public static void main(String[] args) { 17 | 18 | ConfigurableApplicationContext context = new AnnotationConfigApplicationContext("bg.softuni"); 19 | 20 | context.registerShutdownHook(); 21 | 22 | // StudentService studentService = context.getBean(StudentService.class); 23 | // 24 | // System.out.println("Students " + studentService.findYoungestStudents()); 25 | 26 | CounterService counterService = context.getBean(CounterService.class); 27 | counterService.count(); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/StudentService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student; 2 | 3 | import bg.softuni.student.model.Student; 4 | 5 | import java.util.Set; 6 | 7 | public interface StudentService { 8 | 9 | Set findYoungestStudents(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/StudentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student; 2 | 3 | import bg.softuni.student.model.Student; 4 | import bg.softuni.student.repository.CompountStudentRepository; 5 | import bg.softuni.student.repository.StudentRepository; 6 | 7 | import java.time.LocalDate; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.stream.Collectors; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Qualifier; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.stereotype.Service; 16 | 17 | @Service 18 | public class StudentServiceImpl implements StudentService { 19 | private final StudentRepository studentRepository; 20 | 21 | public StudentServiceImpl(StudentRepository studentRepository) { 22 | // we may pass this 23 | this.studentRepository = studentRepository; 24 | } 25 | 26 | @Override 27 | public Set findYoungestStudents() { 28 | var sorted = studentRepository 29 | .getAllStudents() 30 | .stream() 31 | .sorted(Comparator.comparing(Student::birthDay).reversed()) 32 | .toList(); 33 | 34 | if (sorted.isEmpty()) { 35 | return Set.of(); 36 | } else { 37 | LocalDate lastBirthDay = sorted.get(0).birthDay(); 38 | 39 | return sorted 40 | .stream() 41 | .filter(s -> s.birthDay().equals(lastBirthDay)) 42 | .collect(Collectors.toSet()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.config; 2 | 3 | import bg.softuni.student.StudentService; 4 | import bg.softuni.student.StudentServiceImpl; 5 | import bg.softuni.student.repository.FileStudentRepository; 6 | import bg.softuni.student.repository.InMemoryStudentRepository; 7 | import bg.softuni.student.repository.StudentRepository; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class AppConfig { 13 | 14 | // @Bean( 15 | // name = "fileRepo" 16 | // ) 17 | // public StudentRepository studentRepository() { 18 | // return new InMemoryStudentRepository(); 19 | // } 20 | // 21 | // @Bean 22 | // public StudentService studentService(StudentRepository studentRepository) { 23 | // return new StudentServiceImpl(studentRepository); 24 | // } 25 | } 26 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/model/Student.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.model; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | 6 | public record Student(String name, LocalDate birthDay) { 7 | 8 | public Student(String name, LocalDate birthDay) { 9 | this.name = Objects.requireNonNull(name); 10 | this.birthDay = Objects.requireNonNull(birthDay); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/repository/CompountStudentRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.repository; 2 | 3 | import bg.softuni.student.model.Student; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class CompountStudentRepository implements StudentRepository { 8 | 9 | private final List allRepos; 10 | 11 | public CompountStudentRepository(List allRepos) { 12 | this.allRepos = allRepos; 13 | } 14 | 15 | @Override 16 | public List getAllStudents() { 17 | 18 | List allStudents = new ArrayList<>(); 19 | 20 | allRepos.forEach(r -> allStudents.addAll(r.getAllStudents())); 21 | 22 | return allStudents; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/repository/FileStudentRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.repository; 2 | 3 | import bg.softuni.student.model.Student; 4 | 5 | import jakarta.annotation.PostConstruct; 6 | import jakarta.annotation.PreDestroy; 7 | import java.io.BufferedReader; 8 | import java.io.InputStreamReader; 9 | import java.time.LocalDate; 10 | import java.util.List; 11 | import org.springframework.beans.factory.BeanNameAware; 12 | import org.springframework.stereotype.Repository; 13 | 14 | 15 | @Repository 16 | public class FileStudentRepository implements StudentRepository, BeanNameAware { 17 | 18 | private String name; 19 | 20 | public FileStudentRepository() { 21 | System.out.println("Instantiating... "); 22 | } 23 | 24 | @Override 25 | public List getAllStudents() { 26 | return 27 | new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("students.csv"))) 28 | .lines() 29 | .map(this::asStudent) 30 | .toList(); 31 | 32 | 33 | } 34 | 35 | private Student asStudent(String s) { 36 | var line = s.split(","); 37 | return new Student(line[0].trim(), LocalDate.parse(line[1].trim())); 38 | } 39 | 40 | @PostConstruct 41 | public void init() { 42 | System.out.println(this.name + " manages " + getAllStudents().size() + " students."); 43 | } 44 | 45 | @PreDestroy 46 | public void destroy() { 47 | System.out.println("Clean up of file student repo. Bye!"); 48 | } 49 | 50 | @Override 51 | public void setBeanName(String name) { 52 | this.name = name; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/repository/InMemoryStudentRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.repository; 2 | 3 | import bg.softuni.student.model.Student; 4 | 5 | import jakarta.annotation.PostConstruct; 6 | import jakarta.annotation.PreDestroy; 7 | import java.time.LocalDate; 8 | import java.util.List; 9 | import org.springframework.beans.factory.BeanNameAware; 10 | import org.springframework.beans.factory.DisposableBean; 11 | import org.springframework.beans.factory.InitializingBean; 12 | import org.springframework.context.annotation.Primary; 13 | import org.springframework.stereotype.Repository; 14 | 15 | @Repository 16 | public class InMemoryStudentRepository implements StudentRepository, BeanNameAware, 17 | InitializingBean, DisposableBean { 18 | 19 | private String name; 20 | 21 | public InMemoryStudentRepository() { 22 | System.out.println("Instantiating... "); 23 | } 24 | 25 | private final List students = List.of( 26 | new Student("Nina Bojinova", LocalDate.of(1977, 12, 9)), 27 | new Student("Lachezar Balev", LocalDate.of(1979, 3, 7)), 28 | new Student("Lachezar Balev Fake", LocalDate.of(1979, 3, 7)) 29 | ); 30 | 31 | @Override 32 | public List getAllStudents() { 33 | return students; 34 | } 35 | 36 | @Override 37 | public void destroy() throws Exception { 38 | System.out.println("Clean up of in memory student repo. Bye!"); 39 | } 40 | 41 | @Override 42 | public void setBeanName(String name) { 43 | this.name = name; 44 | } 45 | 46 | @Override 47 | public void afterPropertiesSet() throws Exception { 48 | System.out.println(this.name + " manages " + getAllStudents().size() + " students."); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/repository/StudentRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.repository; 2 | 3 | import bg.softuni.student.model.Student; 4 | 5 | import java.util.List; 6 | 7 | public interface StudentRepository { 8 | List getAllStudents(); 9 | } 10 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/scopes/Counter.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.scopes; 2 | 3 | import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; 4 | 5 | import org.springframework.context.annotation.Scope; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class Counter { 10 | 11 | private int cnt; 12 | 13 | void inc() { 14 | cnt++; 15 | } 16 | 17 | int getCnt() { 18 | return cnt; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/scopes/CounterService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.scopes; 2 | 3 | import org.springframework.stereotype.Service; 4 | public interface CounterService { 5 | 6 | void count(); 7 | } 8 | -------------------------------------------------------------------------------- /di-ioc/src/main/java/bg/softuni/student/scopes/CounterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.student.scopes; 2 | 3 | import org.springframework.beans.factory.annotation.Lookup; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | public abstract class CounterServiceImpl implements CounterService { 8 | 9 | @Override 10 | public void count() { 11 | Counter counter1 = counter(); 12 | Counter counter2 = counter(); 13 | 14 | counter1.inc(); 15 | counter2.inc(); 16 | 17 | System.out.println("Counter1: " + counter1.getCnt()); 18 | System.out.println("Counter2: " + counter2.getCnt()); 19 | } 20 | 21 | @Lookup 22 | public abstract Counter counter(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /di-ioc/src/main/resources/beans-unused.xml.txt: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /di-ioc/src/main/resources/students.csv: -------------------------------------------------------------------------------- 1 | Dani Balev, 2008-03-06 2 | Viki Baleva, 2011-08-07 -------------------------------------------------------------------------------- /events-scheduling-caches/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /events-scheduling-caches/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.0' 4 | id 'io.spring.dependency-management' version '1.1.5' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | toolchain { 12 | languageVersion = JavaLanguageVersion.of(21) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation 'org.springframework.boot:spring-boot-starter-cache' 22 | implementation 'org.springframework.boot:spring-boot-starter-web' 23 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 24 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 25 | } 26 | 27 | tasks.named('test') { 28 | useJUnitPlatform() 29 | } 30 | -------------------------------------------------------------------------------- /events-scheduling-caches/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/events-scheduling-caches/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /events-scheduling-caches/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /events-scheduling-caches/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'events-scheduling-caches' 2 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/EventsSchedulingCachesApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | @SpringBootApplication 9 | @EnableScheduling 10 | @EnableCaching 11 | public class EventsSchedulingCachesApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(EventsSchedulingCachesApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/cache/StudentDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.cache; 2 | 3 | public record StudentDTO(String name, Integer age) { 4 | } 5 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/cache/StudentService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.cache; 2 | 3 | import bg.softuni.events_scheduling_caches.scheduling.CronScheduler; 4 | import java.util.List; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.cache.annotation.CacheEvict; 8 | import org.springframework.cache.annotation.CachePut; 9 | import org.springframework.cache.annotation.Cacheable; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | public class StudentService { 14 | 15 | private final Logger LOGGER = LoggerFactory.getLogger(StudentService.class); 16 | 17 | @Cacheable("students") 18 | public List getAllStudents() { 19 | LOGGER.info("Inside getAllStudents()"); 20 | try { 21 | Thread.sleep(1000); 22 | } catch (InterruptedException e) { 23 | throw new RuntimeException(e); 24 | } 25 | 26 | LOGGER.info("Returning result from getAllStudents()"); 27 | return List.of( 28 | new StudentDTO("Pehso", 32), 29 | new StudentDTO("Ani", 23) 30 | ); 31 | } 32 | 33 | @CachePut("students") 34 | public List updateStudents() { 35 | LOGGER.info("Inside updateStudents()"); 36 | 37 | return List.of( 38 | new StudentDTO("Gosho", 11), 39 | new StudentDTO("Milena", 12) 40 | ); 41 | } 42 | 43 | @CacheEvict("students") 44 | public void removeStudentsFromCache(){} 45 | 46 | 47 | @Cacheable(value = "students", key="#name") 48 | public StudentDTO getStudentByName(String name) { 49 | LOGGER.info("GET STUDENT {}", name); 50 | return new StudentDTO(name, null); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/cache/StudentsController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.cache; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class StudentsController { 8 | 9 | private final StudentService studentService; 10 | 11 | public StudentsController(StudentService studentService) { 12 | this.studentService = studentService; 13 | } 14 | 15 | @GetMapping("/cache") 16 | public String getAll() { 17 | 18 | // studentService 19 | // .getAllStudents() 20 | // .forEach(System.out::println); 21 | // 22 | // System.out.println("------"); 23 | // 24 | // studentService 25 | // .updateStudents(); 26 | // 27 | // System.out.println("------"); 28 | // 29 | // studentService 30 | // .getAllStudents() 31 | // .forEach(System.out::println); 32 | // 33 | // System.out.println("------"); 34 | // studentService.removeStudentsFromCache(); 35 | // 36 | // System.out.println("------"); 37 | // 38 | // studentService 39 | // .getAllStudents() 40 | // .forEach(System.out::println); 41 | 42 | 43 | 44 | 45 | 46 | // studentService 47 | // .getAllStudents() 48 | // .forEach(System.out::println); 49 | // 50 | // System.out.println("------"); 51 | // 52 | // studentService 53 | // .getAllStudents() 54 | // .forEach(System.out::println); 55 | // 56 | // System.out.println("------"); 57 | // 58 | // studentService 59 | // .updateStudents(); 60 | // 61 | // System.out.println("------"); 62 | // 63 | // studentService 64 | // .getAllStudents() 65 | // .forEach(System.out::println); 66 | 67 | // var student1 = studentService.getStudentByName("Pesho"); 68 | // var student2 = studentService.getStudentByName("Pesho"); 69 | // var student3 = studentService.getStudentByName("Ani"); 70 | // var student4 = studentService.getStudentByName("Ani"); 71 | // 72 | // System.out.println(student1); 73 | // System.out.println(student2); 74 | // System.out.println(student3); 75 | // System.out.println(student4); 76 | 77 | return "OK"; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/events/EventConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.events; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.event.ApplicationEventMulticaster; 6 | import org.springframework.context.event.SimpleApplicationEventMulticaster; 7 | import org.springframework.core.task.SimpleAsyncTaskExecutor; 8 | 9 | @Configuration 10 | public class EventConfig { 11 | 12 | @Bean 13 | public ApplicationEventMulticaster applicationEventMulticaster() { 14 | SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(); 15 | simpleApplicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); 16 | return simpleApplicationEventMulticaster; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/events/EventController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.events; 2 | 3 | import org.springframework.context.ApplicationEventPublisher; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class EventController { 10 | 11 | private final ApplicationEventPublisher applicationEventPublisher; 12 | 13 | public EventController(ApplicationEventPublisher applicationEventPublisher) { 14 | this.applicationEventPublisher = applicationEventPublisher; 15 | } 16 | 17 | @GetMapping("/fire-event") 18 | public String fireEvent(@RequestParam("name") String name) { 19 | 20 | System.out.println("Step 1. " + Thread.currentThread().getName()); 21 | applicationEventPublisher.publishEvent(new HelloWorldEvent(this, "Hello, " + name)); 22 | System.out.println("Step 2. " + Thread.currentThread().getName()); 23 | 24 | return "OK"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/events/EventService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.events; 2 | 3 | 4 | import org.springframework.context.event.EventListener; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class EventService { 9 | 10 | @EventListener 11 | public void onHelloWorldEvent(HelloWorldEvent helloWorldEvent) { 12 | System.out.println("In EventService: " + Thread.currentThread().getName()); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/events/HelloWorldEvent.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.events; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class HelloWorldEvent extends ApplicationEvent { 6 | 7 | private final String message; 8 | 9 | public HelloWorldEvent(Object source, String message) { 10 | super(source); 11 | this.message = message; 12 | } 13 | 14 | public String getMessage() { 15 | return message; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/events/HelloWorldEventListener.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.events; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | import org.springframework.context.ApplicationListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class HelloWorldEventListener implements ApplicationListener { 9 | 10 | @Override 11 | public void onApplicationEvent(HelloWorldEvent event) { 12 | System.out.println("In HelloWorldEventListener: " + Thread.currentThread().getName()); 13 | } 14 | // 15 | // @Override 16 | // public boolean supportsAsyncExecution() { 17 | // return false; 18 | // } 19 | } 20 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/scheduling/CronScheduler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.scheduling; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | //@Component 9 | public class CronScheduler { 10 | 11 | private final Logger LOGGER = LoggerFactory.getLogger(CronScheduler.class); 12 | 13 | @Scheduled(cron = "*/10 * * * * *") 14 | public void onCron() { 15 | LOGGER.info("On cron"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/scheduling/FixRateScheduler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.scheduling; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | //@Component 9 | public class FixRateScheduler { 10 | 11 | private int counter = 0; 12 | 13 | private Logger LOGGER = LoggerFactory.getLogger(FixRateScheduler.class); 14 | 15 | @Scheduled(fixedRate = 5000) 16 | public void onFixedRate() throws InterruptedException { 17 | counter++; 18 | 19 | int toSleep = counter % 2 == 0 ? 2000 : 6000; 20 | 21 | LOGGER.info("Start of onFixedRate(). To sleep: " + toSleep); 22 | 23 | Thread.sleep(toSleep); 24 | 25 | LOGGER.info("End of onFixedRate()"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/java/bg/softuni/events_scheduling_caches/scheduling/FixedDelayScheduler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches.scheduling; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | //@Component 9 | public class FixedDelayScheduler { 10 | private int counter = 0; 11 | 12 | private final Logger LOGGER = LoggerFactory.getLogger(FixedDelayScheduler.class); 13 | 14 | @Scheduled(fixedDelay = 5000) 15 | public void onFixedDelay() throws InterruptedException { 16 | counter++; 17 | 18 | int toSleep = counter % 2 == 0 ? 2000 : 6000; 19 | 20 | LOGGER.info("Start of onFixedDelay(). To sleep: " + toSleep); 21 | 22 | Thread.sleep(toSleep); 23 | 24 | LOGGER.info("End of onFixedDelay()"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=events-scheduling-caches 2 | -------------------------------------------------------------------------------- /events-scheduling-caches/src/test/java/bg/softuni/events_scheduling_caches/EventsSchedulingCachesApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.events_scheduling_caches; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class EventsSchedulingCachesApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /hateoas/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /hateoas/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.0' 4 | id 'io.spring.dependency-management' version '1.1.5' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | toolchain { 12 | languageVersion = JavaLanguageVersion.of(21) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 22 | implementation 'org.springframework.boot:spring-boot-starter-hateoas' 23 | implementation 'org.springframework.boot:spring-boot-starter-web' 24 | runtimeOnly 'com.mysql:mysql-connector-j' 25 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 26 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 27 | } 28 | 29 | tasks.named('test') { 30 | useJUnitPlatform() 31 | } 32 | -------------------------------------------------------------------------------- /hateoas/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/hateoas/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /hateoas/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /hateoas/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'hateoas' 2 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/HateoasApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HateoasApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(HateoasApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/dto/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.dto; 2 | 3 | public class OrderDTO { 4 | 5 | private Long studentId; 6 | private Long courseId; 7 | 8 | public Long getStudentId() { 9 | return studentId; 10 | } 11 | 12 | public OrderDTO setStudentId(Long studentId) { 13 | this.studentId = studentId; 14 | return this; 15 | } 16 | 17 | public Long getCourseId() { 18 | return courseId; 19 | } 20 | 21 | public OrderDTO setCourseId(Long courseId) { 22 | this.courseId = courseId; 23 | return this; 24 | } 25 | } -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/dto/StudentDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.dto; 2 | 3 | import java.util.List; 4 | import org.springframework.hateoas.server.core.Relation; 5 | 6 | @Relation(collectionRelation = "students") 7 | public class StudentDTO { 8 | 9 | private Long id; 10 | private String name; 11 | private int age; 12 | private boolean deleted; 13 | 14 | private List orders; 15 | 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | public StudentDTO setId(Long id) { 21 | this.id = id; 22 | return this; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public StudentDTO setName(String name) { 30 | this.name = name; 31 | return this; 32 | } 33 | 34 | public int getAge() { 35 | return age; 36 | } 37 | 38 | public StudentDTO setAge(int age) { 39 | this.age = age; 40 | return this; 41 | } 42 | 43 | public boolean isDeleted() { 44 | return deleted; 45 | } 46 | 47 | public StudentDTO setDeleted(boolean deleted) { 48 | this.deleted = deleted; 49 | return this; 50 | } 51 | 52 | public List getOrders() { 53 | return orders; 54 | } 55 | 56 | public StudentDTO setOrders(List orders) { 57 | this.orders = orders; 58 | return this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/entity/CourseEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.FetchType; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.OneToMany; 9 | import jakarta.persistence.Table; 10 | import java.util.List; 11 | 12 | @Entity 13 | @Table(name = "courses") 14 | public class CourseEntity { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | private String name; 21 | private int price; 22 | 23 | @OneToMany(mappedBy = "course", fetch = FetchType.EAGER) 24 | private List orders; 25 | 26 | public Long getId() { 27 | return id; 28 | } 29 | 30 | public CourseEntity setId(Long id) { 31 | this.id = id; 32 | return this; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public CourseEntity setName(String name) { 40 | this.name = name; 41 | return this; 42 | } 43 | 44 | public int getPrice() { 45 | return price; 46 | } 47 | 48 | public CourseEntity setPrice(int price) { 49 | this.price = price; 50 | return this; 51 | } 52 | 53 | public List getOrders() { 54 | return orders; 55 | } 56 | 57 | public CourseEntity setOrders(List orders) { 58 | this.orders = orders; 59 | return this; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/entity/OrderEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.ManyToOne; 8 | import jakarta.persistence.Table; 9 | 10 | @Entity 11 | @Table(name = "orders") 12 | public class OrderEntity { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long id; 17 | 18 | 19 | @ManyToOne 20 | private CourseEntity course; 21 | 22 | @ManyToOne 23 | private StudentEntity student; 24 | 25 | public Long getId() { 26 | return id; 27 | } 28 | 29 | public OrderEntity setId(Long id) { 30 | this.id = id; 31 | return this; 32 | } 33 | 34 | public CourseEntity getCourse() { 35 | return course; 36 | } 37 | 38 | public OrderEntity setCourse(CourseEntity course) { 39 | this.course = course; 40 | return this; 41 | } 42 | 43 | public StudentEntity getStudent() { 44 | return student; 45 | } 46 | 47 | public OrderEntity setStudent(StudentEntity student) { 48 | this.student = student; 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/entity/StudentEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.FetchType; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.OneToMany; 9 | import jakarta.persistence.Table; 10 | import java.util.List; 11 | 12 | @Entity 13 | @Table(name = "students") 14 | public class StudentEntity { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | private String name; 21 | private int age; 22 | private boolean deleted; 23 | 24 | @OneToMany(mappedBy = "student") 25 | private List orders; 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public StudentEntity setId(Long id) { 32 | this.id = id; 33 | return this; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public StudentEntity setName(String name) { 41 | this.name = name; 42 | return this; 43 | } 44 | 45 | public int getAge() { 46 | return age; 47 | } 48 | 49 | public StudentEntity setAge(int age) { 50 | this.age = age; 51 | return this; 52 | } 53 | 54 | public boolean isDeleted() { 55 | return deleted; 56 | } 57 | 58 | public StudentEntity setDeleted(boolean deleted) { 59 | this.deleted = deleted; 60 | return this; 61 | } 62 | 63 | public List getOrders() { 64 | return orders; 65 | } 66 | 67 | public StudentEntity setOrders(List orders) { 68 | this.orders = orders; 69 | return this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/repository/StudentRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.repository; 2 | 3 | import bg.softuni.hateoas.entity.StudentEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface StudentRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/service/StudentService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.service; 2 | 3 | import bg.softuni.hateoas.dto.OrderDTO; 4 | import bg.softuni.hateoas.dto.StudentDTO; 5 | import bg.softuni.hateoas.entity.OrderEntity; 6 | import bg.softuni.hateoas.entity.StudentEntity; 7 | import bg.softuni.hateoas.repository.StudentRepository; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class StudentService { 15 | 16 | private final StudentRepository studentRepository; 17 | 18 | public StudentService(StudentRepository studentRepository) { 19 | this.studentRepository = studentRepository; 20 | } 21 | 22 | public List getAllStudents() { 23 | return studentRepository.findAll().stream().map(this::map).toList(); 24 | } 25 | 26 | public List getStudentOrders(Long studentId) { 27 | return getStudentById(studentId). 28 | map(StudentDTO::getOrders). 29 | orElseGet(ArrayList::new); 30 | } 31 | 32 | public Optional getStudentById(Long studentId) { 33 | return studentRepository.findById(studentId).map(this::map); 34 | } 35 | 36 | private StudentDTO map(StudentEntity entity) { 37 | 38 | var orders = entity. 39 | getOrders(). 40 | stream(). 41 | map(this::map). 42 | toList(); 43 | 44 | return new StudentDTO(). 45 | setAge(entity.getAge()). 46 | setDeleted(entity.isDeleted()). 47 | setId(entity.getId()). 48 | setName(entity.getName()). 49 | setOrders(orders); 50 | } 51 | 52 | private OrderDTO map(OrderEntity entity) { 53 | return new OrderDTO().setStudentId(entity.getStudent().getId()). 54 | setCourseId(entity.getCourse().getId()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /hateoas/src/main/java/bg/softuni/hateoas/web/StudentController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas.web; 2 | 3 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 4 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; 5 | 6 | import bg.softuni.hateoas.dto.OrderDTO; 7 | import bg.softuni.hateoas.dto.StudentDTO; 8 | import bg.softuni.hateoas.service.StudentService; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import org.springframework.hateoas.CollectionModel; 12 | import org.springframework.hateoas.EntityModel; 13 | import org.springframework.hateoas.Link; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PutMapping; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | @RequestMapping("/students") 22 | @RestController 23 | public class StudentController { 24 | 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /hateoas/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mobilele 4 | # thymeleaf: 5 | # check-template-location: true 6 | # cache: false 7 | # prefix: file:./src/main/resources/templates/ 8 | #server: 9 | # port: 8081 10 | datasource: 11 | driverClassName: com.mysql.cj.jdbc.Driver 12 | url: jdbc:mysql://localhost:3306/hateoas?allowPublicKeyRetrieval=true&useSSL=false&createDatabaseIfNotExist=true&serverTimezone=UTC 13 | username: root 14 | password: 15 | jpa: 16 | defer-datasource-initialization: true 17 | properties: 18 | hibernate: 19 | format_sql: true 20 | hibernate: 21 | ddl-auto: update 22 | sql: 23 | init: 24 | mode: never 25 | mvc: 26 | hiddenmethod: 27 | filter: 28 | enabled: true -------------------------------------------------------------------------------- /hateoas/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO students (id, age, deleted, name) VALUES (1, "21", 0, "Pesho"); 2 | INSERT INTO students (id, age, deleted, name) VALUES (2, "22", 0, "Gosho"); 3 | INSERT INTO students (id, age, deleted, name) VALUES (3, "23", 1, "Anna"); 4 | 5 | INSERT INTO courses (id, name, price) VALUES (1, "Spring", 100); 6 | INSERT INTO courses (id, name, price) VALUES (2, "JavaScript", 5); 7 | 8 | INSERT INTO orders (id, course_id, student_id) VALUES (1, 1, 1); 9 | INSERT INTO orders (id, course_id, student_id) VALUES (2, 1, 2); 10 | INSERT INTO orders (id, course_id, student_id) VALUES (3, 2, 2); -------------------------------------------------------------------------------- /hateoas/src/test/java/bg/softuni/hateoas/HateoasApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.hateoas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class HateoasApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /interview-questions/01-spring-mvc.md: -------------------------------------------------------------------------------- 1 | # Spring MVC 2 | 3 | 1. Опишете пътя на един HTTP request от браузъра, през Spring стека, до обратния оттовор към клиента. 4 | 2. Какво е MVC? 5 | 3. Как се анотира един контролер? 6 | 4. Какво е `@ResponseBody`? 7 | 5. Какво е `@ReqestMapping` и какво е поведението на анотиран клас и анотиран метод? 8 | 6. За какво служат `@GetMapping`, `@PostMapping`, `@DeleteMapping` и т.н.? 9 | 7. Какво ще стане ако в контролер имаме анотация `@GetMapping` и се опитаме да извикаме съответния endpoint с POST заявка? 10 | 8. Какво представлява body-то на HTTP POST заявката, когато се submit-не една HTML форма." -------------------------------------------------------------------------------- /interview-questions/02-thymeleaf.md: -------------------------------------------------------------------------------- 1 | # Thymeleaf 2 | 3 | 1. Какво представлява и за какво може да използва един templete engine? 4 | 2. Какви алтернативи на Thymeleaf познавате? 5 | 3. Какво е server side rendering? 6 | 4. Какво е natural template? 7 | 5. Каква е разликата между `${..}` и `@{..}` и `*{..}`? 8 | 6. Какво прави th:object? 9 | 7. Как може да се преизползват отделни парчета от template? -------------------------------------------------------------------------------- /interview-questions/03-state.md: -------------------------------------------------------------------------------- 1 | # State 2 | 3 | 1. Какво е сесия? 4 | 2. Как се предава информация за сесията между клиент и сървър? 5 | 3. Как се достъпва информацията за сесията в Spring MVC? 6 | 4. Как се прочита cookie? 7 | 5. Как се създава cookie? 8 | 6. Как се изтрива cookie? 9 | 7. Какво е local storage и session storage? 10 | 8. Как се чете http header? -------------------------------------------------------------------------------- /interview-questions/04-scheduler-events-cache.md: -------------------------------------------------------------------------------- 1 | # Schedulers, events, cache 2 | 3 | 1. Какво представлява един scheduler task? 4 | 2. За какво се използват тези scheduler tasks? 5 | 3. Как се конфигурира един scheduler task в Spring? 6 | 4. Какви типове конфигурации на scheduler tasks познавате? 7 | 5. Каква е разликата между fixed rate и fixed delay? 8 | 6. Какво представлява един event в Spring? 9 | 7. Как се изпращат и как се слуша за events, в т.ч. custom events? 10 | 8. Как се кешира резултата връщан от сървис в Spring? 11 | 9. Как се рефрешват тези кешове? 12 | 10. Какво ще стане ако конфигурирате кеш, но не импортнете spring cache като зависимост? -------------------------------------------------------------------------------- /interview-questions/05-rest-api.md: -------------------------------------------------------------------------------- 1 | # REST API 2 | 3 | 1. Какво е Restful design, посочете някои от основните му принципи. 4 | 2. Какви Rest client-и интегрирани в Spring познавате? 5 | 3. Синхронен ли е RestClient? Веднъж създаден, може ли да се използва безопасно в многонишково приложение? 6 | 4. Как се създава RestClient? 7 | 5. Какво е `@ResponseBody`? 8 | 6. Какво е `@RestController`? 9 | 7. Опишете какво е необходимо за да имплементираме REST API с get/post/put методи? -------------------------------------------------------------------------------- /interview-questions/06-spring-security.md: -------------------------------------------------------------------------------- 1 | # Spring Security 2 | 3 | 1. За какво служи Spring Security? 4 | 2. Каква е разликата между authentication и authorization? 5 | 3. Кои са класовете които трябва да се експротнат при form login? 6 | 4. Защо се налага съществуването на UserDetailsService? 7 | 5. Какво представлява UserDetails? 8 | 6. Как работи PasswordEncoder? 9 | 7. Каква е разликата между failureUrl и failureForwardUrl? 10 | 8. Трябва ли ни собствен endpoint за login/logout? 11 | 9. Защо логаута е POST request? 12 | 10. Какво е CSRF? -------------------------------------------------------------------------------- /interview-questions/07-error-handling.md: -------------------------------------------------------------------------------- 1 | # Error handling 2 | 3 | 1. Какво е white label error page? 4 | 2. Как може да заменим white label error page със собствена страница? 5 | 3. Как може да показваме страница за грешка в зависимост от HTTP status code на response-a? 6 | 4. Как може да обработим грешки специфични за даден контролер? 7 | 5. Как може да обработим грешки специфични за цялото приложение? 8 | 6. Как може да върнем custom JSON при грешка в REST контролер? -------------------------------------------------------------------------------- /interview-questions/08-unit-tests.md: -------------------------------------------------------------------------------- 1 | # Unit tests 2 | 3 | 1. Какво е unit тест? 4 | 2. Какво е mock и защо се налага да го използваме? 5 | 3. Какво е JUnit и Mockito? 6 | 4. Посочете ситуация, която би направила тестването на клас трудно или невъзможно. 7 | 5. Какво е TDD? 8 | 6. Посочете някои нови неща в JUnit 5. 9 | 7. Какво е `@Captor`? 10 | 8. Каква е разликата между `@BeforeEach` и `@BeforeAll`? -------------------------------------------------------------------------------- /interview-questions/09-integration-tests.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | 1. Какво представляват integration тестовете? 4 | 2. Има ли ApplicationContext при изпълнението на integration тестове? 5 | 3. Какво е `@SpringBootTest`? 6 | 4. Какво е `MockMvc`? 7 | 5. Как се конфигурира `MockMvc`? 8 | 6. Какво е `MockBean`? 9 | 7. Какво се случва с тестовия контекст, когато имаме `@MockBean`? 10 | 8. Само един ли е тестовия контекст? -------------------------------------------------------------------------------- /interview-questions/10-aop.md: -------------------------------------------------------------------------------- 1 | # AOP 2 | 3 | 1. Какво е AOP? 4 | 2. Какво е Cross cutting concern? 5 | 3. Какво е Aspect? 6 | 4. Какво е Join point? 7 | 5. Какво е Pointcut? 8 | 6. Какво е Advice? 9 | 7. Как работи AOP в Spring? 10 | 8. Какво е AspectJ и различно ли е от Spring AOP? -------------------------------------------------------------------------------- /interview-questions/11-docker.md: -------------------------------------------------------------------------------- 1 | # Docker and Swagger 2 | 3 | 1. Какво е Docker и за какво се използва? 4 | 2. Какво е image и container? 5 | 3. Къде се пазят докър имиджите? 6 | 4. Кои са двата фичъра на linux, които помагат за контейнеризация? 7 | 5. Как се стартира image? 8 | 6. Какво е необходимо за да се създаде image? 9 | 7. Какво е Swagger и Open API? 10 | 8. Как се генерира OpenAPI в спринг проект? -------------------------------------------------------------------------------- /kafka-consumer/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /kafka-consumer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.2' 4 | id 'io.spring.dependency-management' version '1.1.6' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | toolchain { 12 | languageVersion = JavaLanguageVersion.of(21) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation 'org.springframework.boot:spring-boot-starter-web' 22 | implementation 'org.springframework.kafka:spring-kafka' 23 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 24 | testImplementation 'org.springframework.kafka:spring-kafka-test' 25 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 26 | } 27 | 28 | tasks.named('test') { 29 | useJUnitPlatform() 30 | } 31 | -------------------------------------------------------------------------------- /kafka-consumer/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/kafka-consumer/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /kafka-consumer/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /kafka-consumer/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'kafka-consumer' 2 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/kafka/consumer/ExRatesConsumer.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.consumer; 2 | 3 | import bg.softuni.mobilele.model.dto.ExRateDTO; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ExRatesConsumer { 11 | 12 | public static final String EX_RATES_TOPIC = "ex-rates"; 13 | 14 | private static final Logger LOGGER = LoggerFactory.getLogger(ExRatesConsumer.class); 15 | 16 | @KafkaListener( 17 | topics = EX_RATES_TOPIC, 18 | groupId = "ex-rates-cg-3") 19 | public void listenGroupFoo(ExRateDTO exRateDTO) { 20 | 21 | LOGGER.info("Consumed ex rate {}/{}", 22 | exRateDTO.currency(), 23 | exRateDTO.rate() 24 | ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/kafka/consumer/KafkaConsumerApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.consumer; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class KafkaConsumerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(KafkaConsumerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/mobilele/model/dto/ExRateDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record ExRateDTO(String currency, BigDecimal rate) { 6 | } 7 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | kafka: 3 | bootstrap-servers: localhost:9092 4 | consumer: 5 | key-deserializer: org.apache.kafka.common.serialization.StringDeserializer 6 | value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer 7 | auto-offset-reset: earliest 8 | properties: 9 | spring.json.trusted.packages: '*' 10 | listener: 11 | concurrency: 2 12 | server: 13 | port: 8082 14 | -------------------------------------------------------------------------------- /kafka-consumer/src/test/java/bg/softuni/kafka/consumer/KafkaConsumerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.consumer; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class KafkaConsumerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /local-docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | db: 4 | image: mysql 5 | ports: 6 | - "3306:3306" 7 | command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_bin'] 8 | environment: 9 | - MYSQL_ALLOW_EMPTY_PASSWORD="yes" 10 | - MYSQL_DATABASE=mobilele 11 | mailhog: 12 | image: mailhog/mailhog 13 | logging: 14 | driver: 'none' # disable saving logs 15 | ports: 16 | - "1025:1025" # smtp server 17 | - "8025:8025" # web ui 18 | # redis: 19 | # image: redis 20 | # ports: 21 | # - "6379:6379" 22 | promtool: 23 | image: prom/prometheus:v2.43.0 24 | ports: 25 | - 9090:9090 26 | volumes: 27 | - type: bind 28 | source: ./prometheus.yaml 29 | target: /etc/prometheus/prometheus.yml 30 | grafana: 31 | image: grafana/grafana:8.5.22 32 | ports: 33 | - 3000:3000 34 | zookeeper: 35 | image: confluentinc/cp-zookeeper 36 | environment: 37 | ZOOKEEPER_CLIENT_PORT: 2181 38 | ZOOKEEPER_TICK_TIME: 2000 39 | ports: 40 | - 2181:2181 41 | kafka: 42 | image: confluentinc/cp-kafka 43 | depends_on: 44 | - zookeeper 45 | ports: 46 | - 9092:9092 47 | environment: 48 | KAFKA_BROKER_ID: 1 49 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 50 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT_HOST://localhost:9092,PLAINTEXT://kafka:29092 51 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 52 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 53 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 -------------------------------------------------------------------------------- /local-docker/prometheus.yaml: -------------------------------------------------------------------------------- 1 | # check sample config here 2 | # https://github.com/prometheus/prometheus/blob/release-2.1/config/testdata/conf.good.yml 3 | 4 | # my global config 5 | global: 6 | scrape_interval: 5s 7 | evaluation_interval: 15s 8 | 9 | scrape_configs: 10 | - job_name: rest-service 11 | 12 | honor_labels: true 13 | # scrape_interval is defined by the configured global (15s). 14 | # scrape_timeout is defined by the global default (10s). 15 | 16 | # metrics_path defaults to '/metrics' 17 | # scheme defaults to 'http'. 18 | 19 | metrics_path: /actuator/prometheus 20 | scheme: http 21 | 22 | static_configs: 23 | - targets: ['host.docker.internal:8082'] -------------------------------------------------------------------------------- /mobilele-offers/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /mobilele-offers/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.1' 4 | id 'io.spring.dependency-management' version '1.1.5' 5 | } 6 | 7 | group = 'bg.softuni.mobilele' 8 | 9 | java { 10 | toolchain { 11 | languageVersion = JavaLanguageVersion.of(21) 12 | } 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 21 | implementation 'org.springframework.boot:spring-boot-starter-web' 22 | implementation 'org.springframework.boot:spring-boot-starter-validation' 23 | 24 | implementation 'org.springframework.boot:spring-boot-starter-security' 25 | 26 | implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' 27 | 28 | implementation 'io.jsonwebtoken:jjwt-api:0.11.5' 29 | implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' 30 | implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' 31 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 32 | runtimeOnly 'io.micrometer:micrometer-registry-prometheus' 33 | 34 | runtimeOnly 'com.mysql:mysql-connector-j' 35 | 36 | testImplementation 'org.testcontainers:junit-jupiter' 37 | testImplementation 'org.springframework.boot:spring-boot-testcontainers' 38 | testImplementation 'org.testcontainers:mysql' 39 | 40 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 41 | testImplementation 'org.springframework.security:spring-security-test' 42 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 43 | } 44 | 45 | tasks.named('test') { 46 | useJUnitPlatform() 47 | } 48 | -------------------------------------------------------------------------------- /mobilele-offers/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:21-alpine 2 | 3 | COPY build/libs/mobilele-offers.jar app.jar 4 | 5 | ENTRYPOINT ["java", "-jar", "/app.jar"] -------------------------------------------------------------------------------- /mobilele-offers/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/mobilele-offers/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mobilele-offers/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /mobilele-offers/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mobilele-offers' 2 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/MobileleOffersApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 7 | 8 | @SpringBootApplication 9 | @EnableMethodSecurity 10 | @EnableScheduling 11 | public class MobileleOffersApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(MobileleOffersApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/config/OpenAPIConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Contact; 6 | import io.swagger.v3.oas.models.info.Info; 7 | import io.swagger.v3.oas.models.security.SecurityScheme; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class OpenAPIConfig { 13 | 14 | @Bean 15 | public OpenAPI customOpenAPI() { 16 | OpenAPI openAPI = new OpenAPI(); 17 | 18 | openAPI.components( 19 | new Components() 20 | .addSecuritySchemes("bearer-token", 21 | new SecurityScheme() 22 | .type(SecurityScheme.Type.HTTP) 23 | .scheme("bearer") 24 | .bearerFormat("JWT") 25 | )); 26 | 27 | openAPI.setInfo( 28 | new Info() 29 | .description("This is the mobilele offer micro service.") 30 | .title("Mobilele offers API") 31 | .version("0.0.1") 32 | .contact( 33 | new Contact() 34 | .name("Pehso ot softuni") 35 | .email("pesho@softuni.com") 36 | ) 37 | ); 38 | 39 | return openAPI; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/model/dto/AddOfferDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.model.dto; 2 | 3 | import bg.softuni.mobilele.offers.model.enums.EngineTypeEnum; 4 | 5 | public record AddOfferDTO( 6 | String description, 7 | Integer mileage, 8 | Integer price, 9 | EngineTypeEnum engineType 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/model/dto/OfferDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.model.dto; 2 | 3 | import bg.softuni.mobilele.offers.model.enums.EngineTypeEnum; 4 | 5 | public record OfferDTO( 6 | Long id, 7 | String description, 8 | Integer mileage, 9 | Integer price, 10 | EngineTypeEnum engineType 11 | ) { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/model/entity/OfferEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.model.entity; 2 | 3 | import bg.softuni.mobilele.offers.model.enums.EngineTypeEnum; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.EnumType; 7 | import jakarta.persistence.Enumerated; 8 | import jakarta.persistence.GeneratedValue; 9 | import jakarta.persistence.GenerationType; 10 | import jakarta.persistence.Id; 11 | import jakarta.persistence.Table; 12 | import jakarta.validation.constraints.NotEmpty; 13 | import jakarta.validation.constraints.NotNull; 14 | import jakarta.validation.constraints.Positive; 15 | import java.time.Instant; 16 | 17 | @Entity 18 | @Table(name = "offers") 19 | public class OfferEntity { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | 24 | @NotEmpty 25 | @Column(columnDefinition = "TEXT")//MYSQL Specific!!!!!, demonstrates test containers 26 | private String description; 27 | @Positive 28 | private Integer mileage; 29 | 30 | @Positive 31 | private int price; 32 | 33 | @Enumerated(EnumType.STRING) 34 | private EngineTypeEnum engine; 35 | @NotNull 36 | @Column 37 | private Instant created = Instant.now(); 38 | 39 | public Integer getMileage() { 40 | return mileage; 41 | } 42 | 43 | public OfferEntity setMileage(Integer mileage) { 44 | this.mileage = mileage; 45 | return this; 46 | } 47 | 48 | public String getDescription() { 49 | return description; 50 | } 51 | 52 | public OfferEntity setDescription(String description) { 53 | this.description = description; 54 | return this; 55 | } 56 | 57 | public EngineTypeEnum getEngine() { 58 | return engine; 59 | } 60 | 61 | public OfferEntity setEngine(EngineTypeEnum engine) { 62 | this.engine = engine; 63 | return this; 64 | } 65 | 66 | public int getPrice() { 67 | return price; 68 | } 69 | 70 | public OfferEntity setPrice(int price) { 71 | this.price = price; 72 | return this; 73 | } 74 | 75 | public Long getId() { 76 | return id; 77 | } 78 | 79 | public OfferEntity setId(Long id) { 80 | this.id = id; 81 | return this; 82 | } 83 | 84 | public Instant getCreated() { 85 | return created; 86 | } 87 | 88 | public OfferEntity setCreated(Instant created) { 89 | this.created = created; 90 | return this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/model/enums/EngineTypeEnum.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.model.enums; 2 | 3 | public enum EngineTypeEnum { 4 | PETROL, 5 | DIESEL, 6 | ELECTRIC, 7 | HYBRID 8 | } 9 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/repository/OfferRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.repository; 2 | 3 | import bg.softuni.mobilele.offers.model.entity.OfferEntity; 4 | import java.time.Instant; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.stereotype.Repository; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | @Repository 13 | public interface OfferRepository extends JpaRepository { 14 | 15 | @Transactional 16 | @Modifying 17 | @Query("DELETE FROM OfferEntity o WHERE o.created < :olderThan") 18 | void deleteOldOffers(Instant olderThan); 19 | } 20 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/security/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.security; 2 | 3 | import bg.softuni.mobilele.offers.service.JwtService; 4 | import jakarta.servlet.FilterChain; 5 | import jakarta.servlet.ServletException; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.context.SecurityContext; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.filter.OncePerRequestFilter; 15 | 16 | @Component 17 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 18 | 19 | private final JwtService jwtService; 20 | 21 | public JwtAuthenticationFilter(JwtService jwtService) { 22 | this.jwtService = jwtService; 23 | } 24 | 25 | @Override 26 | protected void doFilterInternal( 27 | HttpServletRequest request, 28 | HttpServletResponse response, 29 | FilterChain filterChain) throws ServletException, IOException { 30 | 31 | final String authHeader = request.getHeader("Authorization"); 32 | 33 | //Authorization: Bearer 34 | if (authHeader == null || 35 | authHeader.isBlank() || 36 | !authHeader.startsWith("Bearer ") 37 | ) { 38 | filterChain.doFilter(request, response); 39 | return; 40 | } 41 | 42 | String jwtToken = authHeader.substring(7); 43 | 44 | UserDetails userDetails = jwtService.extractUserInformation(jwtToken); 45 | 46 | if (SecurityContextHolder.getContext().getAuthentication() == null) { 47 | SecurityContext context = SecurityContextHolder.getContext(); 48 | 49 | UsernamePasswordAuthenticationToken authenticationToken = 50 | new UsernamePasswordAuthenticationToken( 51 | userDetails, 52 | "", 53 | userDetails.getAuthorities() 54 | ); 55 | context.setAuthentication(authenticationToken); 56 | SecurityContextHolder.setContext(context); 57 | } 58 | 59 | filterChain.doFilter(request, response); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.security; 2 | 3 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AuthenticationProvider; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.AuthenticationException; 12 | import org.springframework.security.web.SecurityFilterChain; 13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 | 15 | @Configuration 16 | public class SecurityConfig { 17 | 18 | @Bean 19 | public SecurityFilterChain securityFilterChain( 20 | HttpSecurity httpSecurity, 21 | JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { 22 | return httpSecurity 23 | .csrf(AbstractHttpConfigurer::disable) 24 | .authorizeHttpRequests( 25 | authorize -> 26 | authorize 27 | .requestMatchers(HttpMethod.GET, "/offers/**", "/swagger-ui/**", "swagger-ui.html", "/v3/api-docs/**").permitAll() 28 | .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() 29 | .anyRequest().authenticated() 30 | ) 31 | .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) 32 | .build(); 33 | 34 | } 35 | 36 | @Bean 37 | public AuthenticationProvider noopAuthenticationProvider() { 38 | return new AuthenticationProvider() { 39 | @Override 40 | public Authentication authenticate(Authentication authentication) 41 | throws AuthenticationException { 42 | return null; 43 | } 44 | 45 | @Override 46 | public boolean supports(Class authentication) { 47 | return false; 48 | } 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/JwtService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | 5 | public interface JwtService { 6 | UserDetails extractUserInformation(String jwtToken); 7 | } 8 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/MonitoringService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service; 2 | 3 | public interface MonitoringService { 4 | 5 | void increaseOfferSearches(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/OfferService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service; 2 | 3 | import bg.softuni.mobilele.offers.model.dto.AddOfferDTO; 4 | import bg.softuni.mobilele.offers.model.dto.OfferDTO; 5 | import java.util.List; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.web.PagedModel; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | 11 | public interface OfferService { 12 | 13 | OfferDTO createOffer(AddOfferDTO addOfferDTO); 14 | 15 | void deleteOffer(UserDetails userDetails, Long offerId); 16 | 17 | OfferDTO getOfferById(Long id); 18 | 19 | PagedModel getAllOffers(Pageable pageable); 20 | 21 | void cleanupOldOffers(); 22 | 23 | boolean isOwner(UserDetails userDetails, Long offerId); 24 | } 25 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/RetentionScheduler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service; 2 | 3 | import bg.softuni.mobilele.offers.repository.OfferRepository; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.scheduling.annotation.Scheduled; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RetentionScheduler { 11 | 12 | private final Logger LOGGER = LoggerFactory.getLogger(RetentionScheduler.class); 13 | private final OfferService offerService; 14 | 15 | public RetentionScheduler(OfferService offerService) { 16 | this.offerService = offerService; 17 | } 18 | 19 | @Scheduled(cron = "0 0 2 * * SAT") 20 | public void deleteOldRecords() { 21 | LOGGER.info("Start deletion of old objects."); 22 | offerService.cleanupOldOffers(); 23 | LOGGER.info("Start deletion finished."); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/exception/ObjectNotFoundException.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(code = HttpStatus.NOT_FOUND) 7 | public class ObjectNotFoundException extends RuntimeException{ 8 | } 9 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/impl/JwtServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service.impl; 2 | 3 | import bg.softuni.mobilele.offers.service.JwtService; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.security.Keys; 7 | import java.nio.charset.StandardCharsets; 8 | import java.security.Key; 9 | import java.util.List; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.core.userdetails.User; 13 | import org.springframework.security.core.userdetails.UserDetails; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | public class JwtServiceImpl implements JwtService { 18 | 19 | private final String jwtSecret; 20 | 21 | public JwtServiceImpl(@Value("${jwt.secret}") String jwtSecret) { 22 | this.jwtSecret = jwtSecret; 23 | } 24 | 25 | @Override 26 | public UserDetails extractUserInformation(String jwtToken) { 27 | Claims claims = extractClaims(jwtToken); 28 | 29 | String userName = getUserName(claims); 30 | List roles = getRoles(claims); 31 | 32 | return new User(userName, "", roles 33 | .stream() 34 | .map(SimpleGrantedAuthority::new) 35 | .toList() 36 | ); 37 | } 38 | 39 | @SuppressWarnings("unchecked") 40 | private static List getRoles(Claims claims) { 41 | return claims.get("roles", List.class); 42 | } 43 | 44 | private static String getUserName(Claims claims) { 45 | return claims.getSubject(); 46 | } 47 | 48 | private Claims extractClaims(String jwtToken) { 49 | return Jwts 50 | .parserBuilder() 51 | .setSigningKey(getSigningKey()) 52 | .build() 53 | .parseClaimsJws(jwtToken) 54 | .getBody(); 55 | } 56 | 57 | private Key getSigningKey() { 58 | byte[] keyBytes = jwtSecret.getBytes(StandardCharsets.UTF_8); 59 | return Keys.hmacShaKeyFor(keyBytes); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/java/bg/softuni/mobilele/offers/service/impl/MonitoringServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers.service.impl; 2 | 3 | import bg.softuni.mobilele.offers.service.MonitoringService; 4 | import io.micrometer.core.instrument.Counter; 5 | import io.micrometer.core.instrument.MeterRegistry; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class MonitoringServiceImpl implements MonitoringService { 10 | 11 | private final Counter offerSearchCounter; 12 | 13 | public MonitoringServiceImpl(MeterRegistry meterRegistry) { 14 | 15 | offerSearchCounter = Counter 16 | .builder("offer.searches") 17 | .description("How many times offers were searched.") 18 | .register(meterRegistry); 19 | } 20 | 21 | @Override 22 | public void increaseOfferSearches() { 23 | offerSearchCounter.increment(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mobilele-offers/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mobilele 4 | datasource: 5 | url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/offers?allowPublicKeyRetrieval=true&useSSL=false&createDatabaseIfNotExist=true&serverTimezone=UTC 6 | username: ${MYSQL_USER:root} 7 | password: ${MYSQL_PASSWORD:} 8 | jpa: 9 | defer-datasource-initialization: true 10 | properties: 11 | hibernate: 12 | format_sql: true 13 | hibernate: 14 | ddl-auto: update 15 | sql: 16 | init: 17 | mode: never 18 | 19 | server: 20 | port: 8081 21 | 22 | management: 23 | endpoints: 24 | web: 25 | exposure: 26 | include: "*" 27 | server: 28 | port: 8082 29 | 30 | 31 | logging: 32 | level: 33 | org.hibernate.SQL: DEBUG 34 | org.hibernate.orm.jdbc.bind: TRACE 35 | 36 | offers.retention.period: P300D 37 | 38 | jwt: 39 | secret: ${JWT_KEY:WSdv1kEE1tCT1a8ihRSqfwMNzlPBq8IWSdv1kEE1tCT1a8ihRSqfwMNzlPBq8IWSdv1kEE1tCT1a8ihRSqfwMNzlPBq8I} -------------------------------------------------------------------------------- /mobilele-offers/src/test/java/bg/softuni/mobilele/offers/MobileleOffersApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.offers; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MobileleOffersApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mobilele-offers/src/test/requests.http: -------------------------------------------------------------------------------- 1 | ### Send POST request with json body 2 | POST http://localhost:8081/offers 3 | Content-Type: application/json 4 | 5 | { 6 | "description": "Test offer 2", 7 | "mileage": 20000, 8 | "price": 220, 9 | "engineType": "DIESEL" 10 | } -------------------------------------------------------------------------------- /mobilele-offers/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mobilele 4 | jpa: 5 | defer-datasource-initialization: true 6 | properties: 7 | hibernate: 8 | format_sql: true 9 | hibernate: 10 | ddl-auto: update 11 | sql: 12 | init: 13 | mode: never 14 | 15 | 16 | jwt: 17 | secret: test -------------------------------------------------------------------------------- /mobilele-offers/src/test/resources/sample-create-offer.json: -------------------------------------------------------------------------------- 1 | { 2 | "description" : "Test offer", 3 | "mileage" : "10000", 4 | "price" : 120.20, 5 | "engineType" : "PETROL" 6 | } -------------------------------------------------------------------------------- /mobilele/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /mobilele/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.1' 4 | id 'io.spring.dependency-management' version '1.1.4' 5 | } 6 | 7 | group = 'bg.softuni' 8 | 9 | java { 10 | sourceCompatibility = '21' 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 19 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | //developmentOnly 'org.springframework.boot:spring-boot-devtools' 22 | implementation 'org.springframework.boot:spring-boot-starter-validation' 23 | implementation 'org.springframework.boot:spring-boot-starter-security' 24 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 25 | implementation 'org.springframework.kafka:spring-kafka' 26 | implementation 'org.modelmapper:modelmapper:3.2.0' 27 | 28 | implementation 'io.jsonwebtoken:jjwt-api:0.11.5' 29 | implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' 30 | implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' 31 | 32 | runtimeOnly 'com.mysql:mysql-connector-j' 33 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 34 | testImplementation 'org.springframework.security:spring-security-test' 35 | testImplementation 'com.maciejwalkowiak.spring:wiremock-spring-boot:2.1.2' 36 | 37 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 38 | testRuntimeOnly 'org.hsqldb:hsqldb' 39 | } 40 | 41 | tasks.named('test') { 42 | useJUnitPlatform() 43 | } 44 | -------------------------------------------------------------------------------- /mobilele/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:21-alpine 2 | 3 | COPY build/libs/mobilele.jar app.jar 4 | 5 | ENTRYPOINT ["java", "-jar", "/app.jar"] -------------------------------------------------------------------------------- /mobilele/docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: mysql 4 | ports: 5 | - "3306:3306" 6 | command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_bin'] 7 | environment: 8 | - MYSQL_ALLOW_EMPTY_PASSWORD="yes" 9 | healthcheck: 10 | test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] 11 | mobilele: 12 | depends_on: [db] 13 | image: luchob/mobilele:v3 14 | ports: 15 | - "8080:8080" 16 | environment: 17 | - MYSQL_HOST=db 18 | - MYSQL_PORT=3306 19 | - MYSQL_USER=root 20 | - FOREX_API_KEY=8a56ae16086e4551af068061afeadb07 21 | - INIT_EX_RATES=true 22 | - OFFERS_BASE_URL=http://mobilele-offers:8081 23 | - JWT_KEY=WSdv1kEE1tCT1a8ihRSqfwMNzlPBq8IWSdv1kEE1tCT1a8ihRSqfwMNzlPBq8IWSdv1kEE1tCT1a8ihRSqfwMNzlPBq8I 24 | - JWT_EXPIRATION=60000 25 | mobilele-offers: 26 | depends_on: [mobilele] 27 | image: luchob/mobilele-offers:v2 28 | ports: 29 | - "8081:8081" 30 | environment: 31 | - MYSQL_HOST=db 32 | - MYSQL_PORT=3306 33 | - MYSQL_USER=root 34 | -------------------------------------------------------------------------------- /mobilele/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/mobilele/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mobilele/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /mobilele/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mobilele' 2 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/MobileleApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.thymeleaf.TemplateEngine; 6 | 7 | @SpringBootApplication 8 | public class MobileleApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(MobileleApplication.class, args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import bg.softuni.mobilele.repository.UserRoleRepository; 4 | import javax.sql.DataSource; 5 | import org.modelmapper.ModelMapper; 6 | import org.springframework.context.MessageSource; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.support.ReloadableResourceBundleMessageSource; 10 | import org.springframework.core.io.ResourceLoader; 11 | import org.springframework.jdbc.datasource.init.DataSourceInitializer; 12 | import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; 15 | 16 | @Configuration 17 | public class AppConfig { 18 | 19 | @Bean 20 | public ModelMapper modelMapper() { 21 | return new ModelMapper(); 22 | } 23 | 24 | @Bean 25 | public DataSourceInitializer dataSourceInitializer(DataSource dataSource, 26 | UserRoleRepository userRoleRepository, 27 | ResourceLoader resourceLoader) { 28 | DataSourceInitializer initializer = new DataSourceInitializer(); 29 | initializer.setDataSource(dataSource); 30 | 31 | if (userRoleRepository.count() == 0) { 32 | ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); 33 | populator.addScript(resourceLoader.getResource("classpath:data.sql")); 34 | initializer.setDatabasePopulator(populator); 35 | } 36 | 37 | return initializer; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/ForexApiConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | @ConfigurationProperties(prefix = "forex.api") 10 | public class ForexApiConfig { 11 | 12 | private String key; 13 | private String url; 14 | private String base; 15 | 16 | public String getKey() { 17 | return key; 18 | } 19 | 20 | public ForexApiConfig setKey(String key) { 21 | this.key = key; 22 | return this; 23 | } 24 | 25 | public String getUrl() { 26 | return url; 27 | } 28 | 29 | public ForexApiConfig setUrl(String url) { 30 | this.url = url; 31 | return this; 32 | } 33 | 34 | public String getBase() { 35 | return base; 36 | } 37 | 38 | public ForexApiConfig setBase(String base) { 39 | this.base = base; 40 | return this; 41 | } 42 | 43 | @PostConstruct 44 | public void checkConfiguration() { 45 | 46 | verifyNotNullOrEmpty("key", key); 47 | verifyNotNullOrEmpty("base", base); 48 | verifyNotNullOrEmpty("url", url); 49 | 50 | if (!"USD".equals(base)) { 51 | throw new IllegalStateException("Sorry, but the free API does not support base, " 52 | + "currencies different than USD."); 53 | } 54 | 55 | 56 | } 57 | 58 | private static void verifyNotNullOrEmpty(String name, String value) { 59 | if (value == null || value.isBlank()) { 60 | throw new IllegalArgumentException("Property " + name + " cannot be empty."); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/I18NConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import java.util.Locale; 4 | import org.springframework.context.MessageSource; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.support.ReloadableResourceBundleMessageSource; 8 | import org.springframework.web.servlet.LocaleResolver; 9 | import org.springframework.web.servlet.i18n.CookieLocaleResolver; 10 | import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 11 | 12 | @Configuration 13 | public class I18NConfig { 14 | 15 | @Bean 16 | public LocaleResolver localeResolver() { 17 | return new CookieLocaleResolver("lang"); 18 | } 19 | 20 | @Bean 21 | public LocaleChangeInterceptor localeChangeInterceptor() { 22 | LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); 23 | localeChangeInterceptor.setParamName("lang"); 24 | return localeChangeInterceptor; 25 | } 26 | 27 | @Bean 28 | public MessageSource messageSource() { 29 | ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 30 | messageSource.setBasename("classpath:i18n/messages"); 31 | messageSource.setDefaultEncoding("UTF-8"); 32 | messageSource.setDefaultLocale(Locale.ENGLISH); 33 | return messageSource; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import org.apache.kafka.clients.admin.NewTopic; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.kafka.config.TopicBuilder; 7 | 8 | @Configuration 9 | public class KafkaConfig { 10 | 11 | public static final String EX_RATES_TOPIC = "ex-rates"; 12 | 13 | @Bean 14 | public NewTopic exRatesTopic() { 15 | return TopicBuilder.name(EX_RATES_TOPIC) 16 | .partitions(2) 17 | .compact() 18 | .build(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/OfferApiConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ConfigurationProperties(prefix = "offers.api") 8 | public class OfferApiConfig { 9 | private String baseUrl; 10 | 11 | public String getBaseUrl() { 12 | return baseUrl; 13 | } 14 | 15 | public OfferApiConfig setBaseUrl(String baseUrl) { 16 | this.baseUrl = baseUrl; 17 | return this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/RestConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import bg.softuni.mobilele.service.JwtService; 4 | import bg.softuni.mobilele.service.UserService; 5 | import java.util.Map; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.client.ClientHttpRequest; 10 | import org.springframework.http.client.ClientHttpRequestInitializer; 11 | import org.springframework.http.client.ClientHttpRequestInterceptor; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.web.client.RestClient; 14 | 15 | @Configuration 16 | public class RestConfig { 17 | 18 | @Bean("genericRestClient") 19 | public RestClient genericRestClient() { 20 | return RestClient.create(); 21 | } 22 | 23 | @Bean("offersRestClient") 24 | public RestClient offersRestClient(OfferApiConfig offersApiConfig, 25 | ClientHttpRequestInterceptor requestInterceptor) { 26 | return RestClient 27 | .builder() 28 | .baseUrl(offersApiConfig.getBaseUrl()) 29 | .defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) 30 | .requestInterceptor(requestInterceptor) 31 | .build(); 32 | } 33 | 34 | @Bean 35 | public ClientHttpRequestInterceptor requestInterceptor(UserService userService, 36 | JwtService jwtService) { 37 | return (r, b, e) -> { 38 | // put the logged user details into bearer token 39 | userService 40 | .getCurrentUser() 41 | .ifPresent(mud -> { 42 | String bearerToken = jwtService.generateToken( 43 | mud.getUuid().toString(),// 44 | Map.of( 45 | "roles", 46 | mud.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList() 47 | ) 48 | ); 49 | 50 | System.out.println("BEARER TOKEN: " + bearerToken); 51 | 52 | r.getHeaders().setBearerAuth(bearerToken); 53 | }); 54 | 55 | return e.execute(r, b); 56 | }; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 7 | 8 | @Configuration 9 | public class WebConfig implements WebMvcConfigurer { 10 | 11 | private final LocaleChangeInterceptor localeChangeInterceptor; 12 | 13 | public WebConfig(LocaleChangeInterceptor localeChangeInterceptor) { 14 | this.localeChangeInterceptor = localeChangeInterceptor; 15 | } 16 | 17 | @Override 18 | public void addInterceptors(InterceptorRegistry registry) { 19 | registry.addInterceptor(localeChangeInterceptor); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/init/ExRatesPublisher.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.init; 2 | 3 | import bg.softuni.mobilele.service.ExRateService; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Order(1) 9 | @Component 10 | public class ExRatesPublisher implements CommandLineRunner { 11 | 12 | private final ExRateService exRateService; 13 | 14 | public ExRatesPublisher(ExRateService exRateService) { 15 | this.exRateService = exRateService; 16 | } 17 | 18 | @Override 19 | public void run(String... args) throws Exception { 20 | exRateService.publishExRates(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/init/ExchangeRateInitializer.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.init; 2 | 3 | import bg.softuni.mobilele.service.ExRateService; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Order(0) 10 | @Component 11 | @ConditionalOnProperty(name = "forex.init-ex-rates", havingValue = "true") 12 | public class ExchangeRateInitializer implements CommandLineRunner { 13 | 14 | private final ExRateService exRateService; 15 | 16 | public ExchangeRateInitializer(ExRateService exRateService) { 17 | this.exRateService = exRateService; 18 | } 19 | 20 | @Override 21 | public void run(String... args) throws Exception { 22 | if (!exRateService.hasInitializedExRates()) { 23 | exRateService.updateRates( 24 | exRateService.fetchExRates() 25 | ); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/AddOfferDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import bg.softuni.mobilele.model.enums.EngineTypeEnum; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.PositiveOrZero; 7 | import jakarta.validation.constraints.Size; 8 | 9 | public record AddOfferDTO( 10 | @NotNull(message = "{add.offer.description.length}") 11 | @Size(message = "{add.offer.description.length}", 12 | min = 5, 13 | max = 500) String description,//not necessarily from message source 14 | @NotNull @PositiveOrZero Integer mileage, 15 | @NotNull @PositiveOrZero Integer price, 16 | @NotNull EngineTypeEnum engineType 17 | ) { 18 | 19 | public static AddOfferDTO empty() { 20 | return new AddOfferDTO(null, null, null, null); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/ConversionResultDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record ConversionResultDTO(String from, String to, BigDecimal amount, BigDecimal result) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/ExRateDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record ExRateDTO(String currency, BigDecimal rate) { 6 | } 7 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/ExRatesDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Map; 5 | 6 | public record ExRatesDTO(String base, Map rates) { 7 | /* 8 | { 9 | "base": "USD", 10 | "rates": { 11 | ... 12 | "BGN": 1.8266, 13 | .... 14 | "EUR": 0.934216, 15 | ... 16 | } 17 | } 18 | */ 19 | } 20 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/OfferDetailsDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import bg.softuni.mobilele.model.enums.EngineTypeEnum; 4 | import java.util.List; 5 | 6 | public record OfferDetailsDTO(Long id, 7 | String description, 8 | Integer mileage, 9 | Integer price, 10 | EngineTypeEnum engineType, 11 | List allCurrencies) { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/OfferSummaryDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import bg.softuni.mobilele.model.enums.EngineTypeEnum; 4 | 5 | public record OfferSummaryDTO(Long id, 6 | String description, 7 | Integer mileage, 8 | EngineTypeEnum engineType) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/UserLoginDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | public record UserLoginDTO(String email, String password) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/dto/UserRegistrationDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.dto; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.Size; 6 | 7 | public class UserRegistrationDTO { 8 | @NotEmpty 9 | @Size(min = 5, max = 20) 10 | private String firstName; 11 | @NotEmpty 12 | @Size(min = 5, max = 20) 13 | private String lastName; 14 | @NotEmpty 15 | private String password; 16 | @NotEmpty 17 | @Email 18 | private String email; 19 | public String getFirstName() { 20 | return firstName; 21 | } 22 | 23 | public UserRegistrationDTO setFirstName(String firstName) { 24 | this.firstName = firstName; 25 | return this; 26 | } 27 | 28 | public String getLastName() { 29 | return lastName; 30 | } 31 | 32 | public UserRegistrationDTO setLastName(String lastName) { 33 | this.lastName = lastName; 34 | return this; 35 | } 36 | 37 | public String getPassword() { 38 | return password; 39 | } 40 | 41 | public UserRegistrationDTO setPassword(String password) { 42 | this.password = password; 43 | return this; 44 | } 45 | 46 | public String getEmail() { 47 | return email; 48 | } 49 | 50 | public UserRegistrationDTO setEmail(String email) { 51 | this.email = email; 52 | return this; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "UserRegistrationDTO{" + 58 | "firstName='" + firstName + '\'' + 59 | ", lastName='" + lastName + '\'' + 60 | ", password='" + (password == null ? "N/A" : "[PROVIDED]") + '\'' + 61 | ", email='" + email + '\'' + 62 | '}'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.GeneratedValue; 4 | import jakarta.persistence.GenerationType; 5 | import jakarta.persistence.Id; 6 | import jakarta.persistence.MappedSuperclass; 7 | 8 | @MappedSuperclass 9 | public class BaseEntity { 10 | 11 | @Id 12 | @GeneratedValue(strategy = GenerationType.IDENTITY) 13 | private Long id; 14 | 15 | public Long getId() { 16 | return id; 17 | } 18 | 19 | public BaseEntity setId(Long id) { 20 | this.id = id; 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/ExRateEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.Table; 6 | import jakarta.validation.constraints.NotEmpty; 7 | import jakarta.validation.constraints.NotNull; 8 | import jakarta.validation.constraints.Positive; 9 | import java.math.BigDecimal; 10 | 11 | @Entity 12 | @Table(name = "ex_rates") 13 | public class ExRateEntity extends BaseEntity { 14 | 15 | @NotEmpty 16 | @Column(unique = true) 17 | private String currency; 18 | 19 | @Positive 20 | @NotNull 21 | private BigDecimal rate; 22 | 23 | public String getCurrency() { 24 | return currency; 25 | } 26 | 27 | public ExRateEntity setCurrency(String currency) { 28 | this.currency = currency; 29 | return this; 30 | } 31 | 32 | public BigDecimal getRate() { 33 | return rate; 34 | } 35 | 36 | public ExRateEntity setRate(BigDecimal rate) { 37 | this.rate = rate; 38 | return this; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "ExRateEntity{" + 44 | "currency='" + currency + '\'' + 45 | ", rate=" + rate + 46 | '}'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/OfferEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | import bg.softuni.mobilele.model.enums.EngineTypeEnum; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.EnumType; 6 | import jakarta.persistence.Enumerated; 7 | import jakarta.persistence.GeneratedValue; 8 | import jakarta.persistence.GenerationType; 9 | import jakarta.persistence.Id; 10 | import jakarta.persistence.Table; 11 | import jakarta.validation.constraints.NotEmpty; 12 | import jakarta.validation.constraints.Positive; 13 | 14 | @Entity 15 | @Table(name = "offers") 16 | public class OfferEntity extends BaseEntity { 17 | 18 | @NotEmpty 19 | private String description; 20 | 21 | @Positive 22 | private Integer mileage; 23 | 24 | @Positive 25 | private int price; 26 | 27 | @Enumerated(EnumType.STRING) 28 | private EngineTypeEnum engine; 29 | 30 | public Integer getMileage() { 31 | return mileage; 32 | } 33 | 34 | public OfferEntity setMileage(Integer mileage) { 35 | this.mileage = mileage; 36 | return this; 37 | } 38 | 39 | public String getDescription() { 40 | return description; 41 | } 42 | 43 | public OfferEntity setDescription(String description) { 44 | this.description = description; 45 | return this; 46 | } 47 | 48 | public EngineTypeEnum getEngine() { 49 | return engine; 50 | } 51 | 52 | public OfferEntity setEngine(EngineTypeEnum engine) { 53 | this.engine = engine; 54 | return this; 55 | } 56 | 57 | public int getPrice() { 58 | return price; 59 | } 60 | 61 | public OfferEntity setPrice(int price) { 62 | this.price = price; 63 | return this; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/UUIDSequence.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import org.hibernate.annotations.ValueGenerationType; 9 | 10 | @ValueGenerationType(generatedBy = UUIDSequenceGenerator.class) 11 | @Target({ElementType.METHOD, ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface UUIDSequence { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/UUIDSequenceGenerator.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | import java.util.EnumSet; 4 | import java.util.UUID; 5 | import org.hibernate.engine.spi.SharedSessionContractImplementor; 6 | import org.hibernate.generator.BeforeExecutionGenerator; 7 | import org.hibernate.generator.EventType; 8 | 9 | public class UUIDSequenceGenerator implements BeforeExecutionGenerator { 10 | 11 | @Override 12 | public Object generate( 13 | SharedSessionContractImplementor session, 14 | Object owner, 15 | Object currentValue, 16 | EventType eventType) { 17 | return UUID.randomUUID(); 18 | } 19 | 20 | @Override 21 | public EnumSet getEventTypes() { 22 | return EnumSet.of(EventType.INSERT); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | import static org.hibernate.type.SqlTypes.VARCHAR; 4 | 5 | import jakarta.persistence.Column; 6 | import jakarta.persistence.Entity; 7 | import jakarta.persistence.FetchType; 8 | import jakarta.persistence.JoinColumn; 9 | import jakarta.persistence.JoinTable; 10 | import jakarta.persistence.ManyToMany; 11 | import jakarta.persistence.Table; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.UUID; 15 | import org.hibernate.annotations.JdbcType; 16 | import org.hibernate.annotations.JdbcTypeCode; 17 | import org.hibernate.annotations.UuidGenerator; 18 | 19 | @Entity 20 | @Table(name = "users") 21 | public class UserEntity extends BaseEntity { 22 | @Column(unique = true) 23 | private String email; 24 | @UuidGenerator 25 | //@UUIDSequence <-- applicable for all kind of identifiers 26 | @JdbcTypeCode(VARCHAR) 27 | private UUID uuid; 28 | 29 | private String password; 30 | 31 | private String firstName; 32 | 33 | private String lastName; 34 | 35 | @ManyToMany( 36 | fetch = FetchType.EAGER 37 | ) 38 | @JoinTable( 39 | name = "users_roles", 40 | joinColumns = @JoinColumn(name = "user_id"), 41 | inverseJoinColumns = @JoinColumn(name = "role_id") 42 | ) 43 | private List roles = new ArrayList<>(); 44 | 45 | public String getEmail() { 46 | return email; 47 | } 48 | 49 | public UserEntity setEmail(String email) { 50 | this.email = email; 51 | return this; 52 | } 53 | 54 | public String getPassword() { 55 | return password; 56 | } 57 | 58 | public UserEntity setPassword(String password) { 59 | this.password = password; 60 | return this; 61 | } 62 | 63 | public String getFirstName() { 64 | return firstName; 65 | } 66 | 67 | public UserEntity setFirstName(String firstName) { 68 | this.firstName = firstName; 69 | return this; 70 | } 71 | 72 | public String getLastName() { 73 | return lastName; 74 | } 75 | 76 | public UserEntity setLastName(String lastName) { 77 | this.lastName = lastName; 78 | return this; 79 | } 80 | 81 | public List getRoles() { 82 | return roles; 83 | } 84 | 85 | public UserEntity setRoles(List roles) { 86 | this.roles = roles; 87 | return this; 88 | } 89 | 90 | public UUID getUuid() { 91 | return uuid; 92 | } 93 | 94 | public UserEntity setUuid(UUID uuid) { 95 | this.uuid = uuid; 96 | return this; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "UserEntity{" + 102 | "email='" + email + '\'' + 103 | ", password='" + password + '\'' + 104 | ", firstName='" + firstName + '\'' + 105 | ", lastName='" + lastName + '\'' + 106 | ", roles=" + roles + 107 | '}'; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/entity/UserRoleEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.entity; 2 | 3 | import bg.softuni.mobilele.model.enums.UserRoleEnum; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.EnumType; 7 | import jakarta.persistence.Enumerated; 8 | import jakarta.persistence.GeneratedValue; 9 | import jakarta.persistence.GenerationType; 10 | import jakarta.persistence.Id; 11 | import jakarta.persistence.Table; 12 | import jakarta.validation.constraints.NotNull; 13 | 14 | @Entity 15 | @Table(name = "roles") 16 | public class UserRoleEntity { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | 22 | @NotNull 23 | @Column(unique = true) 24 | @Enumerated(EnumType.STRING) 25 | private UserRoleEnum role; 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public UserRoleEntity setId(Long id) { 32 | this.id = id; 33 | return this; 34 | } 35 | 36 | public UserRoleEnum getRole() { 37 | return role; 38 | } 39 | 40 | public UserRoleEntity setRole(UserRoleEnum role) { 41 | this.role = role; 42 | return this; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/enums/EngineTypeEnum.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.enums; 2 | 3 | public enum EngineTypeEnum { 4 | 5 | PETROL, 6 | DIESEL, 7 | ELECTRIC, 8 | HYBRID 9 | } 10 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/enums/UserRoleEnum.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.enums; 2 | 3 | public enum UserRoleEnum { 4 | ADMIN, 5 | USER 6 | } 7 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/model/user/MobileleUserDetails.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.model.user; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.Collection; 5 | import java.util.UUID; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.userdetails.User; 8 | 9 | public class MobileleUserDetails extends User { 10 | 11 | private final UUID uuid; 12 | private final String firstName; 13 | private final String lastName; 14 | 15 | public MobileleUserDetails( 16 | UUID uuid, 17 | String username, 18 | String password, 19 | Collection authorities, 20 | String firstName, 21 | String lastName 22 | ) { 23 | super(username, password, authorities); 24 | this.uuid = uuid; 25 | this.firstName = firstName; 26 | this.lastName = lastName; 27 | } 28 | 29 | public UUID getUuid() { 30 | return uuid; 31 | } 32 | 33 | public String getFirstName() { 34 | return firstName; 35 | } 36 | 37 | public String getLastName() { 38 | return lastName; 39 | } 40 | 41 | public String getFullName() { 42 | StringBuilder fullName = new StringBuilder(); 43 | if (firstName != null) { 44 | fullName.append(firstName); 45 | } 46 | if (lastName != null) { 47 | if (!fullName.isEmpty()) { 48 | fullName.append(" "); 49 | } 50 | fullName.append(lastName); 51 | } 52 | 53 | return fullName.toString(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/repository/ExRateRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.repository; 2 | 3 | import bg.softuni.mobilele.model.entity.ExRateEntity; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository 9 | public interface ExRateRepository extends JpaRepository { 10 | Optional findByCurrency(String currency); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/repository/OfferRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.repository; 2 | 3 | import bg.softuni.mobilele.model.entity.OfferEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface OfferRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.repository; 2 | 3 | import bg.softuni.mobilele.model.entity.UserEntity; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository 9 | public interface UserRepository extends JpaRepository { 10 | Optional findByEmail(String email); 11 | } 12 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/repository/UserRoleRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.repository; 2 | 3 | import bg.softuni.mobilele.model.entity.UserRoleEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface UserRoleRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/ExRateService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service; 2 | 3 | import bg.softuni.mobilele.model.dto.ExRatesDTO; 4 | import java.math.BigDecimal; 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | public interface ExRateService { 9 | 10 | List allSupportedCurrencies(); 11 | boolean hasInitializedExRates(); 12 | 13 | ExRatesDTO fetchExRates(); 14 | 15 | void updateRates(ExRatesDTO exRatesDTO); 16 | 17 | BigDecimal convert(String from, String to, BigDecimal amount); 18 | 19 | void publishExRates(); 20 | } 21 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/JwtService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service; 2 | 3 | import java.util.Map; 4 | 5 | public interface JwtService { 6 | String generateToken(String userId, Map claims); 7 | } 8 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/KafkaPublicationService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service; 2 | 3 | import bg.softuni.mobilele.model.dto.ExRateDTO; 4 | 5 | public interface KafkaPublicationService { 6 | 7 | void publishExRate(ExRateDTO exRateDTO); 8 | } 9 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/OfferService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service; 2 | 3 | import bg.softuni.mobilele.model.dto.AddOfferDTO; 4 | import bg.softuni.mobilele.model.dto.OfferDetailsDTO; 5 | import bg.softuni.mobilele.model.dto.OfferSummaryDTO; 6 | import java.util.List; 7 | 8 | public interface OfferService { 9 | 10 | void createOffer(AddOfferDTO addOfferDTO); 11 | 12 | void deleteOffer(long offerId); 13 | 14 | OfferDetailsDTO getOfferDetails(Long id); 15 | 16 | List getAllOffersSummary(); 17 | } 18 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/UserService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service; 2 | 3 | import bg.softuni.mobilele.model.dto.UserLoginDTO; 4 | import bg.softuni.mobilele.model.dto.UserRegistrationDTO; 5 | import bg.softuni.mobilele.model.user.MobileleUserDetails; 6 | import java.util.Optional; 7 | 8 | public interface UserService { 9 | 10 | void registerUser(UserRegistrationDTO userRegistration); 11 | 12 | Optional getCurrentUser(); 13 | } 14 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/exception/ApiObjectNotFoundException.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(code = HttpStatus.NOT_FOUND) 7 | public class ApiObjectNotFoundException extends RuntimeException{ 8 | private final Object id; 9 | 10 | public ApiObjectNotFoundException(String message, Object id) { 11 | super(message); 12 | this.id = id; 13 | } 14 | 15 | public Object getId() { 16 | return id; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/exception/ObjectNotFoundException.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(code = HttpStatus.NOT_FOUND) 7 | public class ObjectNotFoundException extends RuntimeException { 8 | 9 | private final Object id; 10 | 11 | public ObjectNotFoundException(String message, Object id) { 12 | super(message); 13 | this.id = id; 14 | } 15 | 16 | public Object getId() { 17 | return id; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/impl/JwtServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import bg.softuni.mobilele.service.JwtService; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import java.nio.charset.StandardCharsets; 8 | import java.security.Key; 9 | import java.util.Date; 10 | import java.util.Map; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Service 15 | public class JwtServiceImpl implements JwtService { 16 | 17 | private final String jwtSecret; 18 | private final long expiration; 19 | 20 | public JwtServiceImpl(@Value("${jwt.secret}") String jwtSecret, 21 | @Value("${jwt.expiration}") long expiration) { 22 | this.jwtSecret = jwtSecret; 23 | this.expiration = expiration; 24 | } 25 | 26 | @Override 27 | public String generateToken(String userId, Map claims) { 28 | var now = new Date(); 29 | 30 | return Jwts 31 | .builder() 32 | .setClaims(claims) 33 | .setSubject(userId) 34 | .setIssuedAt(now) 35 | .setNotBefore(now) 36 | .setExpiration(new Date(now.getTime() + expiration)) 37 | .signWith(getSigningKey(), SignatureAlgorithm.HS256) 38 | .compact(); 39 | } 40 | 41 | private Key getSigningKey() { 42 | byte[] keyBytes = jwtSecret.getBytes(StandardCharsets.UTF_8); 43 | return Keys.hmacShaKeyFor(keyBytes); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/impl/KafkaPublicationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import static bg.softuni.mobilele.config.KafkaConfig.EX_RATES_TOPIC; 4 | 5 | import bg.softuni.mobilele.model.dto.ExRateDTO; 6 | import bg.softuni.mobilele.service.KafkaPublicationService; 7 | import org.apache.kafka.clients.producer.RecordMetadata; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.kafka.core.KafkaTemplate; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class KafkaPublicationServiceImpl implements KafkaPublicationService { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(KafkaPublicationServiceImpl.class); 17 | private final KafkaTemplate kafkaTemplate; 18 | 19 | public KafkaPublicationServiceImpl(KafkaTemplate kafkaTemplate) { 20 | this.kafkaTemplate = kafkaTemplate; 21 | } 22 | 23 | @Override 24 | public void publishExRate(ExRateDTO exRateDTO) { 25 | kafkaTemplate. 26 | send(EX_RATES_TOPIC, exRateDTO.currency(), exRateDTO). 27 | whenComplete( 28 | (res, ex) -> { 29 | if (ex == null) { 30 | RecordMetadata recordMetadata = res.getRecordMetadata(); 31 | LOGGER.info( 32 | "Successfully send key {} to topic/partiotion/offset={}/{}/{}.", 33 | exRateDTO.currency(), 34 | recordMetadata.topic(), 35 | recordMetadata.partition(), 36 | recordMetadata.offset() 37 | ); 38 | } else { 39 | LOGGER.error("Error producing message in kafka with key {}.", 40 | exRateDTO.currency(), 41 | ex); 42 | } 43 | } 44 | ); 45 | 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/impl/MobileleUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import bg.softuni.mobilele.model.entity.UserRoleEntity; 4 | import bg.softuni.mobilele.model.enums.UserRoleEnum; 5 | import bg.softuni.mobilele.model.user.MobileleUserDetails; 6 | import bg.softuni.mobilele.model.entity.UserEntity; 7 | import bg.softuni.mobilele.repository.UserRepository; 8 | import java.time.LocalDateTime; 9 | import java.util.List; 10 | import org.springframework.security.core.GrantedAuthority; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 | 16 | public class MobileleUserDetailsService implements UserDetailsService { 17 | 18 | private final UserRepository userRepository; 19 | 20 | public MobileleUserDetailsService(UserRepository userRepository) { 21 | this.userRepository = userRepository; 22 | } 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(String email) 26 | throws UsernameNotFoundException { 27 | 28 | return userRepository 29 | .findByEmail(email) 30 | .map(MobileleUserDetailsService::map) 31 | .orElseThrow( 32 | () -> new UsernameNotFoundException("User with email " + email + " not found!")); 33 | } 34 | 35 | private static UserDetails map(UserEntity userEntity) { 36 | 37 | return new MobileleUserDetails( 38 | userEntity.getUuid(), 39 | userEntity.getEmail(), 40 | userEntity.getPassword(), 41 | userEntity.getRoles().stream().map(UserRoleEntity::getRole).map(MobileleUserDetailsService::map).toList(), 42 | userEntity.getFirstName(), 43 | userEntity.getLastName() 44 | ); 45 | } 46 | 47 | private static GrantedAuthority map(UserRoleEnum role) { 48 | return new SimpleGrantedAuthority( 49 | "ROLE_" + role 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/impl/OfferServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import bg.softuni.mobilele.model.dto.AddOfferDTO; 4 | import bg.softuni.mobilele.model.dto.ExRatesDTO; 5 | import bg.softuni.mobilele.model.dto.OfferDetailsDTO; 6 | import bg.softuni.mobilele.model.dto.OfferSummaryDTO; 7 | import bg.softuni.mobilele.model.entity.OfferEntity; 8 | import bg.softuni.mobilele.repository.OfferRepository; 9 | import bg.softuni.mobilele.service.ExRateService; 10 | import bg.softuni.mobilele.service.OfferService; 11 | import bg.softuni.mobilele.service.exception.ObjectNotFoundException; 12 | import java.util.List; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Qualifier; 16 | import org.springframework.core.ParameterizedTypeReference; 17 | import org.springframework.http.HttpStatusCode; 18 | import org.springframework.http.MediaType; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.transaction.annotation.Transactional; 21 | import org.springframework.web.client.RestClient; 22 | 23 | @Service 24 | public class OfferServiceImpl implements OfferService { 25 | 26 | private Logger LOGGER = LoggerFactory.getLogger(OfferServiceImpl.class); 27 | private final RestClient offerRestClient; 28 | private final OfferRepository offerRepository; 29 | private final ExRateService exRateService; 30 | 31 | public OfferServiceImpl( 32 | @Qualifier("offersRestClient") RestClient offerRestClient, 33 | OfferRepository offerRepository, 34 | 35 | ExRateService exRateService) { 36 | this.offerRestClient = offerRestClient; 37 | this.offerRepository = offerRepository; 38 | this.exRateService = exRateService; 39 | } 40 | 41 | @Override 42 | public void createOffer(AddOfferDTO addOfferDTO) { 43 | LOGGER.info("Creating new offer..."); 44 | 45 | offerRestClient 46 | .post() 47 | .uri("/offers") 48 | .body(addOfferDTO) 49 | .retrieve(); 50 | } 51 | 52 | @Override 53 | public void deleteOffer(long offerId) { 54 | offerRepository.deleteById(offerId); 55 | } 56 | 57 | @Override 58 | public OfferDetailsDTO getOfferDetails(Long id) { 59 | 60 | return offerRestClient 61 | .get() 62 | .uri("/offers/{id}", id) 63 | .accept(MediaType.APPLICATION_JSON) 64 | .retrieve() 65 | .body(OfferDetailsDTO.class); 66 | } 67 | 68 | @Override 69 | public List getAllOffersSummary() { 70 | LOGGER.info("Get all offers..."); 71 | 72 | return offerRestClient 73 | .get() 74 | .uri("/offers") 75 | .accept(MediaType.APPLICATION_JSON) 76 | .retrieve() 77 | .body(new ParameterizedTypeReference<>(){}); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import bg.softuni.mobilele.model.dto.UserLoginDTO; 4 | import bg.softuni.mobilele.model.dto.UserRegistrationDTO; 5 | import bg.softuni.mobilele.model.entity.UserEntity; 6 | import bg.softuni.mobilele.model.user.MobileleUserDetails; 7 | import bg.softuni.mobilele.repository.UserRepository; 8 | import bg.softuni.mobilele.service.UserService; 9 | import java.util.Optional; 10 | import org.modelmapper.ModelMapper; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | public class UserServiceImpl implements UserService { 18 | 19 | private final ModelMapper modelMapper; 20 | private final PasswordEncoder passwordEncoder; 21 | private final UserRepository userRepository; 22 | 23 | public UserServiceImpl(ModelMapper modelMapper, 24 | PasswordEncoder passwordEncoder, 25 | UserRepository userRepository) { 26 | this.modelMapper = modelMapper; 27 | this.passwordEncoder = passwordEncoder; 28 | this.userRepository = userRepository; 29 | } 30 | 31 | @Override 32 | public void registerUser(UserRegistrationDTO userRegistration) { 33 | userRepository.save(map(userRegistration)); 34 | } 35 | 36 | @Override 37 | public Optional getCurrentUser() { 38 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 39 | if (authentication != null && 40 | authentication.getPrincipal() instanceof MobileleUserDetails mobileleUserDetails) { 41 | return Optional.of(mobileleUserDetails); 42 | } 43 | return Optional.empty(); 44 | } 45 | 46 | private UserEntity map(UserRegistrationDTO userRegistrationDTO) { 47 | UserEntity mappedEntity = modelMapper.map(userRegistrationDTO, UserEntity.class); 48 | 49 | mappedEntity.setPassword(passwordEncoder.encode(userRegistrationDTO.getPassword())); 50 | 51 | return mappedEntity; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/CurrencyController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import bg.softuni.mobilele.model.dto.ConversionResultDTO; 4 | import bg.softuni.mobilele.service.ExRateService; 5 | import bg.softuni.mobilele.service.exception.ApiObjectNotFoundException; 6 | import bg.softuni.mobilele.web.aop.WarnIfExecutionExceeds; 7 | import java.math.BigDecimal; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | import org.springframework.web.bind.annotation.ResponseStatus; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | public class CurrencyController { 19 | 20 | private final ExRateService exRateService; 21 | 22 | public CurrencyController(ExRateService exRateService) { 23 | this.exRateService = exRateService; 24 | } 25 | 26 | @WarnIfExecutionExceeds( 27 | threshold = 800 28 | ) 29 | @GetMapping("/api/convert") 30 | public ResponseEntity convert( 31 | @RequestParam("from") String from, 32 | @RequestParam("to") String to, 33 | @RequestParam("amount") BigDecimal amount 34 | ) { 35 | BigDecimal result = exRateService.convert(from, to, amount); 36 | return ResponseEntity.ok(new ConversionResultDTO( 37 | from, 38 | to, 39 | amount, 40 | result 41 | )); 42 | } 43 | 44 | @ResponseStatus(HttpStatus.NOT_FOUND) 45 | @ExceptionHandler(ApiObjectNotFoundException.class) 46 | @ResponseBody 47 | public NotFoundErrorInfo handleApiObjectNotFoundException(ApiObjectNotFoundException apiObjectNotFoundException) { 48 | return new NotFoundErrorInfo("NOT FOUND", apiObjectNotFoundException.getId()); 49 | } 50 | 51 | 52 | public record NotFoundErrorInfo(String code, Object id) { 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import bg.softuni.mobilele.service.exception.ObjectNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | @ControllerAdvice 11 | public class GlobalExceptionHandler { 12 | 13 | @ResponseStatus(code = HttpStatus.NOT_FOUND) 14 | @ExceptionHandler(ObjectNotFoundException.class) 15 | public ModelAndView handleObjectNotFound(ObjectNotFoundException onfe) { 16 | ModelAndView modelAndView = new ModelAndView("object-not-found"); 17 | modelAndView.addObject("objectId", onfe.getId()); 18 | 19 | return modelAndView; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/HomeController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import bg.softuni.mobilele.model.user.MobileleUserDetails; 4 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | 10 | @Controller 11 | public class HomeController { 12 | 13 | @GetMapping("/") 14 | public String home(@AuthenticationPrincipal UserDetails userDetails, 15 | Model model) { 16 | 17 | if (userDetails instanceof MobileleUserDetails mobileleUserDetails) { 18 | model.addAttribute("welcomeMessage", mobileleUserDetails.getFullName()); 19 | } else { 20 | model.addAttribute("welcomeMessage", "Anonymous"); 21 | } 22 | 23 | return "index"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/LoginController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import bg.softuni.mobilele.model.dto.UserLoginDTO; 4 | import bg.softuni.mobilele.service.UserService; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | @Controller 11 | @RequestMapping("/users") 12 | public class LoginController { 13 | 14 | @GetMapping("/login") 15 | public String login() { 16 | return "auth-login"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/OffersController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import bg.softuni.mobilele.service.OfferService; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | 9 | @Controller 10 | @RequestMapping("/offers") 11 | public class OffersController { 12 | 13 | private final OfferService offerService; 14 | 15 | public OffersController(OfferService offerService) { 16 | this.offerService = offerService; 17 | } 18 | 19 | @GetMapping("/all") 20 | public String getAllOffers(Model model) { 21 | 22 | model.addAttribute("allOffers", offerService.getAllOffersSummary()); 23 | return "offers"; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/RegistrationController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import bg.softuni.mobilele.model.dto.UserRegistrationDTO; 4 | import bg.softuni.mobilele.service.UserService; 5 | import bg.softuni.mobilele.web.aop.WarnIfExecutionExceeds; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.ModelAttribute; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | @Controller 13 | @RequestMapping("/users") 14 | public class RegistrationController { 15 | 16 | private final UserService userService; 17 | 18 | public RegistrationController(UserService userService) { 19 | this.userService = userService; 20 | } 21 | 22 | @ModelAttribute("registerDTO") 23 | public UserRegistrationDTO registerDTO() { 24 | return new UserRegistrationDTO(); 25 | } 26 | 27 | @GetMapping("/register") 28 | public String register() { 29 | return "auth-register"; 30 | } 31 | 32 | @WarnIfExecutionExceeds( 33 | threshold = 1000 34 | ) 35 | @PostMapping("/register") 36 | public String register(UserRegistrationDTO registerDTO) { 37 | 38 | userService.registerUser(registerDTO); 39 | 40 | return "redirect:/"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/aop/MonitoringAspect.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web.aop; 2 | 3 | import java.lang.reflect.Method; 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | import org.aspectj.lang.annotation.Around; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Pointcut; 8 | import org.aspectj.lang.reflect.MethodSignature; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.util.StopWatch; 13 | 14 | @Aspect 15 | @Component 16 | public class MonitoringAspect { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(MonitoringAspect.class); 19 | 20 | @Around("Pointcuts.onWarnIfExecutionTimeExceeds()") 21 | Object monitorExecutionTime(ProceedingJoinPoint pjp) throws Throwable { 22 | WarnIfExecutionExceeds annotation = getAnnotation(pjp); 23 | long threshold = annotation.threshold(); 24 | 25 | StopWatch stopWatch = new StopWatch(); 26 | stopWatch.start(); 27 | 28 | // before 29 | var result = pjp.proceed(); 30 | // after 31 | stopWatch.stop(); 32 | long methodExecutionTime = stopWatch.lastTaskInfo().getTimeMillis(); 33 | 34 | if (methodExecutionTime > threshold) { 35 | LOGGER.warn("The method {} executed in {} millis which is more than " + 36 | "the acceptable threshold of {} millis. Threshold exceeded by {}.", 37 | pjp.getSignature(), 38 | methodExecutionTime, 39 | threshold, 40 | methodExecutionTime - threshold); 41 | } 42 | 43 | return result; 44 | } 45 | 46 | private static WarnIfExecutionExceeds getAnnotation(ProceedingJoinPoint pjp) { 47 | 48 | Method method = ((MethodSignature)pjp.getSignature()).getMethod(); 49 | 50 | return method.getAnnotation(WarnIfExecutionExceeds.class); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/aop/Pointcuts.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web.aop; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | public class Pointcuts { 6 | @Pointcut("@annotation(WarnIfExecutionExceeds)") 7 | void onWarnIfExecutionTimeExceeds(){} 8 | } 9 | -------------------------------------------------------------------------------- /mobilele/src/main/java/bg/softuni/mobilele/web/aop/WarnIfExecutionExceeds.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web.aop; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface WarnIfExecutionExceeds { 11 | 12 | long threshold(); 13 | } 14 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mobilele 4 | # thymeleaf: 5 | # check-template-location: true 6 | # cache: false 7 | # prefix: file:./src/main/resources/templates/ 8 | #server: 9 | # port: 8081 10 | datasource: 11 | url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/mobilele?allowPublicKeyRetrieval=true&useSSL=false&createDatabaseIfNotExist=true&serverTimezone=UTC 12 | username: ${MYSQL_USER:root} 13 | password: ${MYSQL_PASSWORD:} 14 | jpa: 15 | database-platform: org.hibernate.dialect.MySQLDialect 16 | defer-datasource-initialization: true 17 | properties: 18 | hibernate: 19 | format_sql: true 20 | hibernate: 21 | ddl-auto: update 22 | sql: 23 | init: 24 | mode: never 25 | kafka: 26 | bootstrap-servers: localhost:9092 27 | producer: 28 | key-serializer: org.apache.kafka.common.serialization.StringSerializer 29 | value-serializer: org.springframework.kafka.support.serializer.JsonSerializer 30 | mvc: 31 | hiddenmethod: 32 | filter: 33 | enabled: true 34 | 35 | forex: 36 | api: 37 | key: ${FOREX_API_KEY:test} 38 | url: "https://openexchangerates.org/api/latest.json?app_id={app_id}" 39 | base: "USD" 40 | init-ex-rates: ${INIT_EX_RATES:false} 41 | 42 | offers: 43 | api: 44 | baseUrl: "${OFFERS_BASE_URL:http://localhost:8081}" 45 | 46 | jwt: 47 | secret: ${JWT_KEY:WSdv1kEE1tCT1a8ihRSqfwMNzlPBq8IWSdv1kEE1tCT1a8ihRSqfwMNzlPBq8IWSdv1kEE1tCT1a8ihRSqfwMNzlPBq8I} 48 | expiration: ${JWT_EXPIRATION:60000} -------------------------------------------------------------------------------- /mobilele/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO roles VALUES (1, 'USER'), (2, 'ADMIN'); 2 | 3 | INSERT INTO `users` (`id`, `email`, `first_name`, `last_name`, `password`, `uuid`) 4 | VALUES 5 | (1,'lachezar@example.com','Lachezar','Balev','8dded4dc9b97fe3124a0b5397e3ab5251046e6ffaf0fcf6b32c6eb9fea7b1337bfa48a886c8c064adebeb5ff05b84300','1cc6e990-3699-4633-8c68-f148bec2748a'); 6 | 7 | INSERT INTO `users_roles` (`user_id`, `role_id`) 8 | VALUES 9 | (1, 1), 10 | (1, 2); -------------------------------------------------------------------------------- /mobilele/src/main/resources/i18n/messages_bg.properties: -------------------------------------------------------------------------------- 1 | add.offer.description.length=Описанието трябва да бъде между 5 и 500 символа. 2 | 3 | nav_bar_language=Език 4 | nav_bar_login=Вход 5 | nav_bar_all_brands=Всички марки 6 | nav_bar_add_offers=Добавяне на оферта 7 | nav_bar_login_register=Регистрация -------------------------------------------------------------------------------- /mobilele/src/main/resources/i18n/messages_en.properties: -------------------------------------------------------------------------------- 1 | add.offer.description.length=Description must be between 5 and 500 symbols. 2 | 3 | nav_bar_language=Language 4 | nav_bar_login=Login 5 | nav_bar_all_brands=All Brands 6 | nav_bar_add_offers=Add Offer 7 | nav_bar_login_register=Register -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url('/images/1.jpg'); 3 | background-position: center center; 4 | background-repeat: no-repeat; 5 | background-attachment: fixed; 6 | background-size: cover; 7 | background-color: #464646; 8 | } 9 | 10 | .navbar .logo { 11 | max-height: 50px; 12 | padding-right: 100px; 13 | } 14 | 15 | .container, .container-fluid { 16 | padding-top: 90px; 17 | } 18 | 19 | .bg-text { 20 | background-color: rgb(0,0,0); /* Fallback color */ 21 | background-color: rgba(0,0,0, 0.4); /* Black w/opacity/see-through */ 22 | color: white; 23 | font-weight: bold; 24 | border: 3px solid #f1f1f1; 25 | z-index: 2; 26 | width: 80%; 27 | padding: 20px; 28 | text-align: center; 29 | } 30 | 31 | .brand-section { 32 | background-image: linear-gradient(to bottom right, rgba(220, 220, 220, 0.9), rgba(255, 255, 255, 0.9)); 33 | } 34 | 35 | td, th { 36 | padding: 3px 8px; 37 | } 38 | 39 | .card.offer, .offer-details .list-group-item { 40 | background-image: linear-gradient(to bottom right, rgba(220, 220, 220, 0.9), rgba(255, 255, 255, 0.9)); 41 | } 42 | .card-img-top-wrapper { 43 | height: 150px; 44 | overflow:hidden; 45 | } 46 | 47 | form.main-form { 48 | background-image: linear-gradient(to bottom right, rgba(70, 70, 70, 0.8), rgba(10, 10, 10, 0.8)); 49 | padding:10px 30px; 50 | border-radius: 20px; 51 | } 52 | 53 | .field-error { 54 | background: white url('data:image/svg+xml;utf8, ') no-repeat top right/30px 30px; 55 | } 56 | 57 | .logged-user { 58 | color: white; 59 | } 60 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/css/reset-css.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/mobilele/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/mobilele/src/main/resources/static/images/1.jpg -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/car-dealership-car-car-showroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/mobilele/src/main/resources/static/images/car-dealership-car-car-showroom.jpg -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/mobilele/src/main/resources/static/images/car.png -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/js/currency.js: -------------------------------------------------------------------------------- 1 | let currencyDropDown = document.getElementById('currency') 2 | 3 | currencyDropDown.addEventListener('change', currencyChange) 4 | 5 | function currencyChange() { 6 | let selectedCurrency = currencyDropDown.value 7 | let amountInBGN = document.getElementById('priceInBGN').value 8 | let priceSpan = document.getElementById('price') 9 | 10 | fetch('/api/convert?' + new URLSearchParams( 11 | { 12 | from: 'BGN', 13 | to: selectedCurrency, 14 | amount: amountInBGN 15 | } 16 | )) 17 | .then(response => response.json()) 18 | .then(data => {priceSpan.textContent = data.result}) 19 | .catch(error => { 20 | console.log('An error occurred:' + error) 21 | }) 22 | } -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/auth-login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MobiLeLeLe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |

Login

19 |
22 |
23 | 24 | 30 |
31 |
32 | 33 | 38 |
39 | 40 |

41 | Invalid username or password. 42 |

43 | 44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/details.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobiLeLeLe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |

Details

21 |
24 |
25 | 26 |
27 |
28 | Offer year Brand name Model name
29 |
30 |
    31 |
  • 32 |
    • Mileage Mileage
    33 |
    • Price Price
    34 |
    • Engine type Engine type
    35 |
    • Transmission type
    36 |
    • Currency 37 | 47 |
    48 |
  • 49 |
50 | 51 |
52 | Update 53 |
55 | 56 |
57 | 58 |
59 |
60 |
61 | Car image 62 |
63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobiLeLeLe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |
Welcome to MobiLeLeLe, 21 | ! 22 |
23 |
24 | 25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/object-not-found.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobiLeLeLe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |
Opps! Object with id 1 was not found!
21 |
22 | 23 |
We are sorry! Please navigate to home page...
24 | 25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/offer-not-found.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobiLeLeLe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |
Opps! Offer with id 1 was not found!
21 |
22 | 23 |
We are sorry! Please navigate to home page...
24 | 25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/offers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobiLeLeLe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |

All Offers

20 |
24 |
25 |
26 | Car image 27 |
28 |
29 |
TODO: Offer year Brand name Model name
30 |
31 |
    32 |
  • 33 |
    • Mileage
    34 |
    • TODO: Price
    35 |
    • Engine type
    36 |
    • TODO: Transmission type
    37 |
  • 38 |
39 |
40 | Details 41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /mobilele/src/test/java/bg/softuni/mobilele/MobileleApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MobileleApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mobilele/src/test/java/bg/softuni/mobilele/service/impl/ExRateServiceImplIT.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 4 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 5 | 6 | import bg.softuni.mobilele.config.ForexApiConfig; 7 | import bg.softuni.mobilele.model.dto.ExRatesDTO; 8 | import bg.softuni.mobilele.service.ExRateService; 9 | import com.github.tomakehurst.wiremock.WireMockServer; 10 | import com.maciejwalkowiak.wiremock.spring.ConfigureWireMock; 11 | import com.maciejwalkowiak.wiremock.spring.EnableWireMock; 12 | import com.maciejwalkowiak.wiremock.spring.InjectWireMock; 13 | import java.math.BigDecimal; 14 | import org.aspectj.lang.annotation.Before; 15 | import org.junit.jupiter.api.Assertions; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.boot.test.context.SpringBootTest; 20 | 21 | @SpringBootTest 22 | @EnableWireMock( 23 | @ConfigureWireMock(name = "exchange-rate-service") 24 | ) 25 | public class ExRateServiceImplIT { 26 | 27 | @InjectWireMock("exchange-rate-service") 28 | private WireMockServer wireMockServer; 29 | 30 | @Autowired 31 | private ExRateService exRateService; 32 | 33 | @Autowired 34 | private ForexApiConfig forexApiConfig; 35 | 36 | @BeforeEach 37 | void setUp() { 38 | forexApiConfig.setUrl(wireMockServer.baseUrl() + "/test-currencies"); 39 | } 40 | 41 | @Test 42 | void testFetchExchangeRates() { 43 | wireMockServer.stubFor(get("/test-currencies").willReturn( 44 | aResponse() 45 | .withHeader("Content-Type", "application/json") 46 | .withBody( 47 | """ 48 | { 49 | "base": "USD", 50 | "rates" : { 51 | "BGN": 1.86, 52 | "EUR": 0.91 53 | } 54 | } 55 | """ 56 | ) 57 | ) 58 | ); 59 | 60 | ExRatesDTO exRatesDTO = exRateService.fetchExRates(); 61 | 62 | Assertions.assertEquals("USD", exRatesDTO.base()); 63 | Assertions.assertEquals(2, exRatesDTO.rates().size()); 64 | Assertions.assertEquals(new BigDecimal("1.86"), exRatesDTO.rates().get("BGN")); 65 | Assertions.assertEquals(new BigDecimal("0.91"), exRatesDTO.rates().get("EUR")); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /mobilele/src/test/java/bg/softuni/mobilele/service/impl/UserServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.service.impl; 2 | 3 | import static org.mockito.Mockito.times; 4 | import static org.mockito.Mockito.verify; 5 | import static org.mockito.Mockito.when; 6 | 7 | import bg.softuni.mobilele.model.dto.UserRegistrationDTO; 8 | import bg.softuni.mobilele.model.entity.UserEntity; 9 | import bg.softuni.mobilele.repository.UserRepository; 10 | import jakarta.validation.constraints.Email; 11 | import jakarta.validation.constraints.NotEmpty; 12 | import jakarta.validation.constraints.Size; 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Disabled; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.extension.ExtendWith; 18 | import org.mockito.ArgumentCaptor; 19 | import org.mockito.Captor; 20 | import org.mockito.Mock; 21 | import org.mockito.junit.jupiter.MockitoExtension; 22 | import org.modelmapper.ModelMapper; 23 | import org.springframework.security.crypto.password.PasswordEncoder; 24 | 25 | @ExtendWith(MockitoExtension.class) 26 | public class UserServiceImplTest { 27 | 28 | private UserServiceImpl toTest; 29 | 30 | @Captor 31 | private ArgumentCaptor userEntityCaptor; 32 | 33 | @Mock 34 | private UserRepository mockUserRepository; 35 | 36 | @Mock 37 | private PasswordEncoder mockPasswordEncoder; 38 | 39 | @BeforeEach 40 | void setUp() { 41 | 42 | toTest = new UserServiceImpl( 43 | new ModelMapper(), 44 | mockPasswordEncoder, 45 | mockUserRepository 46 | ); 47 | 48 | } 49 | 50 | @Test 51 | void testUserRegistration() { 52 | // Arrange 53 | 54 | UserRegistrationDTO userRegistrationDTO = 55 | new UserRegistrationDTO() 56 | .setFirstName("Anna") 57 | .setLastName("Dimitrova") 58 | .setPassword("topsecret") 59 | .setEmail("anna@example.com"); 60 | 61 | when(mockPasswordEncoder.encode(userRegistrationDTO.getPassword())) 62 | .thenReturn(userRegistrationDTO.getPassword()+userRegistrationDTO.getPassword()); 63 | 64 | 65 | // ACT 66 | toTest.registerUser(userRegistrationDTO); 67 | 68 | // Assert 69 | verify(mockUserRepository).save(userEntityCaptor.capture()); 70 | 71 | UserEntity actualSavedEntity = userEntityCaptor.getValue(); 72 | 73 | Assertions.assertNotNull(actualSavedEntity); 74 | Assertions.assertEquals(userRegistrationDTO.getFirstName(), actualSavedEntity.getFirstName()); 75 | Assertions.assertEquals(userRegistrationDTO.getLastName(), actualSavedEntity.getLastName()); 76 | Assertions.assertEquals(userRegistrationDTO.getPassword() + userRegistrationDTO.getPassword(), 77 | actualSavedEntity.getPassword()); 78 | Assertions.assertEquals(userRegistrationDTO.getEmail(), actualSavedEntity.getEmail()); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /mobilele/src/test/java/bg/softuni/mobilele/web/CurrencyControllerIT.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import static org.mockito.Mockito.when; 4 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 5 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 7 | 8 | import bg.softuni.mobilele.service.ExRateService; 9 | import bg.softuni.mobilele.service.exception.ApiObjectNotFoundException; 10 | import java.math.BigDecimal; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.boot.test.mock.mockito.MockBean; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | 18 | @SpringBootTest 19 | @AutoConfigureMockMvc 20 | public class CurrencyControllerIT { 21 | 22 | @Autowired 23 | private MockMvc mockMvc; 24 | 25 | // Use only if really needed. 26 | @MockBean 27 | private ExRateService mockExRateService; 28 | 29 | @Test 30 | public void testConvert() throws Exception { 31 | String from = "SUD"; 32 | String to = "ZWD"; 33 | BigDecimal amount = new BigDecimal("100"); 34 | BigDecimal expectedResult = new BigDecimal("50"); 35 | 36 | when(mockExRateService.convert(from, to, amount)).thenReturn(expectedResult); 37 | 38 | mockMvc.perform(get("/api/convert") 39 | .param("from", from) 40 | .param("to", to) 41 | .param("amount", String.valueOf(amount.intValue())) 42 | ).andExpect(status().isOk()) 43 | .andExpect(jsonPath("$.from").value(from)) 44 | .andExpect(jsonPath("$.to").value(to)) 45 | .andExpect(jsonPath("$.amount").value(amount)) 46 | .andExpect(jsonPath("$.result").value(expectedResult)); 47 | } 48 | 49 | @Test 50 | public void testConversionNotFound() throws Exception { 51 | String from = "SUD"; 52 | String to = "ZWD"; 53 | BigDecimal amount = new BigDecimal("100"); 54 | 55 | when(mockExRateService.convert(from, to, amount)) 56 | .thenThrow(new ApiObjectNotFoundException("Test message", "TestId")); 57 | 58 | mockMvc.perform(get("/api/convert") 59 | .param("from", from) 60 | .param("to", to) 61 | .param("amount", String.valueOf(amount.intValue())) 62 | ).andExpect(status().isNotFound()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /mobilele/src/test/java/bg/softuni/mobilele/web/RegistrationControllerIT.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.mobilele.web; 2 | 3 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 4 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 5 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 8 | 9 | import bg.softuni.mobilele.model.entity.UserEntity; 10 | import bg.softuni.mobilele.repository.UserRepository; 11 | import java.util.Optional; 12 | import org.junit.jupiter.api.Assertions; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | @SpringBootTest 21 | @AutoConfigureMockMvc 22 | public class RegistrationControllerIT { 23 | 24 | @Autowired 25 | private MockMvc mockMvc; 26 | 27 | @Autowired 28 | private UserRepository userRepository; 29 | 30 | @Autowired 31 | private PasswordEncoder passwordEncoder; 32 | 33 | @Test 34 | void testRegistration() throws Exception { 35 | 36 | mockMvc.perform(post("/users/register") 37 | .param("email", "anna@example.com") 38 | .param("firstName", "Anna") 39 | .param("lastName", "Petrova") 40 | .param("password", "topsecret") 41 | .with(csrf()) 42 | ).andExpect(status().is3xxRedirection()) 43 | .andExpect(redirectedUrl("/")); 44 | 45 | Optional userEntityOpt = userRepository.findByEmail("anna@example.com"); 46 | 47 | Assertions.assertTrue(userEntityOpt.isPresent()); 48 | 49 | UserEntity userEntity = userEntityOpt.get(); 50 | 51 | Assertions.assertEquals("Anna", userEntity.getFirstName()); 52 | Assertions.assertEquals("Petrova", userEntity.getLastName()); 53 | 54 | Assertions.assertTrue(passwordEncoder.matches("topsecret", userEntity.getPassword())); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mobilele/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | defer-datasource-initialization: true 4 | properties: 5 | hibernate: 6 | format_sql: true 7 | hibernate: 8 | ddl-auto: update 9 | sql: 10 | init: 11 | mode: never 12 | mvc: 13 | hiddenmethod: 14 | filter: 15 | enabled: true 16 | 17 | forex: 18 | api: 19 | key: test 20 | url: "http://example.com" 21 | base: "USD" 22 | 23 | offers: 24 | api: 25 | baseUrl: "http://localhost:8081" -------------------------------------------------------------------------------- /mobilele/src/test/resources/currency-api.http: -------------------------------------------------------------------------------- 1 | ### GET request with parameter 2 | GET http://localhost:8080/api/convert?from=XXX&to=EUR&amount=50 3 | Accept: application/json -------------------------------------------------------------------------------- /proxies/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /proxies/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /proxies/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'org.example' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testImplementation platform('org.junit:junit-bom:5.9.1') 14 | testImplementation 'org.junit.jupiter:junit-jupiter' 15 | } 16 | 17 | test { 18 | useJUnitPlatform() 19 | } -------------------------------------------------------------------------------- /proxies/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/proxies/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /proxies/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jul 30 21:50:43 EEST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /proxies/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'proxies' 2 | 3 | -------------------------------------------------------------------------------- /proxies/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import java.lang.reflect.Proxy; 4 | import org.example.proxydemo.CacheableInvocationHandler; 5 | import org.example.proxydemo.StudentService; 6 | import org.example.proxydemo.StudentServiceImpl; 7 | 8 | public class Main { 9 | 10 | public static void main(String[] args) { 11 | StudentService studentService = studentService(); 12 | 13 | studentService.findStudents(); 14 | studentService.findStudents(); 15 | 16 | studentService.findStudents().forEach( 17 | System.out::println 18 | ); 19 | 20 | } 21 | 22 | private static StudentService studentService() { 23 | return (StudentService) Proxy.newProxyInstance( 24 | Main.class.getClassLoader(), 25 | new Class[]{StudentService.class}, 26 | new CacheableInvocationHandler(new StudentServiceImpl()) 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /proxies/src/main/java/org/example/proxydemo/Cacheable.java: -------------------------------------------------------------------------------- 1 | package org.example.proxydemo; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface Cacheable { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /proxies/src/main/java/org/example/proxydemo/CacheableInvocationHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.proxydemo; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | public class CacheableInvocationHandler implements InvocationHandler { 9 | 10 | private final Map cache = 11 | new ConcurrentHashMap<>(); 12 | 13 | private final StudentService delegate; 14 | 15 | public CacheableInvocationHandler(StudentService delegate) { 16 | this.delegate = delegate; 17 | } 18 | 19 | @Override 20 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 21 | 22 | Cacheable cacheableAnnotation = delegate 23 | .getClass() 24 | .getMethod(method.getName(), method.getParameterTypes()) 25 | .getAnnotation(Cacheable.class); 26 | 27 | if (cacheableAnnotation != null) { 28 | String cacheKey = cacheableAnnotation.value(); 29 | if (cache.containsKey(cacheKey)) { 30 | return cache.get(cacheKey); 31 | } else { 32 | Object result = method.invoke(delegate, args); 33 | cache.put( 34 | cacheKey, 35 | result 36 | ); 37 | return result; 38 | } 39 | } else { 40 | return method.invoke(delegate, args); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /proxies/src/main/java/org/example/proxydemo/StudentDTO.java: -------------------------------------------------------------------------------- 1 | package org.example.proxydemo; 2 | 3 | public record StudentDTO(int age, String name) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /proxies/src/main/java/org/example/proxydemo/StudentService.java: -------------------------------------------------------------------------------- 1 | package org.example.proxydemo; 2 | 3 | import java.util.List; 4 | 5 | public interface StudentService { 6 | List findStudents(); 7 | } 8 | -------------------------------------------------------------------------------- /proxies/src/main/java/org/example/proxydemo/StudentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.example.proxydemo; 2 | 3 | import java.util.List; 4 | 5 | public class StudentServiceImpl implements StudentService { 6 | @Override 7 | @Cacheable(value = "students") 8 | public List findStudents() { 9 | System.out.println("Calculating students...."); 10 | return List.of( 11 | new StudentDTO(20, "Anna"), 12 | new StudentDTO(40, "Pehso") 13 | ); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /state/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /state/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.3.0' 4 | id 'io.spring.dependency-management' version '1.1.5' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '21' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 22 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 23 | } 24 | 25 | tasks.named('test') { 26 | useJUnitPlatform() 27 | } 28 | -------------------------------------------------------------------------------- /state/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/state/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /state/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /state/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'state' 2 | -------------------------------------------------------------------------------- /state/src/main/java/bg/softuni/state/StateApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.state; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class StateApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(StateApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /state/src/main/java/bg/softuni/state/web/CookieController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.state.web; 2 | 3 | import jakarta.servlet.http.Cookie; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.CookieValue; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | 12 | @Controller 13 | public class CookieController { 14 | 15 | private static final String LANG_COOKIE_NAME = "lang"; 16 | private static final String DEFAULT_LANG = "en"; 17 | 18 | @GetMapping("/cookies") 19 | public String cookies( 20 | @CookieValue( 21 | name = LANG_COOKIE_NAME, 22 | defaultValue = DEFAULT_LANG 23 | ) String lang, 24 | Model model) { 25 | 26 | model.addAttribute("lang", lang); 27 | 28 | return "cookies"; 29 | } 30 | 31 | @PostMapping("/cookies") 32 | public String cookies( 33 | @RequestParam("language") String lang, 34 | HttpServletResponse response 35 | ) { 36 | Cookie langCookie = new Cookie(LANG_COOKIE_NAME, lang); 37 | response.addCookie(langCookie); 38 | 39 | return "redirect:/cookies"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /state/src/main/java/bg/softuni/state/web/SessionController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.state.web; 2 | 3 | import jakarta.servlet.http.Cookie; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import jakarta.servlet.http.HttpSession; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.CookieValue; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @Controller 14 | public class SessionController { 15 | 16 | private static final String LANG_SESSION_NAME = "lang"; 17 | private static final String DEFAULT_LANG = "en"; 18 | 19 | @GetMapping("/session") 20 | public String cookies( 21 | HttpSession session, 22 | Model model) { 23 | 24 | Object lang = session.getAttribute(LANG_SESSION_NAME); 25 | 26 | model.addAttribute("lang", 27 | lang != null ? lang : DEFAULT_LANG); 28 | 29 | return "session"; 30 | } 31 | 32 | @PostMapping("/session") 33 | public String cookies( 34 | @RequestParam("language") String lang, 35 | HttpSession session 36 | ) { 37 | session.setAttribute(LANG_SESSION_NAME, lang); 38 | 39 | return "redirect:/session"; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /state/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=state 2 | -------------------------------------------------------------------------------- /state/src/main/resources/templates/cookies.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | Document 10 | 11 | 12 | 13 | Current language is en 14 | 15 |
17 | 18 | 25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /state/src/main/resources/templates/session.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | Document 10 | 11 | 12 | 13 | Current language is en 14 | 15 |
17 | 18 | 25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /state/src/test/java/bg/softuni/state/StateApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.state; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class StateApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /thymeleaf-pure/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /thymeleaf-pure/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /thymeleaf-pure/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'org.example' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | implementation('org.thymeleaf:thymeleaf:3.1.2.RELEASE') 14 | 15 | testImplementation platform('org.junit:junit-bom:5.9.1') 16 | testImplementation 'org.junit.jupiter:junit-jupiter' 17 | } 18 | 19 | test { 20 | useJUnitPlatform() 21 | } -------------------------------------------------------------------------------- /thymeleaf-pure/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/thymeleaf-pure/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /thymeleaf-pure/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 30 18:41:46 EEST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /thymeleaf-pure/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'thymeleaf-pure' 2 | 3 | -------------------------------------------------------------------------------- /thymeleaf-pure/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.thymeleaf.ITemplateEngine; 4 | import org.thymeleaf.TemplateEngine; 5 | import org.thymeleaf.context.Context; 6 | import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; 7 | 8 | public class Main { 9 | 10 | public static void main(String[] args) { 11 | ITemplateEngine engine = createTemplateEngine(); 12 | 13 | Context ctx = new Context(); 14 | ctx.setVariable("name", "Dimo"); 15 | 16 | String html = engine.process("test.html", ctx); 17 | System.out.println(html); 18 | } 19 | 20 | private static ITemplateEngine createTemplateEngine() { 21 | TemplateEngine templateEngine = new TemplateEngine(); 22 | 23 | templateEngine.setTemplateResolver(new ClassLoaderTemplateResolver()); 24 | 25 | return templateEngine; 26 | } 27 | } -------------------------------------------------------------------------------- /thymeleaf-pure/src/main/resources/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 | Hello, Pesho! 13 | 14 | Welcome to our site! 15 | 16 | 17 | -------------------------------------------------------------------------------- /virt-threads-demo/api/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /virt-threads-demo/api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.2.5' 4 | id 'io.spring.dependency-management' version '1.1.4' 5 | } 6 | 7 | group = 'eu.balev.virtual' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '21' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-web' 20 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 21 | } 22 | 23 | tasks.named('test') { 24 | useJUnitPlatform() 25 | } 26 | -------------------------------------------------------------------------------- /virt-threads-demo/api/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/virt-threads-demo/api/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /virt-threads-demo/api/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /virt-threads-demo/api/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'api' 2 | -------------------------------------------------------------------------------- /virt-threads-demo/api/src/main/java/eu/balev/virtual/api/ApiApplication.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.api; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ApiApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ApiApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /virt-threads-demo/api/src/main/java/eu/balev/virtual/api/web/RandomController.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.api.web; 2 | 3 | import java.util.Random; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class RandomController { 11 | 12 | private static Logger LOGGER = LoggerFactory.getLogger(RandomController.class); 13 | 14 | @GetMapping("/random") 15 | public Integer random() { 16 | 17 | LOGGER.info("Requesting a random number"); 18 | try { 19 | Thread.sleep(2000); 20 | } catch (InterruptedException e) { 21 | Thread.currentThread().interrupt(); 22 | } 23 | 24 | return new Random().nextInt(1, 50); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /virt-threads-demo/api/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9001 3 | 4 | spring: 5 | threads: 6 | virtual: 7 | enabled: true 8 | -------------------------------------------------------------------------------- /virt-threads-demo/api/src/test/java/eu/balev/virtual/api/ApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApiApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /virt-threads-demo/server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /virt-threads-demo/server/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.2.5' 4 | id 'io.spring.dependency-management' version '1.1.4' 5 | } 6 | 7 | group = 'eu.balev.virtual' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-web' 20 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 21 | } 22 | 23 | tasks.named('test') { 24 | useJUnitPlatform() 25 | } 26 | -------------------------------------------------------------------------------- /virt-threads-demo/server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-spring-may-2024/a67a84dd273d3f65199a5eda5980c01e2aaa360f/virt-threads-demo/server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /virt-threads-demo/server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /virt-threads-demo/server/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'server' 2 | -------------------------------------------------------------------------------- /virt-threads-demo/server/src/main/java/eu/balev/virtual/server/ServerApplication.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /virt-threads-demo/server/src/main/java/eu/balev/virtual/server/config/RestConfig.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.server.config; 2 | 3 | import java.util.Map; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.client.ClientHttpRequestInterceptor; 7 | import org.springframework.web.client.RestClient; 8 | 9 | @Configuration 10 | public class RestConfig { 11 | 12 | @Bean 13 | public RestClient restClient() { 14 | return RestClient.builder() 15 | .baseUrl("http://localhost:9001") 16 | .defaultHeaders(httpHeaders -> httpHeaders.set("Content-Type", "application/json")) 17 | .build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /virt-threads-demo/server/src/main/java/eu/balev/virtual/server/web/RandomController.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.server.web; 2 | 3 | import java.time.LocalTime; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import org.springframework.web.client.RestClient; 8 | 9 | @RestController 10 | public class RandomController { 11 | 12 | private static class Statistics { 13 | 14 | private static AtomicInteger requests = new AtomicInteger(); 15 | static void inc() { 16 | requests.incrementAndGet(); 17 | } 18 | 19 | static void print() { 20 | if (requests.get() % 100 == 0) { 21 | System.out.println("Received " + requests.get() + " requests. " + LocalTime.now()); 22 | } 23 | } 24 | } 25 | 26 | private final RestClient restClient; 27 | 28 | public RandomController(RestClient restClient) { 29 | this.restClient = restClient; 30 | } 31 | 32 | @GetMapping("/random") 33 | public Integer getRandom() { 34 | 35 | Integer result = restClient 36 | .get() 37 | .uri("/random") 38 | .retrieve() 39 | .body(Integer.class); 40 | 41 | Statistics.inc(); 42 | Statistics.print(); 43 | 44 | return result; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /virt-threads-demo/server/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring.application.name: server 2 | spring: 3 | threads: 4 | virtual: 5 | enabled: true 6 | server: 7 | tomcat: 8 | max-connections: 50000 9 | accept-count: 2000 10 | threads: 11 | max: 30 12 | 13 | #logging: 14 | # level: 15 | # org.apache: DEBUG 16 | -------------------------------------------------------------------------------- /virt-threads-demo/server/src/test/java/eu/balev/virtual/server/ServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package eu.balev.virtual.server; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------