├── README.md ├── common ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── spring5microservices │ │ └── common │ │ ├── collection │ │ ├── WeightedCollection.java │ │ └── tuple │ │ │ ├── Tuple.java │ │ │ ├── Tuple0.java │ │ │ ├── Tuple1.java │ │ │ ├── Tuple2.java │ │ │ ├── Tuple3.java │ │ │ ├── Tuple4.java │ │ │ ├── Tuple5.java │ │ │ ├── Tuple6.java │ │ │ └── Tuple7.java │ │ ├── converter │ │ ├── BaseConverter.java │ │ ├── BaseFromDtoToModelConverter.java │ │ ├── BaseFromModelToDtoConverter.java │ │ └── enums │ │ │ ├── BaseEnumConverter.java │ │ │ ├── BaseFromEnumToValueConverter.java │ │ │ └── BaseFromValueToEnumConverter.java │ │ ├── dto │ │ ├── AuthenticationInformationDto.java │ │ ├── ErrorResponseDto.java │ │ └── UsernameAuthoritiesDto.java │ │ ├── enums │ │ ├── ExtendedHttpStatus.java │ │ └── RestApiErrorCode.java │ │ ├── exception │ │ ├── JsonException.java │ │ ├── TokenExpiredException.java │ │ └── UnauthorizedException.java │ │ ├── interfaces │ │ ├── consumer │ │ │ ├── PentaConsumer.java │ │ │ ├── QuadConsumer.java │ │ │ └── TriConsumer.java │ │ ├── function │ │ │ ├── HeptaFunction.java │ │ │ ├── HexaFunction.java │ │ │ ├── OctaFunction.java │ │ │ ├── PartialFunction.java │ │ │ ├── PentaFunction.java │ │ │ ├── QuadFunction.java │ │ │ └── TriFunction.java │ │ └── predicate │ │ │ ├── HeptaPredicate.java │ │ │ ├── HexaPredicate.java │ │ │ ├── PentaPredicate.java │ │ │ ├── QuadPredicate.java │ │ │ └── TriPredicate.java │ │ ├── service │ │ ├── CacheService.java │ │ └── EncryptorService.java │ │ └── util │ │ ├── CollectionUtil.java │ │ ├── CollectorsUtil.java │ │ ├── ComparatorUtil.java │ │ ├── DateTimeUtil.java │ │ ├── ExceptionUtil.java │ │ ├── FunctionUtil.java │ │ ├── HttpUtil.java │ │ ├── JsonUtil.java │ │ ├── Lazy.java │ │ ├── MapUtil.java │ │ ├── NumberUtil.java │ │ ├── ObjectUtil.java │ │ ├── PredicateUtil.java │ │ ├── StringUtil.java │ │ ├── Try │ │ ├── Failure.java │ │ ├── Success.java │ │ └── Try.java │ │ ├── either │ │ ├── Either.java │ │ ├── Left.java │ │ └── Right.java │ │ ├── validation │ │ ├── Invalid.java │ │ ├── Valid.java │ │ ├── Validate.java │ │ ├── Validation.java │ │ └── ValidationError.java │ │ └── validator │ │ └── enums │ │ ├── EnumHasInternalStringValue.java │ │ ├── EnumHasInternalStringValueValidator.java │ │ └── IEnumInternalPropertyValue.java │ └── test │ ├── java │ └── com │ │ └── spring5microservices │ │ └── common │ │ ├── PizzaDto.java │ │ ├── PizzaEnum.java │ │ ├── UserDto.java │ │ ├── collection │ │ ├── WeightedCollectionTest.java │ │ └── tuple │ │ │ ├── Tuple0Test.java │ │ │ ├── Tuple1Test.java │ │ │ ├── Tuple2Test.java │ │ │ ├── Tuple3Test.java │ │ │ ├── Tuple4Test.java │ │ │ ├── Tuple5Test.java │ │ │ ├── Tuple6Test.java │ │ │ ├── Tuple7Test.java │ │ │ └── TupleTest.java │ │ ├── interfaces │ │ ├── consumer │ │ │ ├── PentaConsumerTest.java │ │ │ ├── QuadConsumerTest.java │ │ │ └── TriConsumerTest.java │ │ ├── function │ │ │ ├── HeptaFunctionTest.java │ │ │ ├── HexaFunctionTest.java │ │ │ ├── OctaFunctionTest.java │ │ │ ├── PartialFunctionTest.java │ │ │ ├── PentaFunctionTest.java │ │ │ ├── QuadFunctionTest.java │ │ │ └── TriFunctionTest.java │ │ └── predicate │ │ │ ├── HeptaPredicateTest.java │ │ │ ├── HexaPredicateTest.java │ │ │ ├── PentaPredicateTest.java │ │ │ ├── QuadPredicateTest.java │ │ │ └── TriPredicateTest.java │ │ ├── service │ │ ├── CacheServiceTest.java │ │ └── EncryptorServiceTest.java │ │ └── util │ │ ├── CollectionUtilTest.java │ │ ├── CollectorsUtilTest.java │ │ ├── ComparatorUtilTest.java │ │ ├── DateTimeUtilTest.java │ │ ├── ExceptionUtilTest.java │ │ ├── FunctionUtilTest.java │ │ ├── HttpUtilTest.java │ │ ├── JsonUtilTest.java │ │ ├── LazyTest.java │ │ ├── MapUtilTest.java │ │ ├── NumberUtilTest.java │ │ ├── ObjectUtilTest.java │ │ ├── PredicateUtilTest.java │ │ ├── StringUtilTest.java │ │ ├── Try │ │ ├── FailureTest.java │ │ ├── SuccessTest.java │ │ └── TryTest.java │ │ ├── either │ │ ├── EitherTest.java │ │ ├── LeftTest.java │ │ └── RightTest.java │ │ ├── validation │ │ ├── InvalidTest.java │ │ ├── ValidTest.java │ │ ├── ValidationTest.java │ │ └── ValidationUseCasesTest.java │ │ └── validator │ │ └── enums │ │ └── EnumHasInternalStringValueValidatorTest.java │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── compose.yml ├── config-server ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── configserver │ │ └── ConfigServerApplication.java │ └── resources │ ├── application-dev.yml │ ├── application-docker.yml │ └── application.yml ├── documentation ├── CommunitationDiagram.png ├── DockerImages.png ├── Encryption.png ├── GrpcCommunicationDiagram.png ├── OrderService.png ├── PizzaService.png ├── SecurityJwtService_AuthorizationInfo.png ├── SecurityJwtService_Credentials.png ├── SecurityJwtService_Login.png ├── SecurityJwtService_Refresh.png ├── SecurityOauthService_AuthorizationInfo.png ├── SecurityOauthService_Credentials.png ├── SecurityOauthService_Login.png ├── SecurityOauthService_Refresh.png ├── Swagger.png └── WebServiceWithGRPC.png ├── gateway-server ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── gatewayserver │ │ ├── GatewayServerApplication.java │ │ ├── configuration │ │ ├── documentation │ │ │ └── DocumentationConfiguration.java │ │ └── rest │ │ │ ├── GlobalErrorWebExceptionHandler.java │ │ │ └── RestRoutes.java │ │ ├── controller │ │ └── CircuitBreakerController.java │ │ └── filter │ │ └── RequestFilter.java │ └── resources │ ├── application-dev.yml │ ├── application-docker.yml │ └── application.yml ├── grpc-api ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── spring5microservices │ │ │ └── grpc │ │ │ ├── configuration │ │ │ └── GrpcHeader.java │ │ │ ├── security │ │ │ └── BasicCredential.java │ │ │ └── util │ │ │ └── GrpcErrorHandlerUtil.java │ └── resources │ │ └── proto │ │ └── ingredient.proto │ └── test │ └── java │ └── com │ └── spring5microservice │ └── grpc │ └── util │ └── GrpcErrorHandlerUtilTest.java ├── order-service ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── order │ │ │ ├── OrderServiceApplication.java │ │ │ ├── annotation │ │ │ ├── RoleAdmin.java │ │ │ └── RoleAdminOrUser.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ ├── WebConfiguration.java │ │ │ ├── documentation │ │ │ │ └── DocumentationConfiguration.java │ │ │ ├── rest │ │ │ │ ├── GlobalErrorWebExceptionHandler.java │ │ │ │ └── RestRoutes.java │ │ │ └── security │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ ├── SecurityManager.java │ │ │ │ ├── WebSecurityConfiguration.java │ │ │ │ ├── client │ │ │ │ └── SecurityServerRestClient.java │ │ │ │ └── filter │ │ │ │ └── SecurityFilter.java │ │ │ ├── controller │ │ │ ├── IngredientController.java │ │ │ └── OrderController.java │ │ │ ├── dao │ │ │ ├── OrderDao.java │ │ │ ├── OrderLineDao.java │ │ │ ├── ParentDao.java │ │ │ └── PizzaDao.java │ │ │ ├── dto │ │ │ ├── IngredientAmountDto.java │ │ │ ├── OrderDto.java │ │ │ ├── OrderLineDto.java │ │ │ ├── PizzaDto.java │ │ │ └── UsernameAuthoritiesDto.java │ │ │ ├── grpc │ │ │ ├── client │ │ │ │ ├── GrpcClient.java │ │ │ │ └── GrpcClientRunner.java │ │ │ ├── configuration │ │ │ │ └── GrpcConfiguration.java │ │ │ ├── interceptor │ │ │ │ └── RequestIdInterceptor.java │ │ │ └── service │ │ │ │ └── IngredientServiceGrpcImpl.java │ │ │ ├── model │ │ │ ├── IModel.java │ │ │ ├── Order.java │ │ │ ├── OrderLine.java │ │ │ ├── Pizza.java │ │ │ └── jooq │ │ │ │ ├── DefaultCatalog.java │ │ │ │ ├── Eat.java │ │ │ │ ├── Indexes.java │ │ │ │ ├── Keys.java │ │ │ │ ├── Sequences.java │ │ │ │ ├── Tables.java │ │ │ │ └── tables │ │ │ │ ├── OrderLineTable.java │ │ │ │ ├── OrderTable.java │ │ │ │ ├── PizzaTable.java │ │ │ │ └── records │ │ │ │ ├── OrderLineRecord.java │ │ │ │ ├── OrderRecord.java │ │ │ │ └── PizzaRecord.java │ │ │ ├── service │ │ │ ├── IngredientService.java │ │ │ ├── OrderLineService.java │ │ │ └── OrderService.java │ │ │ └── util │ │ │ └── converter │ │ │ ├── OrderConverter.java │ │ │ ├── OrderLineConverter.java │ │ │ └── PizzaConverter.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-docker.yml │ │ ├── application.yml │ │ └── logback.xml │ └── test │ ├── java │ └── com │ │ └── order │ │ ├── TestDataFactory.java │ │ ├── controller │ │ ├── BaseControllerTest.java │ │ ├── IngredientControllerTest.java │ │ └── OrderControllerTest.java │ │ ├── dao │ │ ├── OrderDaoTest.java │ │ ├── OrderLineDaoTest.java │ │ └── PizzaDaoTest.java │ │ ├── grpc │ │ └── service │ │ │ └── IngredientServiceGrpcImplTest.java │ │ ├── service │ │ ├── IngredientServiceTest.java │ │ ├── OrderLineServiceTest.java │ │ └── OrderServiceTest.java │ │ └── util │ │ └── converter │ │ ├── OrderConverterTest.java │ │ ├── OrderLineConverterTest.java │ │ └── PizzaConverterTest.java │ └── resources │ ├── application.yml │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── pizza-service ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── pizza │ │ │ ├── PizzaServiceApplication.java │ │ │ ├── annotation │ │ │ ├── RoleAdmin.java │ │ │ └── RoleAdminOrUser.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ ├── WebfluxConfiguration.java │ │ │ ├── cache │ │ │ │ └── CacheConfiguration.java │ │ │ ├── documentation │ │ │ │ └── DocumentationConfiguration.java │ │ │ ├── persistence │ │ │ │ ├── PersistenceConfiguration.java │ │ │ │ └── SqlFunctionsMetadataBuilderContributor.java │ │ │ ├── rest │ │ │ │ ├── GlobalErrorWebExceptionHandler.java │ │ │ │ ├── RestConfiguration.java │ │ │ │ └── RestRoutes.java │ │ │ └── security │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ ├── SecurityContextRepository.java │ │ │ │ ├── SecurityManager.java │ │ │ │ └── WebSecurityConfiguration.java │ │ │ ├── controller │ │ │ ├── PizzaController.java │ │ │ └── UserController.java │ │ │ ├── dto │ │ │ ├── IngredientDto.java │ │ │ ├── IngredientPizzaSummaryDto.java │ │ │ └── PizzaDto.java │ │ │ ├── enums │ │ │ └── PizzaEnum.java │ │ │ ├── grpc │ │ │ ├── configuration │ │ │ │ ├── Constants.java │ │ │ │ └── GrpcConfiguration.java │ │ │ ├── converter │ │ │ │ └── IngredientGrpcConverter.java │ │ │ ├── interceptor │ │ │ │ ├── AuthenticationInterceptor.java │ │ │ │ ├── ExceptionHandlerInterceptor.java │ │ │ │ └── RequestIdInterceptor.java │ │ │ ├── server │ │ │ │ ├── GrpcServer.java │ │ │ │ └── GrpcServerRunner.java │ │ │ └── service │ │ │ │ └── IngredientServiceGrpcImpl.java │ │ │ ├── model │ │ │ ├── Ingredient.java │ │ │ └── Pizza.java │ │ │ ├── repository │ │ │ ├── IngredientRepository.java │ │ │ ├── PizzaRepository.java │ │ │ └── base │ │ │ │ ├── ExtendedJpaRepository.java │ │ │ │ ├── ExtendedJpaRepositoryImpl.java │ │ │ │ ├── ExtendedQueryDslJpaRepository.java │ │ │ │ └── ExtendedQueryDslJpaRepositoryImpl.java │ │ │ ├── service │ │ │ ├── IngredientService.java │ │ │ ├── PizzaService.java │ │ │ └── cache │ │ │ │ └── UserBlacklistCacheService.java │ │ │ └── util │ │ │ ├── PageUtil.java │ │ │ └── converter │ │ │ ├── IngredientConverter.java │ │ │ ├── PizzaConverter.java │ │ │ └── enums │ │ │ ├── PizzaEnumConverter.java │ │ │ └── PizzaEnumDatabaseConverter.java │ └── resources │ │ ├── ValidationMessages.properties │ │ ├── application-dev.yml │ │ ├── application-docker.yml │ │ └── application.yml │ └── test │ ├── java │ └── com │ │ └── pizza │ │ ├── TestDataFactory.java │ │ ├── controller │ │ ├── BaseControllerTest.java │ │ ├── PizzaControllerTest.java │ │ └── UserControllerTest.java │ │ ├── grpc │ │ ├── converter │ │ │ └── IngredientGrpcConverterTest.java │ │ └── service │ │ │ └── IngredientServiceGrpcImplTest.java │ │ ├── repository │ │ ├── IngredientRepositoryTest.java │ │ └── PizzaRepositoryTest.java │ │ ├── service │ │ ├── IngredientServiceTest.java │ │ ├── PizzaServiceTest.java │ │ └── cache │ │ │ └── UserBlacklistCacheServiceTest.java │ │ └── util │ │ ├── PageUtilTest.java │ │ └── converter │ │ ├── IngredientConverterTest.java │ │ └── PizzaConverterTest.java │ └── resources │ ├── application.yml │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── registry-server ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── registryserver │ │ └── RegistryServerApplication.java │ └── resources │ ├── application-dev.yml │ ├── application-docker.yml │ └── application.yml ├── security-jwt-service ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── security │ │ │ └── jwt │ │ │ ├── SecurityJwtServiceApplication.java │ │ │ ├── application │ │ │ └── spring5microservices │ │ │ │ ├── enums │ │ │ │ └── RoleEnum.java │ │ │ │ ├── model │ │ │ │ ├── Role.java │ │ │ │ └── User.java │ │ │ │ ├── repository │ │ │ │ ├── RoleRepository.java │ │ │ │ └── UserRepository.java │ │ │ │ └── service │ │ │ │ ├── AuthenticationGenerator.java │ │ │ │ └── UserService.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ ├── WebfluxConfiguration.java │ │ │ ├── cache │ │ │ │ └── CacheConfiguration.java │ │ │ ├── documentation │ │ │ │ └── DocumentationConfiguration.java │ │ │ ├── rest │ │ │ │ ├── GlobalErrorWebExceptionHandler.java │ │ │ │ └── RestRoutes.java │ │ │ └── security │ │ │ │ ├── JweConfiguration.java │ │ │ │ ├── PasswordEncoderConfiguration.java │ │ │ │ └── WebSecurityConfiguration.java │ │ │ ├── controller │ │ │ ├── BaseController.java │ │ │ ├── CacheController.java │ │ │ └── SecurityController.java │ │ │ ├── dto │ │ │ ├── AuthenticationRequestDto.java │ │ │ └── RawAuthenticationInformationDto.java │ │ │ ├── enums │ │ │ ├── AuthenticationConfigurationEnum.java │ │ │ ├── SignatureAlgorithmEnum.java │ │ │ ├── TokenKeyEnum.java │ │ │ └── TokenType.java │ │ │ ├── exception │ │ │ ├── ClientNotFoundException.java │ │ │ └── TokenInvalidException.java │ │ │ ├── interfaces │ │ │ ├── IAuthenticationGenerator.java │ │ │ └── IUserService.java │ │ │ ├── model │ │ │ └── JwtClientDetails.java │ │ │ ├── repository │ │ │ └── JwtClientDetailsRepository.java │ │ │ ├── service │ │ │ ├── AuthenticationService.java │ │ │ ├── JwtClientDetailsService.java │ │ │ ├── SecurityService.java │ │ │ └── cache │ │ │ │ └── JwtClientDetailsCacheService.java │ │ │ └── util │ │ │ ├── JweUtil.java │ │ │ └── JwsUtil.java │ └── resources │ │ ├── ValidationMessages.properties │ │ ├── application-dev.yml │ │ ├── application-docker.yml │ │ └── application.yml │ └── test │ ├── java │ └── com │ │ └── security │ │ └── jwt │ │ ├── TestDataFactory.java │ │ ├── application │ │ └── spring5microservices │ │ │ ├── repository │ │ │ └── UserRepositoryTest.java │ │ │ └── service │ │ │ ├── AuthenticationGeneratorTest.java │ │ │ └── UserServiceTest.java │ │ ├── controller │ │ ├── BaseControllerTest.java │ │ ├── CacheControllerTest.java │ │ └── SecurityControllerTest.java │ │ ├── enums │ │ └── AuthenticationConfigurationEnumTest.java │ │ ├── repository │ │ └── JwtClientDetailsRepositoryTest.java │ │ ├── service │ │ ├── AuthenticationServiceTest.java │ │ ├── JwtClientDetailsServiceTest.java │ │ ├── SecurityServiceTest.java │ │ └── cache │ │ │ └── JwtClientDetailsCacheServiceTest.java │ │ └── util │ │ ├── JweUtilTest.java │ │ └── JwsUtilTest.java │ └── resources │ ├── application.yml │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── security-oauth-service ├── .gitignore ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── security │ │ │ └── oauth │ │ │ ├── SecurityOauthServiceApplication.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ ├── cache │ │ │ │ └── CacheConfiguration.java │ │ │ ├── rest │ │ │ │ └── RestRoutes.java │ │ │ └── security │ │ │ │ ├── AuthorizationServerConfig.java │ │ │ │ ├── PasswordEncoderConfiguration.java │ │ │ │ ├── WebSecurityConfiguration.java │ │ │ │ ├── jwt │ │ │ │ ├── CustomAccessTokenConverter.java │ │ │ │ └── JwtConfiguration.java │ │ │ │ └── oauth │ │ │ │ └── CustomJdbcClientDetailsService.java │ │ │ ├── enums │ │ │ └── RoleEnum.java │ │ │ ├── model │ │ │ ├── Role.java │ │ │ └── User.java │ │ │ ├── repository │ │ │ ├── RoleRepository.java │ │ │ └── UserRepository.java │ │ │ └── service │ │ │ ├── UserService.java │ │ │ └── cache │ │ │ └── ClientDetailsCacheService.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-docker.yml │ │ └── application.yml │ └── test │ ├── java │ └── com │ │ └── security │ │ └── oauth │ │ ├── repository │ │ └── UserRepositoryTest.java │ │ └── service │ │ ├── UserServiceTest.java │ │ └── cache │ │ └── ClientDetailsCacheServiceTest.java │ └── resources │ └── application.yml └── sql ├── MasterDatabase_Data.sql ├── MasterDatabase_Definition.sql ├── TestDatabase_Data.sql └── TestDatabase_Definition.sql /common/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/converter/BaseFromDtoToModelConverter.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.converter; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | /** 8 | * Parent interface of the all converters that allow ONLY from Dto to Model conversion. 9 | * 10 | * @param 11 | * Type of the Model to manage 12 | * @param 13 | * Type of the Dto to manage 14 | */ 15 | public interface BaseFromDtoToModelConverter extends BaseConverter { 16 | 17 | String errorMessage = "Operation not allowed in a BaseFromDtoToModelConverter converter"; 18 | 19 | 20 | @Override 21 | default D fromModelToDto(final M model) { 22 | throw new UnsupportedOperationException(errorMessage); 23 | } 24 | 25 | 26 | @Override 27 | default Optional fromModelToOptionalDto(final M model) { 28 | throw new UnsupportedOperationException(errorMessage); 29 | } 30 | 31 | 32 | @Override 33 | default List fromModelsToDtos(final Collection models) { 34 | throw new UnsupportedOperationException(errorMessage); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/converter/BaseFromModelToDtoConverter.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.converter; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | /** 8 | * Parent interface of the all converters that allow ONLY from Model to Dto conversion. 9 | * 10 | * @param 11 | * Type of the Model to manage 12 | * @param 13 | * Type of the Dto to manage 14 | */ 15 | public interface BaseFromModelToDtoConverter extends BaseConverter { 16 | 17 | String errorMessage = "Operation not allowed in a BaseFromModelToDtoConverter converter"; 18 | 19 | 20 | @Override 21 | default M fromDtoToModel(final D dto) { 22 | throw new UnsupportedOperationException(errorMessage); 23 | } 24 | 25 | 26 | @Override 27 | default Optional fromDtoToOptionalModel(final D dto) { 28 | throw new UnsupportedOperationException(errorMessage); 29 | } 30 | 31 | 32 | @Override 33 | default List fromDtosToModels(final Collection dtos) { 34 | throw new UnsupportedOperationException(errorMessage); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/converter/enums/BaseEnumConverter.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.converter.enums; 2 | 3 | /** 4 | * Parent interface of the all converters from {@link Enum} to "equivalent" {@code value} and vice versa. 5 | * 6 | * @param 7 | * Type of the {@link Enum} to manage 8 | * @param 9 | * Type of the "equivalent" {@code value} to manage 10 | */ 11 | public interface BaseEnumConverter, V> { 12 | 13 | /** 14 | * Create a new {@link Enum} using the given {@code value}. 15 | * 16 | * @param value 17 | * Value used to get the equivalent {@link Enum} 18 | * 19 | * @return equivalent {@link Enum} 20 | */ 21 | E fromValueToEnum(final V value); 22 | 23 | /** 24 | * Create a new "equivalent" {@code value} using the given {@link Enum}. 25 | * 26 | * @param enumValue 27 | * {@link Enum} that matches with returned {@code value} 28 | * 29 | * @return equivalent {@code value} 30 | */ 31 | V fromEnumToValue(final E enumValue); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/converter/enums/BaseFromEnumToValueConverter.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.converter.enums; 2 | 3 | /** 4 | * Parent interface of the all converters that allow ONLY from {@link Enum} to value conversion. 5 | * 6 | * @param 7 | * Type of the {@link Enum} to manage 8 | * @param 9 | * Type of the "equivalent" {@code value} to manage 10 | */ 11 | public interface BaseFromEnumToValueConverter, V> extends BaseEnumConverter { 12 | 13 | String errorMessage = "Operation not allowed in a BaseFromEnumToValueConverter converter"; 14 | 15 | 16 | @Override 17 | default E fromValueToEnum(final V value) { 18 | throw new UnsupportedOperationException(errorMessage); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/converter/enums/BaseFromValueToEnumConverter.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.converter.enums; 2 | 3 | /** 4 | * Parent interface of the all converters that allow ONLY from value to {@link Enum} conversion. 5 | * 6 | * @param 7 | * Type of the {@link Enum} to manage 8 | * @param 9 | * Type of the "equivalent" {@code value} to manage 10 | */ 11 | public interface BaseFromValueToEnumConverter, V> extends BaseEnumConverter { 12 | 13 | String errorMessage = "Operation not allowed in a BaseFromValueToEnumConverter converter"; 14 | 15 | 16 | @Override 17 | default V fromEnumToValue(final E enumValue) { 18 | throw new UnsupportedOperationException(errorMessage); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/dto/AuthenticationInformationDto.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | 11 | import java.io.Serializable; 12 | import java.util.Map; 13 | 14 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 15 | 16 | /** 17 | * Required information to send as response in the {@code login} request 18 | */ 19 | @AllArgsConstructor 20 | @Builder 21 | @EqualsAndHashCode(of = { "jwtId" }) 22 | @Data 23 | @NoArgsConstructor 24 | @Schema(description = "Returned data after authenticate a user") 25 | public class AuthenticationInformationDto implements Serializable { 26 | 27 | private static final long serialVersionUID = -4007535195077048326L; 28 | 29 | @Schema(description = "Identifier of every authentication information instance. Usually a UUID autogenerated value", requiredMode = RequiredMode.REQUIRED) 30 | @JsonProperty(value = "jti") 31 | private String jwtId; 32 | 33 | @Schema(description = "Token-based authentication to allow an application to access the microservices", requiredMode = RequiredMode.REQUIRED) 34 | @JsonProperty(value = "access_token") 35 | private String accessToken; 36 | 37 | @Schema(description = "Access token expiration time in seconds", requiredMode = RequiredMode.REQUIRED) 38 | @JsonProperty(value = "expires_in") 39 | private int expiresIn; 40 | 41 | @Schema(description = "Special token used to obtain additional access tokens", requiredMode = RequiredMode.REQUIRED) 42 | @JsonProperty(value = "refresh_token") 43 | private String refreshToken; 44 | 45 | @Schema(description = "What type is the provided access token. For example: Bearer", requiredMode = RequiredMode.REQUIRED) 46 | @JsonProperty(value = "token_type") 47 | private String tokenType; 48 | 49 | @Schema(description = "Values related with the internal mechanism to limit an application's access to a user's account") 50 | private String scope; 51 | 52 | @Schema(description = "Extra data returned by security service") 53 | private Map additionalInfo; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/dto/ErrorResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.dto; 2 | 3 | import com.spring5microservices.common.enums.RestApiErrorCode; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 13 | 14 | @Data 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Schema(description = "Information included in an API error response") 18 | public class ErrorResponseDto { 19 | 20 | @Schema(description = "Code with the root cause of the error", requiredMode = RequiredMode.REQUIRED) 21 | private RestApiErrorCode code; 22 | 23 | @Schema(description = "Details about the error") 24 | private List errors; 25 | 26 | public ErrorResponseDto(final RestApiErrorCode code) { 27 | this.code = code; 28 | this.errors = new ArrayList<>(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/dto/UsernameAuthoritiesDto.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import static java.util.Objects.isNull; 16 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 17 | 18 | @Builder 19 | @Data 20 | @EqualsAndHashCode(of = {"username"}) 21 | @NoArgsConstructor 22 | @Schema(description = "Authorization information about an specific user") 23 | public class UsernameAuthoritiesDto { 24 | 25 | @Schema(description = "Identifier of the logged user", requiredMode = RequiredMode.REQUIRED) 26 | private String username; 27 | 28 | @Schema(description = "Roles and/or permissions of the logged user", requiredMode = RequiredMode.REQUIRED) 29 | private Set authorities; 30 | 31 | @Schema(description = "Extra data returned by security service", requiredMode = RequiredMode.REQUIRED) 32 | private Map additionalInfo; 33 | 34 | public UsernameAuthoritiesDto(final String username, 35 | final Collection authorities, 36 | final Map additionalInfo) { 37 | this.username = username; 38 | this.authorities = isNull(authorities) 39 | ? new HashSet<>() 40 | : new HashSet<>(authorities); 41 | this.additionalInfo = isNull(additionalInfo) 42 | ? new HashMap<>() 43 | : new HashMap<>(additionalInfo); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/enums/ExtendedHttpStatus.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.enums; 2 | 3 | /** 4 | * Extended Http responses 5 | */ 6 | public enum ExtendedHttpStatus { 7 | 8 | TOKEN_EXPIRED(440, "The token has expired"); 9 | 10 | private final int value; 11 | private final String reasonPhrase; 12 | 13 | ExtendedHttpStatus(final int value, 14 | final String reasonPhrase) { 15 | this.value = value; 16 | this.reasonPhrase = reasonPhrase; 17 | } 18 | 19 | public int value() { 20 | return this.value; 21 | } 22 | 23 | public String getReasonPhrase() { 24 | return this.reasonPhrase; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/enums/RestApiErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.enums; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | 5 | /** 6 | * Common error codes for the Rest APIs 7 | */ 8 | public enum RestApiErrorCode { 9 | INTERNAL("Internal Error"), 10 | SECURITY("Security Error"), 11 | VALIDATION("Validation Error"); 12 | 13 | private final String value; 14 | 15 | RestApiErrorCode(final String value) { 16 | this.value = value; 17 | } 18 | 19 | @JsonValue 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/exception/JsonException.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.exception; 2 | 3 | /** 4 | * Thrown when there was a problem in the Json serialization/deserialization. 5 | */ 6 | public class JsonException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = -816675920559143640L; 9 | 10 | public JsonException() { 11 | super(); 12 | } 13 | 14 | public JsonException(String message) { 15 | super(message); 16 | } 17 | 18 | public JsonException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public JsonException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected JsonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/exception/TokenExpiredException.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.exception; 2 | 3 | /** 4 | * Thrown when a valid {@code token} has expired. 5 | */ 6 | public class TokenExpiredException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 7918212504449433874L; 9 | 10 | public TokenExpiredException() { 11 | super(); 12 | } 13 | 14 | public TokenExpiredException(String message) { 15 | super(message); 16 | } 17 | 18 | public TokenExpiredException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public TokenExpiredException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected TokenExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/exception/UnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.exception; 2 | 3 | /** 4 | * Thrown when the current operation is not allowed. 5 | */ 6 | public class UnauthorizedException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 7948232504549433866L; 9 | 10 | public UnauthorizedException() { 11 | super(); 12 | } 13 | 14 | public UnauthorizedException(String message) { 15 | super(message); 16 | } 17 | 18 | public UnauthorizedException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public UnauthorizedException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected UnauthorizedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/interfaces/consumer/PentaConsumer.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.interfaces.consumer; 2 | 3 | import java.util.Objects; 4 | import java.util.function.BiConsumer; 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Represents an operation that accepts five input arguments and returns no result. This is the five-arity specialization 9 | * of {@link Consumer} 10 | *

11 | * This is a functional interface whose functional method is {@link PentaConsumer#accept(Object, Object, Object, Object, Object)}. 12 | * 13 | * @param 14 | * The type of the first argument to the {@link PentaConsumer} 15 | * @param 16 | * The type of the second argument to the {@link PentaConsumer} 17 | * @param 18 | * The type of the third argument to the {@link PentaConsumer} 19 | * @param 20 | * The type of the fourth argument to the {@link PentaConsumer} 21 | * @param 22 | * The type of the fifth argument to the {@link PentaConsumer} 23 | * 24 | * @see {@link Consumer} and {@link BiConsumer} 25 | */ 26 | @FunctionalInterface 27 | public interface PentaConsumer { 28 | 29 | /** 30 | * Performs this operation on the given arguments. 31 | * 32 | * @param t1 33 | * The first {@link PentaConsumer} argument 34 | * @param t2 35 | * The second {@link PentaConsumer} argument 36 | * @param t3 37 | * The third {@link PentaConsumer} argument 38 | * @param t4 39 | * The fourth {@link PentaConsumer} argument 40 | * @param t5 41 | * The fifth {@link PentaConsumer} argument 42 | */ 43 | void accept(T1 t1, 44 | T2 t2, 45 | T3 t3, 46 | T4 t4, 47 | T5 t5); 48 | 49 | 50 | /** 51 | * Returns a composed {@link PentaConsumer} that performs, in sequence, this operation followed by the 52 | * {@code after} operation. 53 | * 54 | * @param after 55 | * {@link PentaConsumer} 56 | * 57 | * @throws NullPointerException if {@code after} is {@code null} 58 | */ 59 | default PentaConsumer andThen(PentaConsumer after) { 60 | Objects.requireNonNull(after); 61 | return (t1, t2, t3, t4, t5) -> { 62 | this.accept(t1, t2, t3, t4, t5); 63 | after.accept(t1, t2, t3, t4, t5); 64 | }; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/interfaces/consumer/QuadConsumer.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.interfaces.consumer; 2 | 3 | import java.util.Objects; 4 | import java.util.function.BiConsumer; 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Represents an operation that accepts four input arguments and returns no result. This is the four-arity specialization 9 | * of {@link Consumer} 10 | *

11 | * This is a functional interface whose functional method is {@link QuadConsumer#accept(Object, Object, Object, Object)}. 12 | * 13 | * @param 14 | * The type of the first argument to the {@link QuadConsumer} 15 | * @param 16 | * The type of the second argument to the {@link QuadConsumer} 17 | * @param 18 | * The type of the third argument to the {@link QuadConsumer} 19 | * @param 20 | * The type of the fourth argument to the {@link QuadConsumer} 21 | * 22 | * @see {@link Consumer} and {@link BiConsumer} 23 | */ 24 | @FunctionalInterface 25 | public interface QuadConsumer { 26 | 27 | /** 28 | * Performs this operation on the given arguments. 29 | * 30 | * @param t1 31 | * The first {@link QuadConsumer} argument 32 | * @param t2 33 | * The second {@link QuadConsumer} argument 34 | * @param t3 35 | * The third {@link QuadConsumer} argument 36 | * @param t4 37 | * The fourth {@link QuadConsumer} argument 38 | */ 39 | void accept(T1 t1, 40 | T2 t2, 41 | T3 t3, 42 | T4 t4); 43 | 44 | 45 | /** 46 | * Returns a composed {@link QuadConsumer} that performs, in sequence, this operation followed by the 47 | * {@code after} operation. 48 | * 49 | * @param after 50 | * {@link QuadConsumer} 51 | * 52 | * @throws NullPointerException if {@code after} is {@code null} 53 | */ 54 | default QuadConsumer andThen(QuadConsumer after) { 55 | Objects.requireNonNull(after); 56 | return (t1, t2, t3, t4) -> { 57 | this.accept(t1, t2, t3, t4); 58 | after.accept(t1, t2, t3, t4); 59 | }; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/interfaces/consumer/TriConsumer.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.interfaces.consumer; 2 | 3 | import java.util.Objects; 4 | import java.util.function.BiConsumer; 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Represents an operation that accepts three input arguments and returns no result. This is the three-arity specialization 9 | * of {@link Consumer} 10 | *

11 | * This is a functional interface whose functional method is {@link TriConsumer#accept(Object, Object, Object)}. 12 | * 13 | * @param 14 | * The type of the first argument to the {@link TriConsumer} 15 | * @param 16 | * The type of the second argument to the {@link TriConsumer} 17 | * @param 18 | * The type of the third argument to the {@link TriConsumer} 19 | * 20 | * @see {@link Consumer} and {@link BiConsumer} 21 | */ 22 | @FunctionalInterface 23 | public interface TriConsumer { 24 | 25 | /** 26 | * Performs this operation on the given arguments. 27 | * 28 | * @param t1 29 | * The first {@link TriConsumer} argument 30 | * @param t2 31 | * The second {@link TriConsumer} argument 32 | * @param t3 33 | * The third {@link TriConsumer} argument 34 | */ 35 | void accept(T1 t1, 36 | T2 t2, 37 | T3 t3); 38 | 39 | 40 | /** 41 | * Returns a composed {@link TriConsumer} that performs, in sequence, this operation followed by the 42 | * {@code after} operation. 43 | * 44 | * @param after 45 | * {@link TriConsumer} 46 | * 47 | * @throws NullPointerException if {@code after} is {@code null} 48 | */ 49 | default TriConsumer andThen(TriConsumer after) { 50 | Objects.requireNonNull(after); 51 | return (t1, t2, t3) -> { 52 | this.accept(t1, t2, t3); 53 | after.accept(t1, t2, t3); 54 | }; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/interfaces/function/TriFunction.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.interfaces.function; 2 | 3 | import java.util.function.BiFunction; 4 | import java.util.function.Function; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * Represents a {@link Function} that accepts three arguments and produces a result. This is the three-arity specialization 10 | * of {@link Function}. 11 | * 12 | *

This is a functional interface 13 | * whose functional method is {@link #apply(Object, Object, Object)}. 14 | * 15 | * @param 16 | * The type of the first argument to the {@link TriFunction} 17 | * @param 18 | * The type of the second argument to the {@link TriFunction} 19 | * @param 20 | * The type of the third argument to the {@link TriFunction} 21 | * @param 22 | * The type of the result of the {@link TriFunction} 23 | * 24 | * @see {@link Function} and {@link BiFunction} 25 | */ 26 | @FunctionalInterface 27 | public interface TriFunction { 28 | 29 | /** 30 | * Applies this {@link TriFunction} to the given arguments. 31 | * 32 | * @param t1 33 | * The first {@link TriFunction} argument 34 | * @param t2 35 | * The second {@link TriFunction} argument 36 | * @param t3 37 | * The third {@link TriFunction} argument 38 | * 39 | * @return the {@link TriFunction} result 40 | */ 41 | R apply(final T1 t1, 42 | final T2 t2, 43 | final T3 t3); 44 | 45 | 46 | /** 47 | * Returns a composed {@link TriFunction} that first applies this {@link TriFunction} to its input, and then 48 | * applies the {@code after} {@link Function} to the result. If evaluation of either function throws an exception, 49 | * it is relayed to the caller of the composed {@link TriFunction}. 50 | * 51 | * @param after 52 | * The {@link Function} to apply after this {@link TriFunction} 53 | * @param 54 | * The type of the output of the {@code after} {@link Function}, and of the composed {@link TriFunction} 55 | * 56 | * @return a composed {@link TriFunction} that first applies this {@link TriFunction} and then applies the 57 | * {@code after} {@link Function}. 58 | * 59 | * @throws NullPointerException if {@code after} is {@code null} 60 | */ 61 | default TriFunction andThen(final Function after) { 62 | requireNonNull(after, "after must be not null"); 63 | return (t1, t2, t3) -> 64 | after.apply( 65 | apply(t1, t2, t3) 66 | ); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/util/ComparatorUtil.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | import java.util.Comparator; 6 | 7 | @UtilityClass 8 | public class ComparatorUtil { 9 | 10 | /** 11 | * Returns {@link Comparator} keeping natural order but managing {@code null} values. 12 | * 13 | * @return null safe {@link Comparator} that considers the {@code null}s the smallest values 14 | */ 15 | public static > Comparator safeNaturalOrderNullFirst() { 16 | return Comparator.nullsFirst( 17 | Comparator.naturalOrder() 18 | ); 19 | } 20 | 21 | 22 | /** 23 | * Returns {@link Comparator} keeping natural order but managing {@code null} values. 24 | * 25 | * @return null safe {@link Comparator} that considers the {@code null}s the largest values 26 | */ 27 | public static > Comparator safeNaturalOrderNullLast() { 28 | return Comparator.nullsLast( 29 | Comparator.naturalOrder() 30 | ); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/util/validation/Validate.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util.validation; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * Defines how to validate a given instance using {@link Validation} functionality. If there are not verified rules 7 | * an {@link Invalid} instance must be returned with a {@link ValidationError} {@link Collection}. 8 | * 9 | * @param 10 | * Type of the instance to validate 11 | * 12 | * @see {@link Valid}, {@link Invalid} and {@link ValidationError} 13 | */ 14 | public interface Validate { 15 | 16 | /** 17 | * Validate the given instance. 18 | * 19 | * @param instanceToValidate 20 | * Instance to validate 21 | * 22 | * @return {@link Valid} if all rules were verified, 23 | * {@link Invalid} with a {@link ValidationError} {@link Collection} otherwise. 24 | */ 25 | Validation validate(final T instanceToValidate); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/util/validation/ValidationError.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util.validation; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | 6 | import static java.util.Optional.ofNullable; 7 | 8 | @Getter 9 | @EqualsAndHashCode 10 | public class ValidationError implements Comparable { 11 | 12 | private final int priority; // Greater means more priority 13 | private final String errorMessage; 14 | 15 | 16 | /** 17 | * Construct an {@code ValidationError}. 18 | * 19 | * @param priority 20 | * The importance of the current error 21 | * @param errorMessage 22 | * Error message 23 | */ 24 | private ValidationError(final int priority, 25 | final String errorMessage) { 26 | this.priority = priority; 27 | this.errorMessage = errorMessage; 28 | } 29 | 30 | 31 | /** 32 | * Returns an {@code ValidationError} adding the given {@code priority} and {@code errorMessage}. 33 | * 34 | * @param priority 35 | * The importance of the current error 36 | * @param errorMessage 37 | * Error message 38 | * 39 | * @return {@code ValidationError} 40 | */ 41 | public static ValidationError of(final int priority, 42 | final String errorMessage) { 43 | return new ValidationError( 44 | priority, 45 | errorMessage 46 | ); 47 | } 48 | 49 | 50 | @Override 51 | public int compareTo(final ValidationError other) { 52 | return ofNullable(other) 53 | .map(o -> priority - o.priority) 54 | .orElse(1); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/util/validator/enums/EnumHasInternalStringValue.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util.validator.enums; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 10 | import static java.lang.annotation.ElementType.FIELD; 11 | import static java.lang.annotation.ElementType.PARAMETER; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | /** 15 | * The annotated element must be included in an internal {@link String} property of the given accepted 16 | * {@link Class} of {@link Enum}. 17 | */ 18 | @Documented 19 | @Retention(RUNTIME) 20 | @Target({FIELD, ANNOTATION_TYPE, PARAMETER}) 21 | @Constraint(validatedBy = EnumHasInternalStringValueValidator.class) 22 | public @interface EnumHasInternalStringValue { 23 | 24 | String message() default "must be one of the values included in {values}"; 25 | 26 | Class[] groups() default {}; 27 | 28 | Class[] payload() default {}; 29 | 30 | /** 31 | * @return {@link Class} of {@link Enum} used to check the value 32 | */ 33 | Class>> enumClass(); 34 | 35 | /** 36 | * @return {@code true} if {@code null} is accepted as a valid value, {@code false} otherwise. 37 | */ 38 | boolean isNullAccepted() default false; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/util/validator/enums/EnumHasInternalStringValueValidator.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util.validator.enums; 2 | 3 | import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import static java.util.Objects.isNull; 12 | 13 | /** 14 | * Validates if the given {@link String} matches with one of the internal {@link String} property belonging to the 15 | * provided {@link Class} of {@link Enum} 16 | */ 17 | public class EnumHasInternalStringValueValidator implements ConstraintValidator { 18 | 19 | private static final String ERROR_MESSAGE_PARAMETER = "values"; 20 | 21 | private List enumValidValues; 22 | private String constraintTemplate; 23 | private boolean isNullAccepted; 24 | 25 | 26 | @Override 27 | @SuppressWarnings("unchecked") 28 | public void initialize(final EnumHasInternalStringValue hasInternalStringValue) { 29 | enumValidValues = Arrays.stream(hasInternalStringValue.enumClass().getEnumConstants()) 30 | .map(e -> ((IEnumInternalPropertyValue)e).getInternalPropertyValue()) 31 | .collect(Collectors.toList()); 32 | constraintTemplate = hasInternalStringValue.message(); 33 | isNullAccepted = hasInternalStringValue.isNullAccepted(); 34 | } 35 | 36 | 37 | @Override 38 | public boolean isValid(final String value, 39 | final ConstraintValidatorContext context) { 40 | boolean isValid = 41 | isNull(value) 42 | ? isNullAccepted 43 | : enumValidValues.contains(value); 44 | 45 | if (!isValid) { 46 | HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class); 47 | hibernateContext.disableDefaultConstraintViolation(); 48 | hibernateContext.addMessageParameter( 49 | ERROR_MESSAGE_PARAMETER, 50 | enumValidValues 51 | ) 52 | .buildConstraintViolationWithTemplate(constraintTemplate) 53 | .addConstraintViolation(); 54 | } 55 | return isValid; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/com/spring5microservices/common/util/validator/enums/IEnumInternalPropertyValue.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util.validator.enums; 2 | 3 | /** 4 | * Used to get the value of an internal property in an {@link Enum}. 5 | */ 6 | public interface IEnumInternalPropertyValue { 7 | 8 | /** 9 | * Get the value of an internal property included in the {@link Enum}. 10 | */ 11 | T getInternalPropertyValue(); 12 | } 13 | -------------------------------------------------------------------------------- /common/src/test/java/com/spring5microservices/common/PizzaDto.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common; 2 | 3 | import com.spring5microservices.common.util.validator.enums.EnumHasInternalStringValue; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | @AllArgsConstructor 10 | @EqualsAndHashCode 11 | @Data 12 | @NoArgsConstructor 13 | public class PizzaDto { 14 | 15 | @EnumHasInternalStringValue(enumClass=PizzaEnum.class) 16 | private String name; 17 | 18 | private Double cost; 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /common/src/test/java/com/spring5microservices/common/PizzaEnum.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common; 2 | 3 | import com.spring5microservices.common.util.validator.enums.IEnumInternalPropertyValue; 4 | 5 | public enum PizzaEnum implements IEnumInternalPropertyValue { 6 | MARGUERITA("Margherita"), 7 | CARBONARA("Carbonara"); 8 | 9 | private String databaseValue; 10 | 11 | PizzaEnum(String databaseValue) { 12 | this.databaseValue = databaseValue; 13 | } 14 | 15 | @Override 16 | public String getInternalPropertyValue() { 17 | return this.databaseValue; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/test/java/com/spring5microservices/common/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | @AllArgsConstructor 9 | @EqualsAndHashCode 10 | @Data 11 | @NoArgsConstructor 12 | public class UserDto { 13 | 14 | private Long id; 15 | private String name; 16 | private String address; 17 | private Integer age; 18 | private String birthday; 19 | private String email; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /common/src/test/java/com/spring5microservices/common/util/validator/enums/EnumHasInternalStringValueValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.common.util.validator.enums; 2 | 3 | import javax.validation.ConstraintViolation; 4 | import javax.validation.Validation; 5 | import javax.validation.Validator; 6 | import javax.validation.ValidatorFactory; 7 | 8 | import com.spring5microservices.common.PizzaDto; 9 | import com.spring5microservices.common.PizzaEnum; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.DisplayName; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.extension.ExtendWith; 14 | import org.springframework.test.context.junit.jupiter.SpringExtension; 15 | 16 | import java.util.Set; 17 | 18 | import static com.spring5microservices.common.PizzaEnum.CARBONARA; 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | @ExtendWith(SpringExtension.class) 23 | public class EnumHasInternalStringValueValidatorTest { 24 | 25 | private Validator validator; 26 | 27 | @BeforeEach 28 | public void setup() { 29 | ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 30 | validator = factory.getValidator(); 31 | } 32 | 33 | 34 | @Test 35 | @DisplayName("isValid: when given string value is not in enum then validation fails") 36 | public void whenGivenStringValueIsNotInEnum_thenValidationFails() { 37 | // Given 38 | PizzaDto dto = new PizzaDto(CARBONARA.getInternalPropertyValue() + PizzaEnum.MARGUERITA.getInternalPropertyValue(), 5D); 39 | 40 | // When 41 | Set> violations = validator.validate(dto); 42 | 43 | // Then 44 | assertEquals(1, violations.size()); 45 | 46 | ConstraintViolation error = violations.iterator().next(); 47 | assertEquals("name", error.getPropertyPath().toString()); 48 | assertEquals("must be one of the values included in [Margherita, Carbonara]", error.getMessage()); 49 | } 50 | 51 | 52 | @Test 53 | @DisplayName("isValid: when given string value is in enum then validation Succeeds") 54 | public void whenGivenStringValueIsInEnum_thenValidationSucceeds() { 55 | // Given 56 | PizzaDto dto = new PizzaDto(CARBONARA.getInternalPropertyValue(), 5D); 57 | 58 | // When 59 | Set> violations = validator.validate(dto); 60 | 61 | // Then 62 | assertTrue(violations.isEmpty()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /common/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | # To allow mocking final classes 2 | mock-maker-inline -------------------------------------------------------------------------------- /config-server/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd 29 | -------------------------------------------------------------------------------- /config-server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the local maven and the application to the Docker working directory 8 | COPY mvnw . 9 | COPY .mvn .mvn 10 | COPY pom.xml . 11 | COPY src src 12 | 13 | RUN ./mvnw install -DskipTests 14 | RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 15 | 16 | 17 | # Multi-Stage Build 18 | FROM eclipse-temurin:17-jre-alpine 19 | 20 | # Port used to connect with the application 21 | ENV SERVER_PORT 8888 22 | EXPOSE $SERVER_PORT 23 | 24 | # Default Spring profile 25 | ENV SPRING_PROFILES_ACTIVE docker 26 | 27 | VOLUME /tmp 28 | 29 | ARG DEPENDENCY=/app/target/dependency 30 | 31 | COPY --from=builder ${DEPENDENCY}/BOOT-INF/lib /app/lib 32 | COPY --from=builder ${DEPENDENCY}/META-INF /app/META-INF 33 | COPY --from=builder ${DEPENDENCY}/BOOT-INF/classes /app 34 | 35 | # Use the Spring Boot main application class to run it 36 | ENTRYPOINT ["java", "-cp", "app:app/lib/*", "com.configserver.ConfigServerApplication"] 37 | 38 | 39 | # ------------------------------------------------------------------------------------------ 40 | # COMMANDS: 41 | # 42 | # 1. Build the image in local: 43 | # 44 | # docker build -t config-server . -f ./Dockerfile 45 | # 46 | # 2. Build and launch the container: 47 | # 48 | # docker run -p 8888:8888 --rm --name config-server --network Spring5Microservices config-server 49 | -------------------------------------------------------------------------------- /config-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.18 9 | 10 | 11 | com.configserver 12 | config-server 13 | 1.1.8 14 | config-server 15 | Configuration server for Spring 5 with microservices 16 | 17 | 18 | 17 19 | 17 20 | 17 21 | 2021.0.9 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-config-server 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-starter-netflix-eureka-client 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-actuator 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-dependencies 45 | ${spring-cloud.version} 46 | pom 47 | import 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | ${project.parent.version} 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /config-server/src/main/java/com/configserver/ConfigServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.configserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @SpringBootApplication 8 | @EnableConfigServer 9 | public class ConfigServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ConfigServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /config-server/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8888 3 | 4 | eureka: 5 | client: 6 | # Pull down a local copy of the registry 7 | fetchRegistry: true 8 | healthcheck: 9 | enabled: true 10 | registerWithEureka: true 11 | serviceUrl: 12 | defaultZone: http://localhost:8761/eureka/ 13 | instance: 14 | # Indicates the duration the server waits since it received the last heartbeat before it can evict an instance 15 | # from its registry. This value should be greater than leaseRenewalIntervalInSeconds 16 | leaseExpirationDurationInSeconds: 75 17 | # Indicates the interval of heartbeats that the client sends to the server. The default value is 30 seconds 18 | # which means that the client will send one heartbeat every 30 seconds. 19 | leaseRenewalIntervalInSeconds: 60 20 | # Register the IP of the service rather than the server name (useful when there is no a server-based 21 | # environment where a service is assigned a DNS-backed host name, usually in Docker for example) 22 | preferIpAddress: true 23 | 24 | # Required to expose the routes included actuator: actuator/routes 25 | management: 26 | endpoints: 27 | web: 28 | exposure: 29 | include: health, info -------------------------------------------------------------------------------- /config-server/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:8888} 3 | 4 | eureka: 5 | client: 6 | # Pull down a local copy of the registry 7 | fetchRegistry: true 8 | healthcheck: 9 | enabled: true 10 | registerWithEureka: true 11 | serviceUrl: 12 | defaultZone: ${EUREKA_SERVER:http://registry-server:8761/eureka/} 13 | instance: 14 | # Indicates the duration the server waits since it received the last heartbeat before it can evict an instance 15 | # from its registry. This value should be greater than leaseRenewalIntervalInSeconds 16 | leaseExpirationDurationInSeconds: 75 17 | # Indicates the interval of heartbeats that the client sends to the server. The default value is 30 seconds 18 | # which means that the client will send one heartbeat every 30 seconds. 19 | leaseRenewalIntervalInSeconds: 60 20 | # Register the IP of the service rather than the server name (useful when there is no a server-based 21 | # environment where a service is assigned a DNS-backed host name, usually in Docker for example) 22 | preferIpAddress: true 23 | 24 | # Required to expose the routes included actuator: actuator/routes 25 | management: 26 | endpoints: 27 | web: 28 | exposure: 29 | include: health, info -------------------------------------------------------------------------------- /config-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: config-server 4 | cloud: 5 | config: 6 | server: 7 | encrypt: 8 | # We will send encrypted properties 9 | enabled: false 10 | git: 11 | uri: https://github.com/doctore/Spring5Microservices_ConfigServerData.git 12 | searchPaths: gateway-server,pizza-service,order-service,security-jwt-service,security-oauth-service 13 | defaultLabel: master -------------------------------------------------------------------------------- /documentation/CommunitationDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/CommunitationDiagram.png -------------------------------------------------------------------------------- /documentation/DockerImages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/DockerImages.png -------------------------------------------------------------------------------- /documentation/Encryption.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/Encryption.png -------------------------------------------------------------------------------- /documentation/GrpcCommunicationDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/GrpcCommunicationDiagram.png -------------------------------------------------------------------------------- /documentation/OrderService.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/OrderService.png -------------------------------------------------------------------------------- /documentation/PizzaService.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/PizzaService.png -------------------------------------------------------------------------------- /documentation/SecurityJwtService_AuthorizationInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityJwtService_AuthorizationInfo.png -------------------------------------------------------------------------------- /documentation/SecurityJwtService_Credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityJwtService_Credentials.png -------------------------------------------------------------------------------- /documentation/SecurityJwtService_Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityJwtService_Login.png -------------------------------------------------------------------------------- /documentation/SecurityJwtService_Refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityJwtService_Refresh.png -------------------------------------------------------------------------------- /documentation/SecurityOauthService_AuthorizationInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityOauthService_AuthorizationInfo.png -------------------------------------------------------------------------------- /documentation/SecurityOauthService_Credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityOauthService_Credentials.png -------------------------------------------------------------------------------- /documentation/SecurityOauthService_Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityOauthService_Login.png -------------------------------------------------------------------------------- /documentation/SecurityOauthService_Refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/SecurityOauthService_Refresh.png -------------------------------------------------------------------------------- /documentation/Swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/Swagger.png -------------------------------------------------------------------------------- /documentation/WebServiceWithGRPC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doctore/Spring5Microservices/dc3e975d5635469c884b7dea057746130cbde0b5/documentation/WebServiceWithGRPC.png -------------------------------------------------------------------------------- /gateway-server/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd 29 | -------------------------------------------------------------------------------- /gateway-server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the application to the Docker working directory 8 | COPY target/*.jar app.jar 9 | 10 | # Extract layers of application's jar 11 | RUN java -Djarmode=layertools -jar app.jar extract 12 | 13 | 14 | # Multi-Stage Build 15 | FROM eclipse-temurin:17-jre-alpine 16 | 17 | # Port used to connect with the application 18 | ENV SERVER_PORT 5555 19 | EXPOSE $SERVER_PORT 20 | 21 | # Default symmetric encryption key 22 | ARG ENCRYPT_KEY=ENCRYPT_KEY 23 | ENV ENCRYPT_KEY ${ENCRYPT_KEY} 24 | 25 | # Default Spring profile 26 | ENV SPRING_PROFILES_ACTIVE docker 27 | 28 | VOLUME /tmp 29 | 30 | ARG DEPENDENCY=/app 31 | 32 | COPY --from=builder ${DEPENDENCY}/dependencies/ ./ 33 | COPY --from=builder ${DEPENDENCY}/spring-boot-loader/ ./ 34 | COPY --from=builder ${DEPENDENCY}/snapshot-dependencies/ ./ 35 | COPY --from=builder ${DEPENDENCY}/application/ ./ 36 | 37 | # Copy the Spring Boot fat JarLauncher into the image and use it to run the application 38 | ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] 39 | 40 | 41 | # ------------------------------------------------------------------------------------------ 42 | # COMMANDS: 43 | # 44 | # 1. Build the image in local: 45 | # 46 | # docker build -t gateway-server . -f ./Dockerfile 47 | # 48 | # 2. Build and launch the container: 49 | # 50 | # docker run -p 5555:5555 --rm --name gateway-server --add-host=host.docker.internal:host-gateway --network Spring5Microservices gateway-server 51 | -------------------------------------------------------------------------------- /gateway-server/src/main/java/com/gatewayserver/GatewayServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.gatewayserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @SpringBootApplication 8 | @EnableDiscoveryClient 9 | public class GatewayServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(GatewayServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /gateway-server/src/main/java/com/gatewayserver/configuration/rest/RestRoutes.java: -------------------------------------------------------------------------------- 1 | package com.gatewayserver.configuration.rest; 2 | 3 | /** 4 | * Used to define the REST API routes included in the project 5 | */ 6 | public final class RestRoutes { 7 | 8 | public static final class CIRCUIT_BREAKER { 9 | public static final String ROOT = "/failed"; 10 | public static final String ORDER_SERVICE = "/order-service/redirect"; 11 | public static final String PIZZA_SERVICE = "/pizza-service/redirect"; 12 | public static final String SECURITY_SERVICE = "/security-jwt-service/redirect"; 13 | public static final String SECURITY_OAUTH_SERVICE = "/security-oauth-service/redirect"; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /gateway-server/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 5555 3 | 4 | spring: 5 | config: 6 | import: configserver:http://localhost:8888 -------------------------------------------------------------------------------- /gateway-server/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:5555} 3 | 4 | spring: 5 | config: 6 | import: configserver:${CONFIG_SERVER:http://config-server:8888} -------------------------------------------------------------------------------- /gateway-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: gateway-server -------------------------------------------------------------------------------- /grpc-api/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd -------------------------------------------------------------------------------- /grpc-api/src/main/java/com/spring5microservices/grpc/configuration/GrpcHeader.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.grpc.configuration; 2 | 3 | import io.grpc.Metadata; 4 | import lombok.experimental.UtilityClass; 5 | 6 | /** 7 | * Headers used in communication between gRPC server and client. 8 | */ 9 | @UtilityClass 10 | public class GrpcHeader { 11 | 12 | // Unique request's identifier 13 | public static Metadata.Key REQUEST_ID = 14 | Metadata.Key.of( 15 | "x-request-id", 16 | Metadata.ASCII_STRING_MARSHALLER 17 | ); 18 | 19 | // To include authorization data 20 | public static Metadata.Key AUTHORIZATION = 21 | Metadata.Key.of( 22 | "Authorization", 23 | Metadata.ASCII_STRING_MARSHALLER 24 | ); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /grpc-api/src/main/java/com/spring5microservices/grpc/security/BasicCredential.java: -------------------------------------------------------------------------------- 1 | package com.spring5microservices.grpc.security; 2 | 3 | import com.spring5microservices.common.util.HttpUtil; 4 | import com.spring5microservices.grpc.configuration.GrpcHeader; 5 | import io.grpc.CallCredentials; 6 | import io.grpc.Metadata; 7 | 8 | import java.util.concurrent.Executor; 9 | 10 | import static io.grpc.Status.UNAUTHENTICATED; 11 | 12 | /** 13 | * {@link CallCredentials} implementation, which carries the Basic Authentication that will be propagated from gRPC 14 | * client to the server in the request metadata with the "Authorization" key. 15 | */ 16 | public final class BasicCredential extends CallCredentials { 17 | 18 | private final String basicAuthorization; 19 | 20 | 21 | private BasicCredential(final String basicAuthorization) { 22 | this.basicAuthorization = basicAuthorization; 23 | } 24 | 25 | 26 | public static BasicCredential of(final String username, 27 | final String password) { 28 | return new BasicCredential( 29 | HttpUtil.encodeBasicAuthentication( 30 | username, 31 | password 32 | ) 33 | ); 34 | } 35 | 36 | 37 | @Override 38 | public void applyRequestMetadata(final RequestInfo requestInfo, 39 | final Executor executor, 40 | final MetadataApplier metadataApplier) { 41 | executor.execute(() -> { 42 | try { 43 | Metadata headers = new Metadata(); 44 | headers.put( 45 | GrpcHeader.AUTHORIZATION, 46 | basicAuthorization 47 | ); 48 | metadataApplier.apply(headers); 49 | 50 | } catch (Throwable e) { 51 | metadataApplier.fail( 52 | UNAUTHENTICATED 53 | .withDescription("There was a problem with the basic authorization included in the request") 54 | .withCause(e) 55 | ); 56 | } 57 | }); 58 | } 59 | 60 | 61 | @Override 62 | public void thisUsesUnstableApi() { 63 | // No 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /grpc-api/src/main/resources/proto/ingredient.proto: -------------------------------------------------------------------------------- 1 | // Proto version used 2 | syntax = "proto3"; 3 | 4 | // Separate .java files will be generated for each of the Java classes/enums/etc. included in this .proto file 5 | option java_multiple_files = true; 6 | 7 | // Package used for your generated Java classes. 8 | option java_package = "com.spring5microservices.grpc"; 9 | 10 | // Class name (and hence the file name) for the wrapper Java class to generate 11 | option java_outer_classname = "IngredientServiceProto"; 12 | 13 | 14 | // Interface exported by the server. 15 | service IngredientService { 16 | 17 | // A server-to-client streaming RPC: 18 | // Obtains the list of Ingredient related with the given Pizza 19 | rpc getIngredients(PizzaRequest) returns (stream IngredientResponse) {} 20 | 21 | } 22 | 23 | 24 | // Pizza's identifier 25 | message PizzaRequest { 26 | int32 id = 1; 27 | } 28 | 29 | 30 | // Individual ingredients used in a Pizza 31 | message IngredientResponse { 32 | int32 id = 1; 33 | string name = 2; 34 | } 35 | -------------------------------------------------------------------------------- /order-service/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd -------------------------------------------------------------------------------- /order-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the application to the Docker working directory 8 | COPY target/*.jar app.jar 9 | 10 | # Extract layers of application's jar 11 | RUN java -Djarmode=layertools -jar app.jar extract 12 | 13 | 14 | # Multi-Stage Build 15 | FROM eclipse-temurin:17-jre-alpine 16 | 17 | # Port used to connect with the application 18 | ENV SERVER_PORT 8081 19 | EXPOSE $SERVER_PORT 20 | 21 | # Default symmetric encryption key 22 | ARG ENCRYPT_KEY=ENCRYPT_KEY 23 | ENV ENCRYPT_KEY ${ENCRYPT_KEY} 24 | 25 | # Default Spring profile 26 | ENV SPRING_PROFILES_ACTIVE docker 27 | 28 | VOLUME /tmp 29 | 30 | ARG DEPENDENCY=/app 31 | 32 | COPY --from=builder ${DEPENDENCY}/dependencies/ ./ 33 | COPY --from=builder ${DEPENDENCY}/spring-boot-loader/ ./ 34 | COPY --from=builder ${DEPENDENCY}/snapshot-dependencies/ ./ 35 | COPY --from=builder ${DEPENDENCY}/application/ ./ 36 | 37 | # Copy the Spring Boot fat JarLauncher into the image and use it to run the application 38 | ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] 39 | 40 | 41 | # ------------------------------------------------------------------------------------------ 42 | # COMMANDS: 43 | # 44 | # 1. Build the image in local: 45 | # 46 | # docker build -t order-service . -f ./Dockerfile 47 | # 48 | # 2. Build and launch the container: 49 | # 50 | # docker run -p 8081:8081 --rm --name order-service --add-host=host.docker.internal:host-gateway --network Spring5Microservices order-service 51 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/OrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.order; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | 7 | @EnableFeignClients 8 | @SpringBootApplication 9 | public class OrderServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(OrderServiceApplication.class, args); 13 | 14 | /* 15 | ConfigurableApplicationContext context = SpringApplication.run(OrderServiceApplication.class, args); 16 | OrderDao orderDao = context.getBean(OrderDao.class); 17 | Optional order = Optional.empty(); 18 | try { 19 | Set orders = orderDao.fetchPageToOrderDtoByIdWithOrderLineDto(0, 2); 20 | } catch (Exception e) {} 21 | */ 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/annotation/RoleAdmin.java: -------------------------------------------------------------------------------- 1 | package com.order.annotation; 2 | 3 | import com.order.configuration.Constants; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Documented 15 | @Retention(RUNTIME) 16 | @Target({METHOD, TYPE}) 17 | @PreAuthorize("hasAuthority('" + Constants.ROLE_ADMIN +"')") 18 | public @interface RoleAdmin { 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/annotation/RoleAdminOrUser.java: -------------------------------------------------------------------------------- 1 | package com.order.annotation; 2 | 3 | import com.order.configuration.Constants; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Documented 15 | @Retention(RUNTIME) 16 | @Target({METHOD, TYPE}) 17 | @PreAuthorize("hasAnyAuthority('" + Constants.ROLE_ADMIN +"','" + Constants.ROLE_USER + "')") 18 | public @interface RoleAdminOrUser { 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | package com.order.configuration; 2 | 3 | /** 4 | * Global values used in different part of the application. 5 | */ 6 | public class Constants { 7 | 8 | // Existing roles to manage the authorizations 9 | public static final String ROLE_ADMIN = "ADMIN"; 10 | public static final String ROLE_USER = "USER"; 11 | 12 | // Token configuration 13 | public static final String TOKEN_PREFIX = "Bearer "; 14 | 15 | // Path of the folders in the application 16 | public static final class PATH { 17 | public static final String CONTROLLER = "com.order.controller"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.order.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | import static org.springframework.http.HttpMethod.DELETE; 9 | import static org.springframework.http.HttpMethod.GET; 10 | import static org.springframework.http.HttpMethod.HEAD; 11 | import static org.springframework.http.HttpMethod.OPTIONS; 12 | import static org.springframework.http.HttpMethod.PATCH; 13 | import static org.springframework.http.HttpMethod.POST; 14 | import static org.springframework.http.HttpMethod.PUT; 15 | 16 | @Configuration 17 | @EnableWebMvc 18 | public class WebConfiguration implements WebMvcConfigurer { 19 | 20 | @Override 21 | public void addCorsMappings(CorsRegistry corsRegistry) { 22 | corsRegistry 23 | .addMapping("/**") 24 | .allowedMethods( 25 | DELETE.name(), 26 | GET.name(), 27 | HEAD.name(), 28 | OPTIONS.name(), 29 | PATCH.name(), 30 | POST.name(), 31 | PUT.name() 32 | ); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/configuration/rest/RestRoutes.java: -------------------------------------------------------------------------------- 1 | package com.order.configuration.rest; 2 | 3 | /** 4 | * Used to define the REST API routes included in the project 5 | */ 6 | public final class RestRoutes { 7 | 8 | public static final String ROOT = "/order"; 9 | 10 | public static final class ORDER { 11 | public static final String ROOT = RestRoutes.ROOT; 12 | public static final String WITH_ORDERLINES = "/withOrderlines"; 13 | } 14 | 15 | public static final class INGREDIENT { 16 | public static final String ROOT = "/ingredient"; 17 | public static final String SUMMARY = "/summary"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/configuration/security/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.order.configuration.security; 2 | 3 | import feign.auth.BasicAuthRequestInterceptor; 4 | import lombok.Getter; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * Configuration properties related with authentication/authorization (web service used for it) 11 | */ 12 | @Getter 13 | @Configuration 14 | public class SecurityConfiguration { 15 | 16 | public static final String TOKEN_PREFIX = "Bearer "; 17 | 18 | @Value("${security.restApi.authenticationInformation}") 19 | private String authenticationInformationWebService; 20 | 21 | @Value("${security.restApi.clientId}") 22 | private String clientId; 23 | 24 | @Value("${security.restApi.clientPassword}") 25 | private String clientPassword; 26 | 27 | 28 | @Bean 29 | public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { 30 | return new BasicAuthRequestInterceptor(clientId, clientPassword); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/configuration/security/client/SecurityServerRestClient.java: -------------------------------------------------------------------------------- 1 | package com.order.configuration.security.client; 2 | 3 | import com.order.dto.UsernameAuthoritiesDto; 4 | import org.springframework.cloud.openfeign.FeignClient; 5 | import org.springframework.web.bind.annotation.PostMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | 8 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 9 | 10 | /** 11 | * REST endpoint definitions used to connect to the security microservice. 12 | */ 13 | @FeignClient(value = "securityServer", url = "${security.restApi.authenticationInformation}") 14 | public interface SecurityServerRestClient { 15 | 16 | @PostMapping(value = "/check_token", produces = APPLICATION_JSON_VALUE) 17 | UsernameAuthoritiesDto checkToken(@RequestParam(value="token") String token); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/configuration/security/filter/SecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.order.configuration.security.filter; 2 | 3 | import com.order.configuration.security.SecurityConfiguration; 4 | import com.order.configuration.security.SecurityManager; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.filter.OncePerRequestFilter; 11 | 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.ServletException; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 19 | 20 | /** 21 | * Invoke security validations for the given Http requests 22 | */ 23 | @Component 24 | public class SecurityFilter extends OncePerRequestFilter { 25 | 26 | private final SecurityManager securityManager; 27 | 28 | 29 | @Autowired 30 | public SecurityFilter(@Lazy final SecurityManager securityManager) { 31 | this.securityManager = securityManager; 32 | } 33 | 34 | 35 | @Override 36 | protected void doFilterInternal(final HttpServletRequest request, 37 | final HttpServletResponse response, 38 | final FilterChain filterChain) throws ServletException, IOException { 39 | String token = request.getHeader(AUTHORIZATION); 40 | if (StringUtils.hasText(token)) { 41 | String tokenData = token.replace(SecurityConfiguration.TOKEN_PREFIX, ""); 42 | securityManager.authenticate(tokenData) 43 | .ifPresent(SecurityContextHolder.getContext()::setAuthentication); 44 | } 45 | filterChain.doFilter(request, response); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/dto/IngredientAmountDto.java: -------------------------------------------------------------------------------- 1 | package com.order.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Positive; 11 | import javax.validation.constraints.Size; 12 | 13 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 14 | 15 | @AllArgsConstructor 16 | @EqualsAndHashCode(of = { "name" }) 17 | @Data 18 | @NoArgsConstructor 19 | @Schema(description = "Used to group the ingredients included in every order") 20 | public class IngredientAmountDto { 21 | 22 | @Schema(description = "Name", requiredMode = RequiredMode.REQUIRED) 23 | @NotNull 24 | @Size(min = 1, max = 64) 25 | private String name; 26 | 27 | @Schema(description = "amount", requiredMode = RequiredMode.REQUIRED) 28 | @NotNull 29 | @Positive 30 | private Integer amount; 31 | 32 | } -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/dto/OrderDto.java: -------------------------------------------------------------------------------- 1 | package com.order.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import javax.validation.Valid; 11 | import javax.validation.constraints.NotNull; 12 | import javax.validation.constraints.Size; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 17 | 18 | @AllArgsConstructor 19 | @Builder 20 | @EqualsAndHashCode(of = { "code" }) 21 | @Data 22 | @NoArgsConstructor 23 | @Schema(description = "Information related with an order") 24 | public class OrderDto { 25 | 26 | @Schema(description = "Internal unique identifier", requiredMode = RequiredMode.REQUIRED) 27 | private Integer id; 28 | 29 | @Schema(description = "Unique identifier of the order", requiredMode = RequiredMode.REQUIRED) 30 | @NotNull 31 | @Size(min = 1, max = 64) 32 | private String code; 33 | 34 | @Schema(description = "When the order was created", requiredMode = RequiredMode.REQUIRED) 35 | @NotNull 36 | private Date created; 37 | 38 | @Schema(description = "List of order lines", requiredMode = RequiredMode.REQUIRED) 39 | @Valid 40 | List orderLines; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/dto/OrderLineDto.java: -------------------------------------------------------------------------------- 1 | package com.order.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.Valid; 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Positive; 12 | import java.util.Objects; 13 | 14 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 15 | 16 | @AllArgsConstructor 17 | @Builder 18 | @Data 19 | @NoArgsConstructor 20 | @Schema(description = "Information related with the lines of an order") 21 | public class OrderLineDto { 22 | 23 | @Schema(description = "Internal unique identifier", requiredMode = RequiredMode.REQUIRED) 24 | private Integer id; 25 | 26 | @Schema(description = "Order related", requiredMode = RequiredMode.REQUIRED) 27 | private Integer orderId; 28 | 29 | @Schema(description = "Pizza included in this line", requiredMode = RequiredMode.REQUIRED) 30 | @NotNull 31 | @Valid 32 | private PizzaDto pizza; 33 | 34 | @Schema(description = "Number of pizzas", requiredMode = RequiredMode.REQUIRED) 35 | @NotNull 36 | @Positive 37 | private Short amount; 38 | 39 | @Schema(description = "Cost", requiredMode = RequiredMode.REQUIRED) 40 | @NotNull 41 | @Positive 42 | private Double cost; 43 | 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | OrderLineDto orderLine = (OrderLineDto) o; 50 | return null == id ? pizza.equals(orderLine.pizza) : id.equals(orderLine.id); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return null == id ? Objects.hash(pizza) : Objects.hash(id); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/dto/PizzaDto.java: -------------------------------------------------------------------------------- 1 | package com.order.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Positive; 12 | import javax.validation.constraints.Size; 13 | 14 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 15 | 16 | @AllArgsConstructor 17 | @Builder 18 | @EqualsAndHashCode(of = {"code"}) 19 | @Data 20 | @NoArgsConstructor 21 | @Schema(description = "Information related with a pizza") 22 | public class PizzaDto { 23 | 24 | @Schema(description = "Internal unique identifier", requiredMode = RequiredMode.REQUIRED) 25 | private Short id; 26 | 27 | @Schema(description = "Name", requiredMode = RequiredMode.REQUIRED) 28 | @NotNull 29 | @Size(min=1, max=64) 30 | private String name; 31 | 32 | @Schema(description = "Cost", requiredMode = RequiredMode.REQUIRED) 33 | @NotNull 34 | @Positive 35 | private Double cost; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/dto/UsernameAuthoritiesDto.java: -------------------------------------------------------------------------------- 1 | package com.order.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Set; 10 | 11 | /** 12 | * Class used to receive the authorization information related with logged users 13 | */ 14 | @AllArgsConstructor 15 | @EqualsAndHashCode(of = {"username"}) 16 | @Data 17 | @NoArgsConstructor 18 | public class UsernameAuthoritiesDto { 19 | 20 | @JsonProperty("user_name") 21 | private String username; 22 | private Set authorities; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/grpc/client/GrpcClientRunner.java: -------------------------------------------------------------------------------- 1 | package com.order.grpc.client; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.ApplicationArguments; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class GrpcClientRunner implements ApplicationRunner { 11 | 12 | private final GrpcClient grpcClient; 13 | 14 | 15 | @Autowired 16 | public GrpcClientRunner(@Lazy final GrpcClient grpcClient) { 17 | this.grpcClient = grpcClient; 18 | } 19 | 20 | 21 | @Override 22 | public void run(ApplicationArguments args) { 23 | grpcClient.start(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/grpc/configuration/GrpcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.order.grpc.configuration; 2 | 3 | import lombok.Getter; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @Getter 9 | public class GrpcConfiguration { 10 | 11 | @Value("${grpc.client.awaitTerminationInSeconds}") 12 | private int clientAwaitTerminationInSeconds; 13 | 14 | @Value("${grpc.client.withDeadlineAfterInSeconds}") 15 | private int clientWithDeadlineAfterInSeconds; 16 | 17 | @Value("${grpc.server.host}") 18 | private String serverHost; 19 | 20 | @Value("${grpc.server.port}") 21 | private int serverPort; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/grpc/service/IngredientServiceGrpcImpl.java: -------------------------------------------------------------------------------- 1 | package com.order.grpc.service; 2 | 3 | import com.spring5microservices.grpc.IngredientResponse; 4 | import com.spring5microservices.grpc.IngredientServiceGrpc; 5 | import com.spring5microservices.grpc.PizzaRequest; 6 | import com.order.grpc.client.GrpcClient; 7 | import com.spring5microservices.common.util.CollectionUtil; 8 | import lombok.extern.log4j.Log4j2; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.context.annotation.Lazy; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import static java.lang.String.format; 17 | import static java.util.Optional.ofNullable; 18 | 19 | @Log4j2 20 | @Service 21 | public class IngredientServiceGrpcImpl { 22 | 23 | private final GrpcClient grpcClient; 24 | 25 | 26 | @Autowired 27 | public IngredientServiceGrpcImpl(@Lazy final GrpcClient grpcClient) { 28 | this.grpcClient = grpcClient; 29 | } 30 | 31 | 32 | public List findByPizzaId(Short pizzaId) { 33 | log.info( 34 | format("Sending a request to get the ingredients contained in the pizza's identifier: %s", 35 | pizzaId) 36 | ); 37 | return ofNullable(pizzaId) 38 | .map(id -> 39 | PizzaRequest.newBuilder() 40 | .setId(id) 41 | .build() 42 | ) 43 | .map(request -> 44 | CollectionUtil.fromIterator( 45 | getIngredientServiceGrpc() 46 | .getIngredients(request) 47 | ) 48 | ) 49 | .orElseGet(ArrayList::new); 50 | } 51 | 52 | 53 | private IngredientServiceGrpc.IngredientServiceBlockingStub getIngredientServiceGrpc() { 54 | return grpcClient.getIngredientServiceGrpc(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/IModel.java: -------------------------------------------------------------------------------- 1 | package com.order.model; 2 | 3 | public interface IModel { 4 | 5 | /** 6 | * Return {@code true} if the current model has not been persisted, {@code false} otherwise. 7 | */ 8 | boolean isNew(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/Order.java: -------------------------------------------------------------------------------- 1 | package com.order.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | import java.sql.Timestamp; 10 | 11 | import javax.validation.constraints.NotNull; 12 | import javax.validation.constraints.Size; 13 | 14 | @AllArgsConstructor 15 | @EqualsAndHashCode(of = {"code"}) 16 | @Data 17 | @NoArgsConstructor 18 | public class Order implements IModel, Serializable { 19 | 20 | private static final long serialVersionUID = -779236471; 21 | 22 | private Integer id; 23 | 24 | @NotNull 25 | @Size(min=1, max=64) 26 | private String code; 27 | 28 | @NotNull 29 | private Timestamp created; 30 | 31 | @Override 32 | public boolean isNew() { 33 | return null == id; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/OrderLine.java: -------------------------------------------------------------------------------- 1 | package com.order.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Positive; 12 | 13 | @AllArgsConstructor 14 | @Data 15 | @NoArgsConstructor 16 | public class OrderLine implements IModel, Serializable { 17 | 18 | private static final long serialVersionUID = 1518934662; 19 | 20 | private Integer id; 21 | 22 | @NotNull 23 | @Positive 24 | private Integer orderId; 25 | 26 | @NotNull 27 | @Positive 28 | private Short pizzaId; 29 | 30 | @NotNull 31 | @Positive 32 | private Short amount; 33 | 34 | @NotNull 35 | @Positive 36 | private Double cost; 37 | 38 | 39 | @Override 40 | public boolean isNew() { 41 | return null == id; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | OrderLine orderLine = (OrderLine) o; 49 | return null == id ? (orderId.equals(orderLine.orderId) && pizzaId.equals(orderLine.pizzaId)) 50 | : id.equals(orderLine.id); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return null == id ? Objects.hash(orderId) + Objects.hash(pizzaId) : Objects.hash(id); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/Pizza.java: -------------------------------------------------------------------------------- 1 | package com.order.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Positive; 12 | import javax.validation.constraints.Size; 13 | 14 | @AllArgsConstructor 15 | @EqualsAndHashCode(of = {"name"}) 16 | @Data 17 | @NoArgsConstructor 18 | public class Pizza implements IModel, Serializable { 19 | 20 | private static final long serialVersionUID = -1229319586; 21 | 22 | private Short id; 23 | 24 | @NotNull 25 | @Size(min=1, max=64) 26 | private String name; 27 | 28 | @NotNull 29 | @Positive 30 | private Double cost; 31 | 32 | @Override 33 | public boolean isNew() { 34 | return null == id; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/jooq/DefaultCatalog.java: -------------------------------------------------------------------------------- 1 | package com.order.model.jooq; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import org.jooq.Schema; 8 | import org.jooq.impl.CatalogImpl; 9 | 10 | public class DefaultCatalog extends CatalogImpl { 11 | 12 | private static final long serialVersionUID = 331823911; 13 | 14 | /** 15 | * The reference instance of 16 | */ 17 | public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog(); 18 | 19 | /** 20 | * The schema eat. 21 | */ 22 | public final Eat EAT = Eat.EAT; 23 | 24 | /** 25 | * No further instances allowed 26 | */ 27 | private DefaultCatalog() { 28 | super(""); 29 | } 30 | 31 | @Override 32 | public final List getSchemas() { 33 | List result = new ArrayList(); 34 | result.addAll(getSchemas0()); 35 | return result; 36 | } 37 | 38 | private final List getSchemas0() { 39 | return Arrays.asList( 40 | Eat.EAT); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/jooq/Eat.java: -------------------------------------------------------------------------------- 1 | package com.order.model.jooq; 2 | 3 | import com.order.model.jooq.tables.OrderTable; 4 | import com.order.model.jooq.tables.OrderLineTable; 5 | import com.order.model.jooq.tables.PizzaTable; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import org.jooq.Catalog; 12 | import org.jooq.Sequence; 13 | import org.jooq.Table; 14 | import org.jooq.impl.SchemaImpl; 15 | 16 | public class Eat extends SchemaImpl { 17 | 18 | private static final long serialVersionUID = -1485920652; 19 | 20 | /** 21 | * The reference instance of eat 22 | */ 23 | public static final Eat EAT = new Eat(); 24 | 25 | /** 26 | * The table eat.order. 27 | */ 28 | public final OrderTable ORDER = OrderTable.ORDER_TABLE; 29 | 30 | /** 31 | * The table eat.order_line. 32 | */ 33 | public final OrderLineTable ORDER_LINE = OrderLineTable.ORDER_LINE_TABLE; 34 | 35 | /** 36 | * The table eat.pizza. 37 | */ 38 | public final PizzaTable PIZZA = PizzaTable.PIZZA_TABLE; 39 | 40 | /** 41 | * No further instances allowed 42 | */ 43 | private Eat() { 44 | super("eat", null); 45 | } 46 | 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | @Override 52 | public Catalog getCatalog() { 53 | return DefaultCatalog.DEFAULT_CATALOG; 54 | } 55 | 56 | @Override 57 | public final List> getSequences() { 58 | List result = new ArrayList(); 59 | result.addAll(getSequences0()); 60 | return result; 61 | } 62 | 63 | private final List> getSequences0() { 64 | return Arrays.>asList( 65 | Sequences.ORDER_ID_SEQ, 66 | Sequences.ORDER_LINE_ID_SEQ, 67 | Sequences.PIZZA_ID_SEQ); 68 | } 69 | 70 | @Override 71 | public final List> getTables() { 72 | List result = new ArrayList(); 73 | result.addAll(getTables0()); 74 | return result; 75 | } 76 | 77 | private final List> getTables0() { 78 | return Arrays.>asList( 79 | OrderTable.ORDER_TABLE, 80 | OrderLineTable.ORDER_LINE_TABLE, 81 | PizzaTable.PIZZA_TABLE); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/jooq/Indexes.java: -------------------------------------------------------------------------------- 1 | package com.order.model.jooq; 2 | 3 | import com.order.model.jooq.tables.OrderTable; 4 | import com.order.model.jooq.tables.OrderLineTable; 5 | import com.order.model.jooq.tables.PizzaTable; 6 | 7 | import org.jooq.Index; 8 | import org.jooq.OrderField; 9 | import org.jooq.impl.Internal; 10 | 11 | public class Indexes { 12 | 13 | // ------------------------------------------------------------------------- 14 | // INDEX definitions 15 | // ------------------------------------------------------------------------- 16 | 17 | public static final Index ORDER_CODE_UINDEX = Indexes0.ORDER_CODE_UINDEX; 18 | public static final Index ORDER_PK = Indexes0.ORDER_PK; 19 | public static final Index ORDER_LINE_PK = Indexes0.ORDER_LINE_PK; 20 | public static final Index PIZZA_NAME_UINDEX = Indexes0.PIZZA_NAME_UINDEX; 21 | public static final Index PIZZA_PK = Indexes0.PIZZA_PK; 22 | 23 | // ------------------------------------------------------------------------- 24 | // [#1459] distribute members to avoid static initialisers > 64kb 25 | // ------------------------------------------------------------------------- 26 | 27 | private static class Indexes0 { 28 | public static Index ORDER_CODE_UINDEX = Internal.createIndex("order_code_uindex", OrderTable.ORDER_TABLE, new OrderField[] { OrderTable.ORDER_TABLE.CODE }, true); 29 | public static Index ORDER_PK = Internal.createIndex("order_pk", OrderTable.ORDER_TABLE, new OrderField[] { OrderTable.ORDER_TABLE.ID }, true); 30 | public static Index ORDER_LINE_PK = Internal.createIndex("order_line_pk", OrderLineTable.ORDER_LINE_TABLE, new OrderField[] { OrderLineTable.ORDER_LINE_TABLE.ID }, true); 31 | public static Index PIZZA_NAME_UINDEX = Internal.createIndex("pizza_name_uindex", PizzaTable.PIZZA_TABLE, new OrderField[] { PizzaTable.PIZZA_TABLE.NAME }, true); 32 | public static Index PIZZA_PK = Internal.createIndex("pizza_pk", PizzaTable.PIZZA_TABLE, new OrderField[] { PizzaTable.PIZZA_TABLE.ID }, true); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/jooq/Sequences.java: -------------------------------------------------------------------------------- 1 | package com.order.model.jooq; 2 | 3 | import org.jooq.Sequence; 4 | import org.jooq.impl.SequenceImpl; 5 | 6 | public class Sequences { 7 | 8 | /** 9 | * The sequence eat.order_id_seq 10 | */ 11 | public static final Sequence ORDER_ID_SEQ = new SequenceImpl("order_id_seq", Eat.EAT, org.jooq.impl.SQLDataType.INTEGER.nullable(false)); 12 | 13 | /** 14 | * The sequence eat.order_line_id_seq 15 | */ 16 | public static final Sequence ORDER_LINE_ID_SEQ = new SequenceImpl("order_line_id_seq", Eat.EAT, org.jooq.impl.SQLDataType.INTEGER.nullable(false)); 17 | 18 | /** 19 | * The sequence eat.pizza_id_seq 20 | */ 21 | public static final Sequence PIZZA_ID_SEQ = new SequenceImpl("pizza_id_seq", Eat.EAT, org.jooq.impl.SQLDataType.SMALLINT.nullable(false)); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/model/jooq/Tables.java: -------------------------------------------------------------------------------- 1 | package com.order.model.jooq; 2 | 3 | import com.order.model.jooq.tables.OrderTable; 4 | import com.order.model.jooq.tables.OrderLineTable; 5 | import com.order.model.jooq.tables.PizzaTable; 6 | 7 | public class Tables { 8 | 9 | /** 10 | * The table eat.order. 11 | */ 12 | public static final OrderTable ORDER = OrderTable.ORDER_TABLE; 13 | 14 | /** 15 | * The table eat.order_line. 16 | */ 17 | public static final OrderLineTable ORDER_LINE = OrderLineTable.ORDER_LINE_TABLE; 18 | 19 | /** 20 | * The table eat.pizza. 21 | */ 22 | public static final PizzaTable PIZZA = PizzaTable.PIZZA_TABLE; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/util/converter/OrderConverter.java: -------------------------------------------------------------------------------- 1 | package com.order.util.converter; 2 | 3 | import com.order.dto.OrderDto; 4 | import com.order.model.Order; 5 | import com.spring5microservices.common.converter.BaseConverter; 6 | import org.mapstruct.Mapper; 7 | 8 | /** 9 | * Utility class to convert from {@link Order} to {@link OrderDto} and vice versa. 10 | */ 11 | @Mapper 12 | public interface OrderConverter extends BaseConverter {} 13 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/order/util/converter/PizzaConverter.java: -------------------------------------------------------------------------------- 1 | package com.order.util.converter; 2 | 3 | import com.order.dto.PizzaDto; 4 | import com.order.model.Pizza; 5 | import com.spring5microservices.common.converter.BaseConverter; 6 | import org.mapstruct.Mapper; 7 | 8 | /** 9 | * Utility class to convert from {@link Pizza} to {@link PizzaDto} and vice versa. 10 | */ 11 | @Mapper 12 | public interface PizzaConverter extends BaseConverter {} 13 | -------------------------------------------------------------------------------- /order-service/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | spring: 5 | config: 6 | import: configserver:http://localhost:8888 -------------------------------------------------------------------------------- /order-service/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:8081} 3 | 4 | spring: 5 | config: 6 | import: configserver:${CONFIG_SERVER:http://config-server:8888} 7 | -------------------------------------------------------------------------------- /order-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: order-service -------------------------------------------------------------------------------- /order-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/order/TestDataFactory.java: -------------------------------------------------------------------------------- 1 | package com.order; 2 | 3 | import com.order.dto.IngredientAmountDto; 4 | import com.order.dto.OrderDto; 5 | import com.order.dto.OrderLineDto; 6 | import com.order.dto.PizzaDto; 7 | import com.order.model.Order; 8 | import com.order.model.OrderLine; 9 | import com.order.model.Pizza; 10 | import com.spring5microservices.grpc.IngredientResponse; 11 | import lombok.experimental.UtilityClass; 12 | 13 | import java.sql.Timestamp; 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | @UtilityClass 18 | public class TestDataFactory { 19 | 20 | public static Order buildOrder(Integer id, String code, Timestamp created) { 21 | return new Order(id, code, created); 22 | } 23 | 24 | public static OrderLine buildOrderLine(Integer id, Integer orderId, Short pizzaId, Short amount, Double cost) { 25 | return new OrderLine(id, orderId, pizzaId, amount, cost); 26 | } 27 | 28 | public static Pizza buildPizza(Short id, String name, Double cost) { 29 | return new Pizza(id, name, cost); 30 | } 31 | 32 | public static OrderDto buildOrderDto(Integer id, String code, Date created, List orderLines) { 33 | return new OrderDto(id, code, created, orderLines); 34 | } 35 | 36 | public static OrderLineDto buildOrderLineDto(Integer id, Integer orderId, PizzaDto pizza, Short amount, Double cost) { 37 | return new OrderLineDto(id, orderId, pizza, amount, cost); 38 | } 39 | 40 | public static PizzaDto buildPizzaDto(Short id, String name, Double cost) { 41 | return new PizzaDto(id, name, cost); 42 | } 43 | 44 | public static IngredientAmountDto buildIngredientAmount(String name, Integer amount) { 45 | return new IngredientAmountDto(name, amount); 46 | } 47 | 48 | public static IngredientResponse buildIngredientResponse(Integer id, String name) { 49 | return IngredientResponse.newBuilder() 50 | .setId(id) 51 | .setName(name) 52 | .build(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /order-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | # Disable configuration server for testing 4 | config: 5 | enabled: false 6 | ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) 7 | datasource: 8 | driverClassName: org.postgresql.Driver 9 | url: jdbc:postgresql://localhost:5432/microservice_test 10 | username: microservice_test 11 | password: microservice_test 12 | jooq: 13 | sql-dialect: POSTGRES 14 | 15 | # Disable eureka server connection request for testing 16 | eureka: 17 | client: 18 | enabled: false 19 | 20 | security: 21 | restApi: 22 | authenticationInformation: OnlyToAvoidAFailureLoadingConfigurationClass 23 | clientId: TestClient 24 | clientPassword: TestPassword 25 | 26 | springdoc: 27 | api-docs: 28 | path: onlyForTests 29 | documentation: 30 | apiVersion: 1.0 31 | title: Test Rest Api 32 | description: Test description 33 | security: 34 | authorization: Bearer Auth 35 | schema: bearer 36 | format: JWT 37 | swagger-ui: 38 | path: testPath 39 | webjars: 40 | prefix: testPrefix 41 | 42 | grpc: 43 | client: 44 | awaitTerminationInSeconds: 0 45 | withDeadlineAfterInSeconds: 0 46 | server: 47 | host: localhost 48 | port: 11 49 | -------------------------------------------------------------------------------- /order-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | # To allow mocking final classes 2 | mock-maker-inline -------------------------------------------------------------------------------- /pizza-service/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd -------------------------------------------------------------------------------- /pizza-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the application to the Docker working directory 8 | COPY target/*.jar app.jar 9 | 10 | # Extract layers of application's jar 11 | RUN java -Djarmode=layertools -jar app.jar extract 12 | 13 | 14 | # Multi-Stage Build 15 | FROM eclipse-temurin:17-jre-alpine 16 | 17 | # Port used to connect with the application 18 | ENV SERVER_PORT 8080 19 | EXPOSE $SERVER_PORT 20 | 21 | # Default symmetric encryption key 22 | ARG ENCRYPT_KEY=ENCRYPT_KEY 23 | ENV ENCRYPT_KEY ${ENCRYPT_KEY} 24 | 25 | # Default Spring profile 26 | ENV SPRING_PROFILES_ACTIVE docker 27 | 28 | VOLUME /tmp 29 | 30 | ARG DEPENDENCY=/app 31 | 32 | COPY --from=builder ${DEPENDENCY}/dependencies/ ./ 33 | COPY --from=builder ${DEPENDENCY}/spring-boot-loader/ ./ 34 | COPY --from=builder ${DEPENDENCY}/snapshot-dependencies/ ./ 35 | COPY --from=builder ${DEPENDENCY}/application/ ./ 36 | 37 | # Copy the Spring Boot fat JarLauncher into the image and use it to run the application 38 | ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] 39 | 40 | 41 | # ------------------------------------------------------------------------------------------ 42 | # COMMANDS: 43 | # 44 | # 1. Build the image in local: 45 | # 46 | # docker build -t pizza-service . -f ./Dockerfile 47 | # 48 | # 2. Build and launch the container: 49 | # 50 | # docker run -p 8080:8080 --rm --name pizza-service --add-host=host.docker.internal:host-gateway --network Spring5Microservices pizza-service 51 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/PizzaServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.pizza; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class PizzaServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(PizzaServiceApplication.class, args); 11 | 12 | /* 13 | ConfigurableApplicationContext context = SpringApplication.run(PizzaServiceApplication.class, args); 14 | 15 | PizzaRepository pizzaRepository = context.getBean(PizzaRepository.class); 16 | Optional pizzaOptional = pizzaRepository.findWithIngredientsByName(PizzaEnum.CARBONARA); 17 | 18 | IngredientRepository ingredientRepository = context.getBean(IngredientRepository.class); 19 | List result = ingredientRepository.getIngredientWithItsMoreExpensivePizza( 20 | asList("Bacon", "Cheese", "Garlic", "Seafood", "Mozzarella", "Tomato sauce") 21 | ); 22 | 23 | int a = 1; 24 | */ 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/annotation/RoleAdmin.java: -------------------------------------------------------------------------------- 1 | package com.pizza.annotation; 2 | 3 | import com.pizza.configuration.Constants; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | import java.lang.annotation.Documented; 9 | 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Documented 15 | @Retention(RUNTIME) 16 | @Target({METHOD, TYPE}) 17 | @PreAuthorize("hasAuthority('" + Constants.ROLE_ADMIN +"')") 18 | public @interface RoleAdmin { 19 | } 20 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/annotation/RoleAdminOrUser.java: -------------------------------------------------------------------------------- 1 | package com.pizza.annotation; 2 | 3 | import com.pizza.configuration.Constants; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Documented 15 | @Retention(RUNTIME) 16 | @Target({METHOD, TYPE}) 17 | @PreAuthorize("hasAnyAuthority('" + Constants.ROLE_ADMIN +"','" + Constants.ROLE_USER + "')") 18 | public @interface RoleAdminOrUser { 19 | } 20 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration; 2 | 3 | /** 4 | * Global values used in different part of the application. 5 | */ 6 | public class Constants { 7 | 8 | // Global cache configuration 9 | public static final String CACHE_INSTANCE_NAME = "PizzaServiceCacheInstance"; 10 | 11 | // Database schema on which the entities have been included 12 | public static final String DATABASE_SCHEMA = "eat"; 13 | 14 | // Path of the folders in the application 15 | public static final class PATH { 16 | public static final String REPOSITORY = "com.pizza.repository"; 17 | 18 | // External path 19 | public static final class EXTERNAL { 20 | public static final String COMMON = "com.spring5microservices.common"; 21 | } 22 | } 23 | 24 | // Mapping used to match the result of some custom queries 25 | public static final class SQL_RESULT_MAPPING { 26 | public static final String PIZZA_INGREDIENTS = "PizzaIngredientsMapping"; 27 | } 28 | 29 | // Existing roles to manage the authorizations 30 | public static final String ROLE_ADMIN = "ADMIN"; 31 | public static final String ROLE_USER = "USER"; 32 | 33 | // Token configuration 34 | public static final String TOKEN_PREFIX = "Bearer "; 35 | 36 | // Default charset for plain text 37 | public static final String TEXT_PLAIN_UTF8_VALUE = "text/plain;charset=UTF-8"; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/WebfluxConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.reactive.config.CorsRegistry; 5 | import org.springframework.web.reactive.config.EnableWebFlux; 6 | import org.springframework.web.reactive.config.WebFluxConfigurer; 7 | 8 | import static org.springframework.http.HttpMethod.DELETE; 9 | import static org.springframework.http.HttpMethod.GET; 10 | import static org.springframework.http.HttpMethod.HEAD; 11 | import static org.springframework.http.HttpMethod.OPTIONS; 12 | import static org.springframework.http.HttpMethod.PATCH; 13 | import static org.springframework.http.HttpMethod.POST; 14 | import static org.springframework.http.HttpMethod.PUT; 15 | 16 | @Configuration 17 | @EnableWebFlux 18 | public class WebfluxConfiguration implements WebFluxConfigurer { 19 | 20 | @Override 21 | public void addCorsMappings(CorsRegistry corsRegistry) { 22 | corsRegistry 23 | .addMapping("/**") 24 | .allowedMethods( 25 | DELETE.name(), 26 | GET.name(), 27 | HEAD.name(), 28 | OPTIONS.name(), 29 | PATCH.name(), 30 | POST.name(), 31 | PUT.name() 32 | ); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/persistence/PersistenceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration.persistence; 2 | 3 | import com.pizza.configuration.Constants; 4 | import com.pizza.repository.base.ExtendedQueryDslJpaRepositoryImpl; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 7 | 8 | /** 9 | * Custom {@link Configuration} class to add specific configuration options related with the persistence. 10 | */ 11 | @Configuration 12 | @EnableJpaRepositories(basePackages = Constants.PATH.REPOSITORY, repositoryBaseClass = ExtendedQueryDslJpaRepositoryImpl.class) 13 | public class PersistenceConfiguration { } 14 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/persistence/SqlFunctionsMetadataBuilderContributor.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration.persistence; 2 | 3 | import org.hibernate.boot.MetadataBuilder; 4 | import org.hibernate.boot.spi.MetadataBuilderContributor; 5 | import org.hibernate.dialect.function.SQLFunctionTemplate; 6 | import org.hibernate.type.StandardBasicTypes; 7 | 8 | /** 9 | * Include custom functions we want to use in HQL queries 10 | */ 11 | public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor { 12 | 13 | @Override 14 | public void contribute(MetadataBuilder metadataBuilder) { 15 | 16 | /** 17 | * To work with QueryDSL datetime operations 18 | */ 19 | metadataBuilder.applySqlFunction( 20 | "add_years", 21 | new SQLFunctionTemplate( 22 | StandardBasicTypes.DATE, 23 | "?1 + interval '1 year' * ?2" 24 | ) 25 | ); 26 | metadataBuilder.applySqlFunction( 27 | "add_months", 28 | new SQLFunctionTemplate( 29 | StandardBasicTypes.DATE, 30 | "?1 + interval '1 month' * ?2" 31 | ) 32 | ); 33 | metadataBuilder.applySqlFunction( 34 | "add_days", 35 | new SQLFunctionTemplate( 36 | StandardBasicTypes.DATE, 37 | "?1 + interval '1 day' * ?2" 38 | ) 39 | ); 40 | metadataBuilder.applySqlFunction( 41 | "add_hours", 42 | new SQLFunctionTemplate( 43 | StandardBasicTypes.DATE, 44 | "?1 + interval '1 hour' * ?2" 45 | ) 46 | ); 47 | metadataBuilder.applySqlFunction( 48 | "add_minutes", 49 | new SQLFunctionTemplate( 50 | StandardBasicTypes.DATE, 51 | "?1 + interval '1 minute' * ?2" 52 | ) 53 | ); 54 | metadataBuilder.applySqlFunction( 55 | "add_seconds", 56 | new SQLFunctionTemplate( 57 | StandardBasicTypes.DATE, 58 | "?1 + interval '1 second' * ?2" 59 | ) 60 | ); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/rest/RestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration.rest; 2 | 3 | import io.netty.channel.ChannelOption; 4 | import io.netty.handler.timeout.ReadTimeoutHandler; 5 | import io.netty.handler.timeout.WriteTimeoutHandler; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.client.reactive.ReactorClientHttpConnector; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | import reactor.netty.http.client.HttpClient; 12 | 13 | import java.time.Duration; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @Configuration 17 | public class RestConfiguration { 18 | 19 | @Value("${rest.connect.timeoutInMilliseconds}") 20 | private int connectTimeoutMillis; 21 | 22 | @Value("${rest.read.timeoutInMilliseconds}") 23 | private int readTimeoutMillis; 24 | 25 | @Value("${rest.response.timeoutInMilliseconds}") 26 | private int responseTimeoutMillis; 27 | 28 | @Value("${rest.write.timeoutInMilliseconds}") 29 | private int writeTimeoutMillis; 30 | 31 | @Bean 32 | public WebClient webClient() { 33 | HttpClient httpClient = HttpClient.create() 34 | .option( 35 | ChannelOption.CONNECT_TIMEOUT_MILLIS, 36 | connectTimeoutMillis 37 | ) 38 | .responseTimeout( 39 | Duration.ofMillis(responseTimeoutMillis) 40 | ) 41 | .doOnConnected(conn -> 42 | conn.addHandlerLast( 43 | new ReadTimeoutHandler( 44 | readTimeoutMillis, 45 | TimeUnit.MILLISECONDS 46 | ) 47 | ) 48 | .addHandlerLast( 49 | new WriteTimeoutHandler( 50 | writeTimeoutMillis, 51 | TimeUnit.MILLISECONDS 52 | ) 53 | ) 54 | ); 55 | return WebClient.builder() 56 | .clientConnector( 57 | new ReactorClientHttpConnector(httpClient) 58 | ) 59 | .build(); 60 | } 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/rest/RestRoutes.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration.rest; 2 | 3 | /** 4 | * Used to define the REST API routes included in the project 5 | */ 6 | public final class RestRoutes { 7 | 8 | public static final String ROOT = "/pizza"; 9 | 10 | public static final class PIZZA { 11 | public static final String ROOT = RestRoutes.ROOT; 12 | public static final String PAGE_WITH_INGREDIENTS = "/pageWithIngredients"; 13 | } 14 | 15 | public static final class USER { 16 | public static final String ROOT = RestRoutes.ROOT + "/user"; 17 | public static final String BLACKLIST = "/blacklist"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/security/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration.security; 2 | 3 | import lombok.Getter; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * Configuration properties related with authentication/authorization (web service used for it) 9 | */ 10 | @Getter 11 | @Configuration 12 | public class SecurityConfiguration { 13 | 14 | @Value("${security.restApi.authenticationInformation}") 15 | private String authenticationInformationWebService; 16 | 17 | @Value("${security.restApi.clientId}") 18 | private String clientId; 19 | 20 | @Value("${security.restApi.clientPassword}") 21 | private String clientPassword; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/configuration/security/SecurityContextRepository.java: -------------------------------------------------------------------------------- 1 | package com.pizza.configuration.security; 2 | 3 | import com.pizza.configuration.Constants; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Lazy; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.server.reactive.ServerHttpRequest; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.context.SecurityContext; 11 | import org.springframework.security.core.context.SecurityContextImpl; 12 | import org.springframework.security.web.server.context.ServerSecurityContextRepository; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.server.ServerWebExchange; 15 | import reactor.core.publisher.Mono; 16 | 17 | /** 18 | * Gets the token included in {@code Authorization} Http header and 19 | * forwarded to {@link SecurityManager} to verify it. 20 | */ 21 | @Component 22 | public class SecurityContextRepository implements ServerSecurityContextRepository { 23 | 24 | private final SecurityManager securityManager; 25 | 26 | 27 | @Autowired 28 | public SecurityContextRepository(@Lazy final SecurityManager securityManager) { 29 | this.securityManager = securityManager; 30 | } 31 | 32 | 33 | @Override 34 | public Mono save(final ServerWebExchange swe, 35 | final SecurityContext sc) { 36 | throw new UnsupportedOperationException("Not supported operation"); 37 | } 38 | 39 | 40 | @Override 41 | public Mono load(final ServerWebExchange swe) { 42 | ServerHttpRequest request = swe.getRequest(); 43 | String authHeader = request.getHeaders() 44 | .getFirst(HttpHeaders.AUTHORIZATION); 45 | 46 | if (null != authHeader) { 47 | authHeader = authHeader.replace(Constants.TOKEN_PREFIX, ""); 48 | Authentication auth = new UsernamePasswordAuthenticationToken(authHeader, authHeader); 49 | return this.securityManager 50 | .authenticate(auth) 51 | .map(SecurityContextImpl::new); 52 | } else { 53 | return Mono.empty(); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/dto/IngredientDto.java: -------------------------------------------------------------------------------- 1 | package com.pizza.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Size; 11 | 12 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 13 | 14 | @AllArgsConstructor 15 | @EqualsAndHashCode(of = { "name" }) 16 | @Data 17 | @NoArgsConstructor 18 | public class IngredientDto { 19 | 20 | @Schema(description = "Internal unique identifier", requiredMode = RequiredMode.REQUIRED) 21 | private Integer id; 22 | 23 | @Schema(description = "Name", requiredMode = RequiredMode.REQUIRED) 24 | @NotNull 25 | @Size(min = 1, max = 64) 26 | private String name; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/dto/IngredientPizzaSummaryDto.java: -------------------------------------------------------------------------------- 1 | package com.pizza.dto; 2 | 3 | import com.pizza.enums.PizzaEnum; 4 | import com.querydsl.core.Tuple; 5 | import com.spring5microservices.common.util.validator.enums.EnumHasInternalStringValue; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | 12 | import javax.validation.constraints.NotNull; 13 | import javax.validation.constraints.Positive; 14 | import javax.validation.constraints.Size; 15 | 16 | import static java.util.Optional.ofNullable; 17 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 18 | 19 | @AllArgsConstructor 20 | @EqualsAndHashCode(of = { "ingredient", "pizza" }) 21 | @Data 22 | @NoArgsConstructor 23 | @Schema(description = "Used to provide a relation between pizza, ingredient and cost") 24 | public class IngredientPizzaSummaryDto { 25 | 26 | // Specific for QueryDSL 27 | public IngredientPizzaSummaryDto(final Tuple tuple) { 28 | Object[] tupleValues = ofNullable(tuple) 29 | .map(Tuple::toArray) 30 | .orElseGet(() -> new Object[] {}); 31 | 32 | this.ingredient = ofNullable(tupleValues[0]) 33 | .map(v -> tuple.get(0, String.class)) 34 | .orElse(null); 35 | this.pizza = ofNullable(tupleValues[1]) 36 | .map(v -> tuple.get(1, String.class)) 37 | .orElse(null); 38 | this.cost = ofNullable(tupleValues[2]) 39 | .map(v -> tuple.get(2, Double.class)) 40 | .orElse(null); 41 | } 42 | 43 | @Schema(description = "Ingredient name", requiredMode = RequiredMode.REQUIRED) 44 | @NotNull 45 | @Size(min = 1, max = 64) 46 | private String ingredient; 47 | 48 | @Schema(description = "Pizza name", requiredMode = RequiredMode.REQUIRED) 49 | @NotNull 50 | @EnumHasInternalStringValue(enumClass = PizzaEnum.class) 51 | private String pizza; 52 | 53 | @Schema(description = "Cost of the pizza", requiredMode = RequiredMode.REQUIRED) 54 | @NotNull 55 | @Positive 56 | private Double cost; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/dto/PizzaDto.java: -------------------------------------------------------------------------------- 1 | package com.pizza.dto; 2 | 3 | import com.pizza.enums.PizzaEnum; 4 | import com.spring5microservices.common.util.validator.enums.EnumHasInternalStringValue; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | 11 | import javax.validation.Valid; 12 | import javax.validation.constraints.NotNull; 13 | import javax.validation.constraints.Positive; 14 | import java.util.Set; 15 | 16 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 17 | 18 | @AllArgsConstructor 19 | @EqualsAndHashCode(of = { "name" }) 20 | @Data 21 | @NoArgsConstructor 22 | public class PizzaDto { 23 | 24 | @Schema(description = "Internal unique identifier", requiredMode = RequiredMode.REQUIRED) 25 | private Integer id; 26 | 27 | @Schema(description = "Name", requiredMode = RequiredMode.REQUIRED) 28 | @NotNull 29 | @EnumHasInternalStringValue(enumClass=PizzaEnum.class) 30 | private String name; 31 | 32 | @Schema(description = "Cost", requiredMode = RequiredMode.REQUIRED) 33 | @NotNull 34 | @Positive 35 | private Double cost; 36 | 37 | @Schema(description = "List of ingredients") 38 | @Valid 39 | private Set ingredients; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/enums/PizzaEnum.java: -------------------------------------------------------------------------------- 1 | package com.pizza.enums; 2 | 3 | import com.pizza.model.Pizza; 4 | import com.spring5microservices.common.util.validator.enums.IEnumInternalPropertyValue; 5 | 6 | import java.util.Arrays; 7 | import java.util.Optional; 8 | 9 | /** 10 | * Allowed types of {@link Pizza}. 11 | */ 12 | public enum PizzaEnum implements IEnumInternalPropertyValue { 13 | MARGUERITA("Margherita"), 14 | MARINARA("Marinara"), 15 | CARBONARA("Carbonara"), 16 | FRUTTI_DI_MARE("Frutti di Mare"), 17 | PUGLIESE("Pugliese"), 18 | HAWAIIAN("Hawaiian"); 19 | 20 | private final String databaseValue; 21 | 22 | PizzaEnum(String databaseValue) { 23 | this.databaseValue = databaseValue; 24 | } 25 | 26 | @Override 27 | public String getInternalPropertyValue() { 28 | return this.databaseValue; 29 | } 30 | 31 | /** 32 | * Using the given parameter returns the {@link PizzaEnum} with equal value in its {@code databaseValue} property. 33 | * 34 | * @param databaseValue 35 | * Value to search 36 | * 37 | * @return {@link Optional} of {@link PizzaEnum} if given value exists. 38 | * An empty {@link Optional} otherwise. 39 | */ 40 | public static Optional getFromDatabaseValue(String databaseValue) { 41 | return Optional.ofNullable(databaseValue) 42 | .flatMap(dv -> Arrays.stream(PizzaEnum.values()) 43 | .filter(ev -> dv.equals(ev.databaseValue)) 44 | .findFirst()); 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/grpc/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | package com.pizza.grpc.configuration; 2 | 3 | import io.grpc.Context; 4 | 5 | /** 6 | * Global values used in different part of the gRPC functionality. 7 | */ 8 | public class Constants { 9 | 10 | // gRPC client identifier that sent the request to the server 11 | public static final Context.Key GRPC_CLIENT_ID = Context.key("grpcClientId"); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/grpc/configuration/GrpcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.pizza.grpc.configuration; 2 | 3 | import lombok.Getter; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @Getter 9 | public class GrpcConfiguration { 10 | 11 | @Value("${grpc.server.port}") 12 | private int serverPort; 13 | 14 | @Value("${grpc.server.awaitTerminationInSeconds}") 15 | private int serverAwaitTerminationInSeconds; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/grpc/converter/IngredientGrpcConverter.java: -------------------------------------------------------------------------------- 1 | package com.pizza.grpc.converter; 2 | 3 | import com.spring5microservices.grpc.IngredientResponse; 4 | import com.pizza.model.Ingredient; 5 | import com.spring5microservices.common.converter.BaseFromModelToDtoConverter; 6 | import org.mapstruct.Mapper; 7 | 8 | import static java.util.Optional.ofNullable; 9 | 10 | /** 11 | * Utility class to convert from {@link Ingredient} to {@link IngredientResponse}. 12 | */ 13 | @Mapper 14 | public interface IngredientGrpcConverter extends BaseFromModelToDtoConverter { 15 | 16 | @Override 17 | default IngredientResponse fromModelToDto(final Ingredient model) { 18 | return ofNullable(model) 19 | .map(m -> 20 | IngredientResponse.newBuilder() 21 | .setId(model.getId()) 22 | .setName(model.getName()) 23 | .build() 24 | ) 25 | .orElse(null); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/grpc/interceptor/ExceptionHandlerInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.pizza.grpc.interceptor; 2 | 3 | import com.spring5microservices.grpc.util.GrpcErrorHandlerUtil; 4 | import io.grpc.ForwardingServerCallListener; 5 | import io.grpc.Metadata; 6 | import io.grpc.ServerCall; 7 | import io.grpc.ServerCallHandler; 8 | import io.grpc.ServerInterceptor; 9 | import io.grpc.Status; 10 | import lombok.extern.log4j.Log4j2; 11 | import org.springframework.stereotype.Component; 12 | 13 | import static java.lang.String.format; 14 | 15 | /** 16 | * Implementation of {@link ServerInterceptor} that translates and arguments gRPC exceptions. 17 | */ 18 | @Log4j2 19 | @Component 20 | public class ExceptionHandlerInterceptor implements ServerInterceptor { 21 | 22 | @Override 23 | public ServerCall.Listener interceptCall(final ServerCall serverCall, 24 | final Metadata metadata, 25 | final ServerCallHandler serverCallHandler) { 26 | ServerCall.Listener delegate = serverCallHandler.startCall( 27 | serverCall, 28 | metadata 29 | ); 30 | return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(delegate) { 31 | 32 | @Override 33 | public void onHalfClose() { 34 | try { 35 | super.onHalfClose(); 36 | } catch (Exception e) { 37 | serverCall.close( 38 | logExceptionAndGetStatus(e), 39 | new Metadata() 40 | ); 41 | } 42 | } 43 | }; 44 | } 45 | 46 | 47 | /** 48 | * Logs the current {@link Exception} and transforms it into the equivalent {@link Status}. 49 | * 50 | * @param sourceException 51 | * {@link Throwable} used to determine the right {@link Status} to return 52 | * 53 | * @return {@link Status} equivalent to provided {@code sourceException} 54 | */ 55 | private Status logExceptionAndGetStatus(final Exception sourceException) { 56 | Status returnedStatus = GrpcErrorHandlerUtil.getStatusFromThrowable(sourceException); 57 | log.error( 58 | format("There was an error in current request and the returned response will be status: %s and description: %s", 59 | returnedStatus.getCode(), 60 | returnedStatus.getDescription() 61 | ), 62 | sourceException 63 | ); 64 | return returnedStatus; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/grpc/server/GrpcServerRunner.java: -------------------------------------------------------------------------------- 1 | package com.pizza.grpc.server; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.ApplicationArguments; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class GrpcServerRunner implements ApplicationRunner { 11 | 12 | private final GrpcServer grpcServer; 13 | 14 | 15 | @Autowired 16 | public GrpcServerRunner(@Lazy final GrpcServer grpcServer) { 17 | this.grpcServer = grpcServer; 18 | } 19 | 20 | 21 | @Override 22 | public void run(ApplicationArguments args) throws Exception { 23 | grpcServer.start(); 24 | grpcServer.blockUntilShutdown(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/grpc/service/IngredientServiceGrpcImpl.java: -------------------------------------------------------------------------------- 1 | package com.pizza.grpc.service; 2 | 3 | import com.spring5microservices.grpc.IngredientResponse; 4 | import com.spring5microservices.grpc.IngredientServiceGrpc; 5 | import com.spring5microservices.grpc.PizzaRequest; 6 | import com.pizza.grpc.converter.IngredientGrpcConverter; 7 | import com.pizza.service.IngredientService; 8 | import io.grpc.stub.StreamObserver; 9 | import lombok.extern.log4j.Log4j2; 10 | import org.springframework.context.annotation.Lazy; 11 | import org.springframework.stereotype.Service; 12 | 13 | import static com.spring5microservices.common.util.ObjectUtil.getOrElse; 14 | import static java.lang.String.format; 15 | import static java.util.Optional.ofNullable; 16 | 17 | @Log4j2 18 | @Service 19 | public class IngredientServiceGrpcImpl extends IngredientServiceGrpc.IngredientServiceImplBase { 20 | 21 | private final IngredientService ingredientService; 22 | 23 | private final IngredientGrpcConverter ingredientGrpcConverter; 24 | 25 | 26 | public IngredientServiceGrpcImpl(@Lazy final IngredientService ingredientService, 27 | @Lazy final IngredientGrpcConverter ingredientGrpcConverter) { 28 | this.ingredientService = ingredientService; 29 | this.ingredientGrpcConverter = ingredientGrpcConverter; 30 | } 31 | 32 | 33 | @Override 34 | public void getIngredients(final PizzaRequest pizzaRequest, 35 | final StreamObserver responseObserver) { 36 | log.info( 37 | format("Getting ingredients contained in the pizza's identifier: %s", 38 | getOrElse( 39 | pizzaRequest, 40 | PizzaRequest::getId, 41 | "null" 42 | ) 43 | ) 44 | ); 45 | ofNullable(pizzaRequest) 46 | .map(PizzaRequest::getId) 47 | .map(ingredientService::findByPizzaId) 48 | .ifPresent(ingredients -> 49 | ingredients.stream() 50 | .map(ingredientGrpcConverter::fromModelToDto) 51 | .forEach(responseObserver::onNext) 52 | ); 53 | responseObserver.onCompleted(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/model/Ingredient.java: -------------------------------------------------------------------------------- 1 | package com.pizza.model; 2 | 3 | import com.pizza.configuration.Constants; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.Table; 15 | import javax.validation.constraints.NotNull; 16 | import javax.validation.constraints.Size; 17 | 18 | @AllArgsConstructor 19 | @EqualsAndHashCode(of = {"name"}) 20 | @Data 21 | @NoArgsConstructor 22 | @Entity 23 | @Table(name = "ingredient", schema = Constants.DATABASE_SCHEMA) 24 | public class Ingredient { 25 | 26 | @Id 27 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator=Constants.DATABASE_SCHEMA + "ingredient_id_seq") 28 | @Column 29 | private Integer id; 30 | 31 | @NotNull 32 | @Size(min=1, max=64) 33 | @Column 34 | private String name; 35 | 36 | } -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/model/Pizza.java: -------------------------------------------------------------------------------- 1 | package com.pizza.model; 2 | 3 | import com.pizza.configuration.Constants; 4 | import com.pizza.enums.PizzaEnum; 5 | import com.pizza.util.converter.enums.PizzaEnumDatabaseConverter; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | 11 | import javax.persistence.Convert; 12 | import javax.persistence.Entity; 13 | import javax.persistence.EntityResult; 14 | import javax.persistence.FieldResult; 15 | import javax.persistence.GeneratedValue; 16 | import javax.persistence.GenerationType; 17 | import javax.persistence.Id; 18 | import javax.persistence.JoinColumn; 19 | import javax.persistence.JoinTable; 20 | import javax.persistence.ManyToMany; 21 | import javax.persistence.SqlResultSetMapping; 22 | import javax.persistence.Table; 23 | import javax.validation.constraints.NotNull; 24 | import javax.validation.constraints.Positive; 25 | import java.util.Set; 26 | 27 | @AllArgsConstructor 28 | @EqualsAndHashCode(of = {"name"}) 29 | @Data 30 | @NoArgsConstructor 31 | @Entity 32 | @Table(name = "pizza", schema = Constants.DATABASE_SCHEMA) 33 | @SqlResultSetMapping( 34 | name = Constants.SQL_RESULT_MAPPING.PIZZA_INGREDIENTS, 35 | entities = { 36 | @EntityResult(entityClass = Pizza.class), 37 | @EntityResult( 38 | entityClass = Ingredient.class, 39 | fields = { 40 | @FieldResult(name = "id", column = "ingredients_id"), 41 | @FieldResult(name = "name", column = "ingredients_name")})}) 42 | public class Pizza { 43 | 44 | @Id 45 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator=Constants.DATABASE_SCHEMA + "pizza_id_seq") 46 | private Integer id; 47 | 48 | @NotNull 49 | @Convert(converter=PizzaEnumDatabaseConverter.class) 50 | private PizzaEnum name; 51 | 52 | @NotNull 53 | @Positive 54 | private Double cost; 55 | 56 | @ManyToMany 57 | @JoinTable(schema = Constants.DATABASE_SCHEMA, 58 | name = "pizza_ingredient", 59 | inverseJoinColumns = { @JoinColumn(name = "ingredient_id") }) 60 | private Set ingredients; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/repository/base/ExtendedJpaRepository.java: -------------------------------------------------------------------------------- 1 | package com.pizza.repository.base; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.repository.NoRepositoryBean; 5 | 6 | import javax.persistence.EntityManager; 7 | import javax.persistence.TypedQuery; 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Extended {@link JpaRepository} to include custom methods we want to share among all repositories. 12 | * 13 | * @param 14 | * Entity owner of the current repository 15 | * @param 16 | * Primary key of the Entity 17 | */ 18 | @NoRepositoryBean 19 | public interface ExtendedJpaRepository extends JpaRepository { 20 | 21 | /** 22 | * Return the internal {@link EntityManager} to provide more functionality to the repositories. 23 | * 24 | * @return {@link EntityManager} 25 | */ 26 | EntityManager getEntityManager(); 27 | 28 | 29 | /** 30 | * Return the HQL representation of the internal query of the given {@link TypedQuery}. 31 | * 32 | * @param query 33 | * {@link TypedQuery} to get its SQL query 34 | * 35 | * @return {@link String} with the HQL representation of the internal query, 36 | * empty {@link String} is there is any error getting it. 37 | */ 38 | String getHQLQuery(final TypedQuery query); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/repository/base/ExtendedJpaRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.pizza.repository.base; 2 | 3 | import org.hibernate.query.internal.QueryImpl; 4 | import org.springframework.data.jpa.repository.support.JpaEntityInformation; 5 | import org.springframework.data.jpa.repository.support.SimpleJpaRepository; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.TypedQuery; 9 | import java.io.Serializable; 10 | 11 | import static java.util.Optional.ofNullable; 12 | 13 | /** 14 | * Extended {@link SimpleJpaRepository} to include custom methods we want to share among all repositories. 15 | * 16 | * @param 17 | * Entity owner of the current repository 18 | * @param 19 | * Primary key of the Entity 20 | */ 21 | public class ExtendedJpaRepositoryImpl extends SimpleJpaRepository implements ExtendedJpaRepository { 22 | 23 | protected EntityManager entityManager; 24 | 25 | public ExtendedJpaRepositoryImpl(final JpaEntityInformation entityInformation, 26 | final EntityManager entityManager) { 27 | super( 28 | entityInformation, 29 | entityManager 30 | ); 31 | this.entityManager = entityManager; 32 | } 33 | 34 | @Override 35 | public EntityManager getEntityManager() { 36 | return entityManager; 37 | } 38 | 39 | 40 | @Override 41 | public String getHQLQuery(final TypedQuery query) { 42 | return ofNullable(query) 43 | .map(q -> { 44 | try { 45 | return query.unwrap(QueryImpl.class) 46 | .getQueryString(); 47 | 48 | } catch (Exception e) { 49 | return null; 50 | } 51 | }) 52 | .orElse(""); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/repository/base/ExtendedQueryDslJpaRepository.java: -------------------------------------------------------------------------------- 1 | package com.pizza.repository.base; 2 | 3 | import com.querydsl.core.types.EntityPath; 4 | import com.querydsl.jpa.impl.JPAQuery; 5 | import com.querydsl.jpa.sql.JPASQLQuery; 6 | import org.springframework.data.repository.NoRepositoryBean; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Extended {@link ExtendedJpaRepository} to include QueryDSL functionality we want to share among all repositories. 12 | * 13 | * @param 14 | * Entity owner of the current repository 15 | * @param 16 | * Primary key of the Entity 17 | */ 18 | @NoRepositoryBean 19 | public interface ExtendedQueryDslJpaRepository extends ExtendedJpaRepository { 20 | 21 | /** 22 | * Used to get {@link JPAQuery} instance and create custom JPA queries. 23 | * 24 | * @return {@link JPAQuery} 25 | */ 26 | JPAQuery getJPAQuery(); 27 | 28 | /** 29 | * Used to get {@link JPASQLQuery} instance and create custom JPA native queries. 30 | * 31 | * @return {@link JPASQLQuery} 32 | */ 33 | JPASQLQuery getJPASQLQuery(); 34 | 35 | /** 36 | * Generates a {@link JPAQuery} object with a {@code select} and {@code from} using the provided {@code entityPath}. 37 | * 38 | * @param entityPath 39 | * {@link EntityPath} used in {@code select} and {@code from} clauses. 40 | * 41 | * @return @return {@link JPAQuery} 42 | */ 43 | JPAQuery selectFrom(final EntityPath entityPath); 44 | 45 | /** 46 | * Generates a {@link JPASQLQuery} object with a {@code select} and {@code from} using the provided {@code entityPath}. 47 | * 48 | * @param entityPath 49 | * {@link EntityPath} used in {@code select} and {@code from} clauses. 50 | * 51 | * @return @return {@link JPASQLQuery} 52 | */ 53 | JPASQLQuery nativeSelectFrom(final EntityPath entityPath); 54 | 55 | } -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/repository/base/ExtendedQueryDslJpaRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.pizza.repository.base; 2 | 3 | import com.querydsl.core.types.EntityPath; 4 | import com.querydsl.jpa.impl.JPAQuery; 5 | import com.querydsl.jpa.sql.JPASQLQuery; 6 | import com.querydsl.sql.PostgreSQLTemplates; 7 | import com.querydsl.sql.SQLTemplates; 8 | import org.springframework.data.jpa.repository.support.JpaEntityInformation; 9 | 10 | import javax.persistence.EntityManager; 11 | import java.io.Serializable; 12 | 13 | /** 14 | * Extended {@link ExtendedJpaRepositoryImpl} to include QueryDSL functionality we want to share among all repositories. 15 | * 16 | * @param 17 | * Entity owner of the current repository 18 | * @param 19 | * Primary key of the Entity 20 | */ 21 | public class ExtendedQueryDslJpaRepositoryImpl extends ExtendedJpaRepositoryImpl 22 | implements ExtendedQueryDslJpaRepository { 23 | 24 | public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { 25 | super( 26 | entityInformation, 27 | entityManager 28 | ); 29 | } 30 | 31 | @Override 32 | public JPAQuery getJPAQuery() { 33 | return new JPAQuery<>(entityManager); 34 | } 35 | 36 | @Override 37 | public JPASQLQuery getJPASQLQuery() { 38 | return new JPASQLQuery<>( 39 | entityManager, 40 | getSQLTemplates() 41 | ); 42 | } 43 | 44 | @Override 45 | public JPAQuery selectFrom(final EntityPath entityPath) { 46 | return getJPAQuery() 47 | .select(entityPath) 48 | .from(entityPath); 49 | } 50 | 51 | @Override 52 | public JPASQLQuery nativeSelectFrom(final EntityPath entityPath) { 53 | return getJPASQLQuery() 54 | .select(entityPath) 55 | .from(entityPath); 56 | } 57 | 58 | private SQLTemplates getSQLTemplates() { 59 | return PostgreSQLTemplates 60 | .builder() 61 | .printSchema() 62 | .build(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/service/IngredientService.java: -------------------------------------------------------------------------------- 1 | package com.pizza.service; 2 | 3 | import com.pizza.model.Ingredient; 4 | import com.pizza.model.Pizza; 5 | import com.pizza.repository.IngredientRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Lazy; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | import static java.util.Optional.ofNullable; 17 | 18 | @Service 19 | public class IngredientService { 20 | 21 | private final IngredientRepository repository; 22 | 23 | 24 | @Autowired 25 | public IngredientService(@Lazy final IngredientRepository repository) { 26 | this.repository = repository; 27 | } 28 | 29 | 30 | /** 31 | * Return the {@link Ingredient}s contained in the {@link Pizza}'s identifier {@code pizzaId} 32 | * 33 | * @param pizzaId 34 | * {@link Pizza#getId()} 35 | * 36 | * @return {@link Set} of {@link Ingredient} 37 | */ 38 | public Set findByPizzaId(final Integer pizzaId) { 39 | return ofNullable(pizzaId) 40 | .map(repository::findByPizzaId) 41 | .orElseGet(HashSet::new); 42 | } 43 | 44 | 45 | /** 46 | * Persist the information included in the given {@code ingredients} 47 | * 48 | * @param ingredients 49 | * {@link List} of {@link Ingredient} to save 50 | * 51 | * @return updated {@link Ingredient}s 52 | */ 53 | public List saveAll(final Collection ingredients) { 54 | return ofNullable(ingredients) 55 | .map(repository::saveAll) 56 | .orElseGet(ArrayList::new); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/service/cache/UserBlacklistCacheService.java: -------------------------------------------------------------------------------- 1 | package com.pizza.service.cache; 2 | 3 | import com.pizza.configuration.cache.CacheConfiguration; 4 | import com.spring5microservices.common.service.CacheService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.stereotype.Service; 8 | 9 | import static java.util.Optional.ofNullable; 10 | 11 | @Service 12 | public class UserBlacklistCacheService { 13 | 14 | private static final boolean DEFAULT_BOOLEAN_VALUE = true; 15 | 16 | private final CacheConfiguration cacheConfiguration; 17 | 18 | private final CacheService cacheService; 19 | 20 | 21 | @Autowired 22 | public UserBlacklistCacheService(@Lazy final CacheConfiguration cacheConfiguration, 23 | @Lazy final CacheService cacheService) { 24 | this.cacheConfiguration = cacheConfiguration; 25 | this.cacheService = cacheService; 26 | } 27 | 28 | 29 | /** 30 | * Check if exists the given {@code username} inside the related cache. 31 | * 32 | * @param username 33 | * {@code username} to search 34 | * 35 | * @return {@code true} if the {@code username} exists, {@code false} otherwise 36 | */ 37 | public boolean contains(final String username) { 38 | return ofNullable(username) 39 | .map(id -> 40 | cacheService.contains( 41 | cacheConfiguration.getUserBlacklistCacheName(), 42 | username 43 | ) 44 | ) 45 | .orElse(false); 46 | } 47 | 48 | 49 | /** 50 | * Include a {@code username} inside the related cache. 51 | * 52 | * @param username 53 | * {@code username} to store 54 | * 55 | * @return {@code true} if the data was stored, {@code false} otherwise 56 | */ 57 | public boolean put(final String username) { 58 | return cacheService.put( 59 | cacheConfiguration.getUserBlacklistCacheName(), 60 | username, 61 | DEFAULT_BOOLEAN_VALUE 62 | ); 63 | } 64 | 65 | 66 | /** 67 | * Remove the given {@code username} of the related cache. 68 | * 69 | * @param username 70 | * {@code username} to remove 71 | * 72 | * @return {@code true} if the data was removed, {@code false} otherwise 73 | */ 74 | public boolean remove(final String username) { 75 | return cacheService.remove( 76 | cacheConfiguration.getUserBlacklistCacheName(), 77 | username 78 | ); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/util/PageUtil.java: -------------------------------------------------------------------------------- 1 | package com.pizza.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.PageRequest; 6 | import org.springframework.data.domain.Sort; 7 | 8 | import static java.util.Optional.ofNullable; 9 | 10 | /** 11 | * Helper functions to work with ecosystem related with {@link Page} like: 12 | * {@link PageRequest}, {@link Sort}, etc 13 | */ 14 | @UtilityClass 15 | public class PageUtil { 16 | 17 | /** 18 | * Generates a {@link PageRequest} to use in a database query 19 | * 20 | * @param page 21 | * Number of page to get 22 | * @param size 23 | * Number of elements in every page 24 | * @param sort 25 | * {@link Sort} with the sorting configuration 26 | * 27 | * @return {@link PageRequest} 28 | */ 29 | public static PageRequest buildPageRequest(final int page, 30 | final int size, 31 | final Sort sort) { 32 | return ofNullable(sort) 33 | .map(s -> 34 | PageRequest.of( 35 | page, 36 | size, 37 | sort 38 | ) 39 | ) 40 | .orElseGet(() -> 41 | PageRequest.of( 42 | page, 43 | size 44 | ) 45 | ); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/util/converter/IngredientConverter.java: -------------------------------------------------------------------------------- 1 | package com.pizza.util.converter; 2 | 3 | import com.pizza.dto.IngredientDto; 4 | import com.pizza.model.Ingredient; 5 | import com.spring5microservices.common.converter.BaseConverter; 6 | import org.mapstruct.Mapper; 7 | 8 | /** 9 | * Utility class to convert from {@link Ingredient} to {@link IngredientDto} and vice versa. 10 | */ 11 | @Mapper 12 | public interface IngredientConverter extends BaseConverter {} 13 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/util/converter/PizzaConverter.java: -------------------------------------------------------------------------------- 1 | package com.pizza.util.converter; 2 | 3 | import com.pizza.dto.PizzaDto; 4 | import com.pizza.model.Pizza; 5 | import com.pizza.util.converter.enums.PizzaEnumConverter; 6 | import com.spring5microservices.common.converter.BaseConverter; 7 | import org.mapstruct.Mapper; 8 | 9 | /** 10 | * Utility class to convert from {@link Pizza} to {@link PizzaDto} and vice versa. 11 | */ 12 | @Mapper(uses={IngredientConverter.class, PizzaEnumConverter.class}) 13 | public interface PizzaConverter extends BaseConverter {} 14 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/util/converter/enums/PizzaEnumConverter.java: -------------------------------------------------------------------------------- 1 | package com.pizza.util.converter.enums; 2 | 3 | import com.pizza.enums.PizzaEnum; 4 | import com.spring5microservices.common.converter.enums.BaseEnumConverter; 5 | import org.mapstruct.Mapper; 6 | 7 | import static java.util.Optional.ofNullable; 8 | 9 | /** 10 | * Utility class to convert from {@link PizzaEnum} to "equivalent" {@link String} and vice versa. 11 | */ 12 | @Mapper 13 | public class PizzaEnumConverter implements BaseEnumConverter { 14 | 15 | @Override 16 | public PizzaEnum fromValueToEnum(final String value) { 17 | return PizzaEnum.getFromDatabaseValue(value) 18 | .orElse(null); 19 | } 20 | 21 | @Override 22 | public String fromEnumToValue(final PizzaEnum enumValue) { 23 | return ofNullable(enumValue) 24 | .map(PizzaEnum::getInternalPropertyValue) 25 | .orElse(null); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pizza-service/src/main/java/com/pizza/util/converter/enums/PizzaEnumDatabaseConverter.java: -------------------------------------------------------------------------------- 1 | package com.pizza.util.converter.enums; 2 | 3 | import com.pizza.enums.PizzaEnum; 4 | import com.pizza.model.Pizza; 5 | 6 | import javax.persistence.AttributeConverter; 7 | import javax.persistence.Converter; 8 | 9 | import static java.util.Optional.ofNullable; 10 | 11 | /** 12 | * Class used to convert from/to the database value stored in the column {@code name} of the {@code pizza} 13 | * table from/to {@link Pizza#getName()} 14 | */ 15 | @Converter 16 | public class PizzaEnumDatabaseConverter implements AttributeConverter { 17 | 18 | @Override 19 | public String convertToDatabaseColumn(final PizzaEnum pizzaEnum) { 20 | return ofNullable(pizzaEnum) 21 | .map(PizzaEnum::getInternalPropertyValue) 22 | .orElse(null); 23 | } 24 | 25 | @Override 26 | public PizzaEnum convertToEntityAttribute(final String databaseValue) { 27 | return PizzaEnum.getFromDatabaseValue(databaseValue) 28 | .orElse(null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pizza-service/src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | javax.validation.constraints.AssertFalse.message = must be false 2 | javax.validation.constraints.AssertTrue.message = must be true 3 | javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value} 4 | javax.validation.constraints.DecimalMin.message = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value} 5 | javax.validation.constraints.Digits.message = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected) 6 | javax.validation.constraints.Email.message = must be a well-formed email address 7 | javax.validation.constraints.Future.message = must be a future date 8 | javax.validation.constraints.FutureOrPresent.message = must be a date in the present or in the future 9 | javax.validation.constraints.Max.message = must be less than or equal to {value} 10 | javax.validation.constraints.Min.message = must be greater than or equal to {value} 11 | javax.validation.constraints.Negative.message = must be less than 0 12 | javax.validation.constraints.NegativeOrZero.message = must be less than or equal to 0 13 | javax.validation.constraints.NotBlank.message = must not be blank 14 | javax.validation.constraints.NotEmpty.message = must not be empty 15 | javax.validation.constraints.NotNull.message = must not be null 16 | javax.validation.constraints.Null.message = must be null 17 | javax.validation.constraints.Past.message = must be a past date 18 | javax.validation.constraints.PastOrPresent.message = must be a date in the past or in the present 19 | javax.validation.constraints.Pattern.message = must match "{regexp}" 20 | javax.validation.constraints.Positive.message = must be greater than 0 21 | javax.validation.constraints.PositiveOrZero.message = must be greater than or equal to 0 22 | javax.validation.constraints.Size.message = size must be between {min} and {max} -------------------------------------------------------------------------------- /pizza-service/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | config: 6 | import: configserver:http://localhost:8888 -------------------------------------------------------------------------------- /pizza-service/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:8080} 3 | 4 | spring: 5 | config: 6 | import: configserver:${CONFIG_SERVER:http://config-server:8888} 7 | -------------------------------------------------------------------------------- /pizza-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: pizza-service -------------------------------------------------------------------------------- /pizza-service/src/test/java/com/pizza/TestDataFactory.java: -------------------------------------------------------------------------------- 1 | package com.pizza; 2 | 3 | import com.pizza.dto.IngredientDto; 4 | import com.pizza.dto.IngredientPizzaSummaryDto; 5 | import com.pizza.dto.PizzaDto; 6 | import com.pizza.enums.PizzaEnum; 7 | import com.spring5microservices.grpc.IngredientResponse; 8 | import com.pizza.model.Ingredient; 9 | import com.pizza.model.Pizza; 10 | import lombok.experimental.UtilityClass; 11 | 12 | import java.util.Set; 13 | 14 | @UtilityClass 15 | public class TestDataFactory { 16 | 17 | public static Ingredient buildIngredient(Integer id, String name) { 18 | return new Ingredient(id, name); 19 | } 20 | 21 | public static IngredientDto buildIngredientDto(Integer id, String name) { 22 | return new IngredientDto(id, name); 23 | } 24 | 25 | public static IngredientPizzaSummaryDto buildIngredientPizzaSummaryDto(String ingredient, String pizza, Double cost) { 26 | return new IngredientPizzaSummaryDto(ingredient, pizza, cost); 27 | } 28 | 29 | public static IngredientResponse buildIngredientResponse(Integer id, String name) { 30 | return IngredientResponse.newBuilder() 31 | .setId(id) 32 | .setName(name) 33 | .build(); 34 | } 35 | 36 | public static Pizza buildPizza(Integer id, PizzaEnum name, Double cost, Set ingredients) { 37 | return new Pizza(id, name, cost, ingredients); 38 | } 39 | 40 | public static PizzaDto buildPizzaDto(Integer id, String name, Double cost, Set ingredients) { 41 | return new PizzaDto(id, name, cost, ingredients); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /pizza-service/src/test/java/com/pizza/controller/BaseControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.pizza.controller; 2 | 3 | import com.pizza.grpc.server.GrpcServerRunner; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.boot.test.mock.mockito.MockBean; 7 | import org.springframework.cache.CacheManager; 8 | import org.springframework.context.ApplicationContext; 9 | 10 | public abstract class BaseControllerTest { 11 | 12 | @Autowired 13 | protected ApplicationContext context; 14 | 15 | // To avoid Hazelcast instance creation 16 | @MockBean 17 | @Qualifier("cacheManager") 18 | private CacheManager mockCacheManager; 19 | 20 | // To avoid gRPC server initialization 21 | @MockBean 22 | private GrpcServerRunner grpcServerRunner; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /pizza-service/src/test/java/com/pizza/util/PageUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.pizza.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Sort; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class PageUtilTest { 10 | 11 | @Test 12 | public void buildPageRequest_whenNullSortIsGiven_thenNoSortIsConfigured() { 13 | // Given 14 | int page = 1; 15 | int size = 2; 16 | 17 | // When 18 | PageRequest pageRequest = PageUtil.buildPageRequest(page, size, null); 19 | 20 | // Then 21 | assertEquals(page, pageRequest.getPageNumber()); 22 | assertEquals(size, pageRequest.getPageSize()); 23 | assertEquals(Sort.unsorted(), pageRequest.getSort()); 24 | } 25 | 26 | 27 | @Test 28 | public void buildPageRequest_whenNotNullSortIsGiven_thenItWillBeIncludedInPageRequest() { 29 | // Given 30 | int page = 1; 31 | int size = 2; 32 | Sort sort = Sort.by(Sort.Direction.ASC, "property1"); 33 | 34 | // When 35 | PageRequest pageRequest = PageUtil.buildPageRequest(page, size, sort); 36 | 37 | // Then 38 | assertEquals(page, pageRequest.getPageNumber()); 39 | assertEquals(size, pageRequest.getPageSize()); 40 | assertEquals(sort, pageRequest.getSort()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /pizza-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | # Disable configuration server for testing 4 | config: 5 | enabled: false 6 | datasource: 7 | url: jdbc:postgresql://localhost:5432/microservice_test 8 | username: microservice_test 9 | password: microservice_test 10 | jpa: 11 | properties: 12 | hibernate: 13 | dialect: org.hibernate.dialect.PostgreSQL10Dialect 14 | format_sql: false 15 | jdbc: 16 | lob: 17 | # With Java >= 9, to avoid: 18 | # SQLFeatureNotSupportedException: Method PgConnection.createClob() is not yet implemented 19 | non_contextual_creation: true 20 | # To include custom functions we want to use in HQL queries 21 | metadata_builder_contributor: com.pizza.configuration.persistence.SqlFunctionsMetadataBuilderContributor 22 | show_sql: false 23 | 24 | # Disable eureka server connection request for testing 25 | eureka: 26 | client: 27 | enabled: false 28 | 29 | security: 30 | restApi: 31 | authenticationInformation: OnlyToAvoidAFailureLoadingConfigurationClass 32 | clientId: TestClient 33 | clientPassword: TestPassword 34 | 35 | rest: 36 | connect: 37 | timeoutInMilliseconds: 5000 38 | read: 39 | timeoutInMilliseconds: 5000 40 | response: 41 | timeoutInMilliseconds: 5000 42 | write: 43 | timeoutInMilliseconds: 5000 44 | 45 | cache: 46 | userBlacklist: 47 | entryCapacity: 2 48 | expireInSeconds: 10 49 | name: OnlyToAvoidAFailureLoadingConfigurationClass 50 | 51 | springdoc: 52 | api-docs: 53 | path: onlyForTests 54 | documentation: 55 | apiVersion: 1.0 56 | title: Test Rest Api 57 | description: Test description 58 | security: 59 | authorization: Bearer Auth 60 | schema: bearer 61 | format: JWT 62 | swagger-ui: 63 | path: testPath 64 | webjars: 65 | prefix: testPrefix 66 | 67 | grpc: 68 | server: 69 | port: 11 70 | awaitTerminationInSeconds: 0 71 | 72 | #show sql statement 73 | #logging: 74 | # level: 75 | # org: 76 | # hibernate: 77 | # SQL: debug 78 | # type: 79 | # descriptor: 80 | # sql: trace 81 | -------------------------------------------------------------------------------- /pizza-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | # To allow mocking final classes 2 | mock-maker-inline -------------------------------------------------------------------------------- /registry-server/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd 29 | -------------------------------------------------------------------------------- /registry-server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the local maven and the application to the Docker working directory 8 | COPY mvnw . 9 | COPY .mvn .mvn 10 | COPY pom.xml . 11 | COPY src src 12 | 13 | RUN ./mvnw install -DskipTests 14 | RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 15 | 16 | 17 | # Multi-Stage Build 18 | FROM eclipse-temurin:17-jre-alpine 19 | 20 | # Port used to connect with the application 21 | ENV SERVER_PORT 8761 22 | EXPOSE $SERVER_PORT 23 | 24 | # Default Spring profile 25 | ENV SPRING_PROFILES_ACTIVE docker 26 | 27 | VOLUME /tmp 28 | 29 | ARG DEPENDENCY=/app/target/dependency 30 | 31 | COPY --from=builder ${DEPENDENCY}/BOOT-INF/lib /app/lib 32 | COPY --from=builder ${DEPENDENCY}/META-INF /app/META-INF 33 | COPY --from=builder ${DEPENDENCY}/BOOT-INF/classes /app 34 | 35 | # Use the Spring Boot main application class to run it 36 | ENTRYPOINT ["java", "-cp", "app:app/lib/*", "com.registryserver.RegistryServerApplication"] 37 | 38 | 39 | # ------------------------------------------------------------------------------------------ 40 | # COMMANDS: 41 | # 42 | # 1. Build the image in local: 43 | # 44 | # docker build -t registry-server . -f ./Dockerfile 45 | # 46 | # 2. Build and launch the container: 47 | # 48 | # docker run -p 8761:8761 --rm --name registry-server --network Spring5Microservices registry-server 49 | -------------------------------------------------------------------------------- /registry-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.7.18 10 | 11 | 12 | com.registryserver 13 | registry-server 14 | 1.1.8 15 | registry-server 16 | Registry server with Eureka for Spring 5 with microservices 17 | 18 | 19 | 17 20 | 17 21 | 17 22 | 2021.0.9 23 | 24 | 25 | 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-netflix-eureka-server 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-dependencies 38 | ${spring-cloud.version} 39 | pom 40 | import 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-maven-plugin 51 | ${project.parent.version} 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /registry-server/src/main/java/com/registryserver/RegistryServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.registryserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class RegistryServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(RegistryServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /registry-server/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | eureka: 5 | instance: 6 | hostname: localhost 7 | client: 8 | # Do not cache registry information locally 9 | fetchRegistry: false 10 | # Do not register with Eureka service 11 | registerWithEureka: false 12 | serviceUrl: 13 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka 14 | server: 15 | # Only in NON PRODUCTION settings, preventing the deregistration of active services 16 | # when network hiccup has stopped the renewal request from making its way to Eureka 17 | enableSelfPreservation: false 18 | # Initial time to wait before server takes requests 19 | waitTimeInMsWhenSyncEmpty: 5 -------------------------------------------------------------------------------- /registry-server/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:8761} 3 | 4 | eureka: 5 | instance: 6 | hostname: ${SERVER_HOSTNAME:localhost} 7 | client: 8 | # Do not cache registry information locally 9 | fetchRegistry: false 10 | # Do not register with Eureka service 11 | registerWithEureka: false 12 | serviceUrl: 13 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka 14 | server: 15 | # Only in NON PRODUCTION settings, preventing the deregistration of active services 16 | # when network hiccup has stopped the renewal request from making its way to Eureka 17 | enableSelfPreservation: false 18 | # Initial time to wait before server takes requests 19 | waitTimeInMsWhenSyncEmpty: 5 -------------------------------------------------------------------------------- /registry-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: registry-server -------------------------------------------------------------------------------- /security-jwt-service/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd 29 | -------------------------------------------------------------------------------- /security-jwt-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the application to the Docker working directory 8 | COPY target/*.jar app.jar 9 | 10 | # Extract layers of application's jar 11 | RUN java -Djarmode=layertools -jar app.jar extract 12 | 13 | 14 | # Multi-Stage Build 15 | FROM eclipse-temurin:17-jre-alpine 16 | 17 | # Port used to connect with the application 18 | ENV SERVER_PORT 8180 19 | EXPOSE $SERVER_PORT 20 | 21 | # Default symmetric encryption key 22 | ARG ENCRYPT_KEY=ENCRYPT_KEY 23 | ENV ENCRYPT_KEY ${ENCRYPT_KEY} 24 | 25 | # Default Spring profile 26 | ENV SPRING_PROFILES_ACTIVE docker 27 | 28 | VOLUME /tmp 29 | 30 | ARG DEPENDENCY=/app 31 | 32 | COPY --from=builder ${DEPENDENCY}/dependencies/ ./ 33 | COPY --from=builder ${DEPENDENCY}/spring-boot-loader/ ./ 34 | COPY --from=builder ${DEPENDENCY}/snapshot-dependencies/ ./ 35 | COPY --from=builder ${DEPENDENCY}/application/ ./ 36 | 37 | # Copy the Spring Boot fat JarLauncher into the image and use it to run the application 38 | ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] 39 | 40 | 41 | # ------------------------------------------------------------------------------------------ 42 | # COMMANDS: 43 | # 44 | # 1. Build the image in local: 45 | # 46 | # docker build -t security-jwt-service . -f ./Dockerfile 47 | # 48 | # 2. Build and launch the container: 49 | # 50 | # docker run -p 8180:8180 --rm --name security-jwt-service --add-host=host.docker.internal:host-gateway --network Spring5Microservices security-jwt-service 51 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/SecurityJwtServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @SpringBootApplication 8 | @EnableDiscoveryClient 9 | public class SecurityJwtServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SecurityJwtServiceApplication.class, args); 13 | 14 | /* 15 | ConfigurableApplicationContext context = SpringApplication.run(SecurityJwtServiceApplication.class, args); 16 | SecurityService securityService = context.getBean(SecurityService.class); 17 | Optional authenticationInfo = securityService.login("Spring5Microservices", "admin", "admin"); 18 | UsernameAuthoritiesDto usernameAuthorities = securityService.getAuthorizationInformation(authenticationInfo.get().getAccessToken(), "Spring5Microservices"); 19 | int a = 1; 20 | */ 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/application/spring5microservices/enums/RoleEnum.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.application.spring5microservices.enums; 2 | 3 | /** 4 | * Allowed roles of the users included in the application 5 | */ 6 | public enum RoleEnum { 7 | ADMIN, 8 | USER 9 | } 10 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/application/spring5microservices/model/Role.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.application.spring5microservices.model; 2 | 3 | import com.security.jwt.configuration.Constants; 4 | import com.security.jwt.application.spring5microservices.enums.RoleEnum; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | 11 | import javax.persistence.Entity; 12 | import javax.persistence.EnumType; 13 | import javax.persistence.Enumerated; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.GenerationType; 16 | import javax.persistence.Id; 17 | import javax.persistence.Table; 18 | import javax.validation.constraints.NotNull; 19 | import java.io.Serializable; 20 | 21 | @AllArgsConstructor 22 | @Builder 23 | @Data 24 | @Entity 25 | @EqualsAndHashCode(of = {"name"}) 26 | @NoArgsConstructor 27 | @Table(schema = Constants.DATABASE_SCHEMA.EAT) 28 | public class Role implements Serializable { 29 | 30 | private static final long serialVersionUID = -3655820157062921094L; 31 | 32 | @Id 33 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = Constants.DATABASE_SCHEMA.EAT + "role_id_seq") 34 | private Integer id; 35 | 36 | @NotNull 37 | @Enumerated(EnumType.STRING) 38 | private RoleEnum name; 39 | 40 | } -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/application/spring5microservices/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.application.spring5microservices.repository; 2 | 3 | import com.security.jwt.application.spring5microservices.model.Role; 4 | import com.security.jwt.configuration.Constants; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository(value = Constants.APPLICATIONS.SPRING5_MICROSERVICES + "RoleRepository") 9 | public interface RoleRepository extends JpaRepository {} 10 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/application/spring5microservices/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.application.spring5microservices.repository; 2 | 3 | import com.security.jwt.application.spring5microservices.model.Role; 4 | import com.security.jwt.application.spring5microservices.model.User; 5 | import com.security.jwt.configuration.Constants; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.lang.Nullable; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.Optional; 11 | 12 | @Repository(value = Constants.APPLICATIONS.SPRING5_MICROSERVICES + "UserRepository") 13 | public interface UserRepository extends JpaRepository { 14 | 15 | /** 16 | * Gets the {@link User} (including its {@link Role}s) which {@link User#getUsername()} matches with the given one. 17 | * 18 | * @param username 19 | * Username to search a coincidence in {@link User#getUsername()} 20 | * 21 | * @return {@link Optional} with the {@link User} which username matches with the given one. 22 | * {@link Optional#empty()} otherwise. 23 | */ 24 | Optional findByUsername(@Nullable String username); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.configuration; 2 | 3 | /** 4 | * Global values used in different part of the application 5 | */ 6 | public class Constants { 7 | 8 | // Applications included in the project to manage their security functionality (used for bean definitions) 9 | public static final class APPLICATIONS { 10 | public static final String SPRING5_MICROSERVICES = "Spring5Microservices"; 11 | } 12 | 13 | // Global cache configuration 14 | public static final String CACHE_INSTANCE_NAME = "SecurityJwtCacheInstance"; 15 | 16 | /** 17 | * Prefix used to store the cipher passwords in database 18 | */ 19 | public static final String CIPHER_SECRET_PREFIX = "{cipher}"; 20 | 21 | // Database schemas on which the entities have been included 22 | public static final class DATABASE_SCHEMA { 23 | public static final String EAT = "eat"; 24 | public static final String SECURITY = "security"; 25 | }; 26 | 27 | // Path of the folders in the application 28 | public static final class PATH { 29 | 30 | // External path 31 | public static final class EXTERNAL { 32 | public static final String COMMON = "com.spring5microservices.common"; 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/configuration/WebfluxConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.reactive.config.CorsRegistry; 5 | import org.springframework.web.reactive.config.EnableWebFlux; 6 | import org.springframework.web.reactive.config.WebFluxConfigurer; 7 | 8 | import static org.springframework.http.HttpMethod.DELETE; 9 | import static org.springframework.http.HttpMethod.GET; 10 | import static org.springframework.http.HttpMethod.HEAD; 11 | import static org.springframework.http.HttpMethod.OPTIONS; 12 | import static org.springframework.http.HttpMethod.PATCH; 13 | import static org.springframework.http.HttpMethod.POST; 14 | import static org.springframework.http.HttpMethod.PUT; 15 | 16 | @Configuration 17 | @EnableWebFlux 18 | public class WebfluxConfiguration implements WebFluxConfigurer { 19 | 20 | @Override 21 | public void addCorsMappings(CorsRegistry corsRegistry) { 22 | corsRegistry.addMapping("/**") 23 | .allowedMethods( 24 | DELETE.name(), 25 | GET.name(), 26 | HEAD.name(), 27 | OPTIONS.name(), 28 | PATCH.name(), 29 | POST.name(), 30 | PUT.name() 31 | ); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/configuration/documentation/DocumentationConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.configuration.documentation; 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.Info; 6 | import io.swagger.v3.oas.models.security.SecurityRequirement; 7 | import io.swagger.v3.oas.models.security.SecurityScheme; 8 | import lombok.Getter; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @Getter 15 | public class DocumentationConfiguration { 16 | 17 | @Value("${springdoc.api-docs.path}") 18 | private String apiDocsPath; 19 | 20 | @Value("${springdoc.swagger-ui.path}") 21 | private String apiUiUrl; 22 | 23 | @Value("${springdoc.documentation.apiVersion}") 24 | private String apiVersion; 25 | 26 | @Value("${springdoc.documentation.description}") 27 | private String description; 28 | 29 | @Value("${springdoc.documentation.title}") 30 | private String title; 31 | 32 | @Value("${springdoc.security.authorization}") 33 | private String securityAuthorization; 34 | 35 | @Value("${springdoc.security.schema}") 36 | private String securitySchema; 37 | 38 | @Value("${springdoc.webjars.prefix}") 39 | private String webjarsUrl; 40 | 41 | 42 | @Bean 43 | public OpenAPI customOpenAPI() { 44 | return new OpenAPI() 45 | .addSecurityItem( 46 | new SecurityRequirement() 47 | .addList(securityAuthorization) 48 | ) 49 | .components( 50 | new Components() 51 | .addSecuritySchemes( 52 | securityAuthorization, 53 | securityScheme() 54 | ) 55 | ) 56 | .info(apiInfo()); 57 | } 58 | 59 | /** 60 | * Include more information related with the Rest Api documentation 61 | * 62 | * @return {@link Info} 63 | */ 64 | private Info apiInfo() { 65 | return new Info() 66 | .title(title) 67 | .description(description) 68 | .version(apiVersion); 69 | } 70 | 71 | 72 | private SecurityScheme securityScheme() { 73 | return new SecurityScheme() 74 | .name(securityAuthorization) 75 | .type(SecurityScheme.Type.HTTP) 76 | .scheme(securitySchema); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/configuration/rest/RestRoutes.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.configuration.rest; 2 | 3 | /** 4 | * Used to define the REST API routes included in the project 5 | */ 6 | public final class RestRoutes { 7 | 8 | public static final String ROOT = "/security/jwt"; 9 | 10 | public static final class CACHE { 11 | public static final String ROOT = RestRoutes.ROOT + "/cache"; 12 | public static final String CLEAR = "/clear"; 13 | } 14 | 15 | public static final class SECURITY { 16 | public static final String ROOT = RestRoutes.ROOT; 17 | public static final String AUTHORIZATION_INFO = "/authinfo"; 18 | public static final String LOGIN = "/login"; 19 | public static final String REFRESH = "/refresh"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/configuration/security/JweConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.configuration.security; 2 | 3 | import lombok.Getter; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * Configuration properties related with JWE token 9 | */ 10 | @Getter 11 | @Configuration 12 | public class JweConfiguration { 13 | 14 | @Value("${security.jwe.encryptionSecret}") 15 | private String encryptionSecret; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/configuration/security/PasswordEncoderConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.configuration.security; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class PasswordEncoderConfiguration { 10 | 11 | /** 12 | * Encoder used to manage the passwords included in database. 13 | * 14 | * @return {@link PasswordEncoder} 15 | */ 16 | @Bean 17 | public PasswordEncoder passwordEncoder() { 18 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.controller; 2 | 3 | import com.spring5microservices.common.exception.UnauthorizedException; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.context.ReactiveSecurityContextHolder; 6 | import org.springframework.security.core.context.SecurityContext; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import reactor.core.publisher.Mono; 9 | 10 | public abstract class BaseController { 11 | 12 | /** 13 | * Get the authenticated {@link UserDetails} to know the application is trying to use the provided web services. 14 | * 15 | * @return {@link UserDetails} 16 | * 17 | * @throws UnauthorizedException if the given {@code clientId} does not exist in database 18 | */ 19 | protected Mono getPrincipal() { 20 | return ReactiveSecurityContextHolder.getContext() 21 | .map(SecurityContext::getAuthentication) 22 | .map(Authentication::getPrincipal) 23 | .cast(UserDetails.class); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/dto/AuthenticationRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Size; 11 | 12 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode; 13 | 14 | @AllArgsConstructor 15 | @Data 16 | @EqualsAndHashCode(of = { "username" }) 17 | @NoArgsConstructor 18 | @Schema(description = "Required data to authenticate a user") 19 | public class AuthenticationRequestDto { 20 | 21 | @Schema(requiredMode = RequiredMode.REQUIRED) 22 | @NotNull 23 | @Size(min = 1, max = 64) 24 | private String username; 25 | 26 | @Schema(requiredMode = RequiredMode.REQUIRED) 27 | @NotNull 28 | @Size(min = 1, max = 128) 29 | private String password; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/dto/RawAuthenticationInformationDto.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Required information to generate the final token sent as response 12 | */ 13 | @AllArgsConstructor 14 | @Builder 15 | @Data 16 | @NoArgsConstructor 17 | public class RawAuthenticationInformationDto { 18 | 19 | Map accessTokenInformation; 20 | Map refreshTokenInformation; 21 | Map additionalTokenInformation; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/enums/SignatureAlgorithmEnum.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.enums; 2 | 3 | import com.nimbusds.jose.JWSAlgorithm; 4 | 5 | /** 6 | * Allowed algorithms to sign a JWT token 7 | */ 8 | public enum SignatureAlgorithmEnum { 9 | 10 | HS256(JWSAlgorithm.HS256), 11 | HS384(JWSAlgorithm.HS384), 12 | HS512(JWSAlgorithm.HS512); 13 | 14 | private final JWSAlgorithm algorithm; 15 | 16 | SignatureAlgorithmEnum(JWSAlgorithm algorithm) { 17 | this.algorithm = algorithm; 18 | } 19 | 20 | public JWSAlgorithm getAlgorithm() { 21 | return algorithm; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/enums/TokenKeyEnum.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.enums; 2 | 3 | public enum TokenKeyEnum { 4 | 5 | AUDIENCE("aud"), 6 | AUTHORITIES("authorities"), 7 | EXPIRATION_TIME("exp"), 8 | ISSUED_AT("iat"), 9 | JWT_ID("jti"), 10 | NAME("name"), 11 | REFRESH_JWT_ID("ati"), 12 | USERNAME("username"); 13 | 14 | private final String key; 15 | 16 | TokenKeyEnum(String key) { 17 | this.key = key; 18 | } 19 | 20 | public String getKey() { 21 | return key; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/enums/TokenType.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.enums; 2 | 3 | import com.security.jwt.model.JwtClientDetails; 4 | 5 | /** 6 | * Token types managed by the security microservice and configured in {@link JwtClientDetails} 7 | */ 8 | public enum TokenType { 9 | Bearer 10 | } 11 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/exception/ClientNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.exception; 2 | 3 | import com.security.jwt.model.JwtClientDetails; 4 | import com.security.jwt.service.JwtClientDetailsService; 5 | 6 | /** 7 | * Thrown if an {@link JwtClientDetailsService} implementation cannot locate a {@link JwtClientDetails} by its clientId. 8 | */ 9 | public class ClientNotFoundException extends RuntimeException { 10 | 11 | private static final long serialVersionUID = 338648360450595760L; 12 | 13 | public ClientNotFoundException() { 14 | super(); 15 | } 16 | 17 | public ClientNotFoundException(String message) { 18 | super(message); 19 | } 20 | 21 | public ClientNotFoundException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public ClientNotFoundException(Throwable cause) { 26 | super(cause); 27 | } 28 | 29 | protected ClientNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 30 | super(message, cause, enableSuppression, writableStackTrace); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/exception/TokenInvalidException.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.exception; 2 | 3 | /** 4 | * Thrown when the {@code token} is not a valid one. 5 | */ 6 | public class TokenInvalidException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = -7862642265730188024L; 9 | 10 | public TokenInvalidException() { 11 | super(); 12 | } 13 | 14 | public TokenInvalidException(String message) { 15 | super(message); 16 | } 17 | 18 | public TokenInvalidException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public TokenInvalidException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected TokenInvalidException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/interfaces/IAuthenticationGenerator.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.interfaces; 2 | 3 | import com.security.jwt.dto.RawAuthenticationInformationDto; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * Functionality related with the authentication process. 11 | */ 12 | public interface IAuthenticationGenerator { 13 | 14 | /** 15 | * Return the data required for the authentication process. 16 | * 17 | * @param userDetails 18 | * {@link UserDetails} identifier use to get the information to fill the tokens 19 | * 20 | * @return {@link Optional} of {@link RawAuthenticationInformationDto} with information to include 21 | * 22 | * @throws UsernameNotFoundException if the given {@code username} does not exist 23 | */ 24 | Optional getRawAuthenticationInformation(final UserDetails userDetails); 25 | 26 | /** 27 | * Return the key in the access token used to store the {@code username} information. 28 | * 29 | * @return {@link String} 30 | */ 31 | String getUsernameKey(); 32 | 33 | /** 34 | * Return the key in the access token used to store the {@code roles} information. 35 | * 36 | * @return {@link String} 37 | */ 38 | String getRolesKey(); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/interfaces/IUserService.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.interfaces; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | import org.springframework.security.core.userdetails.UserDetailsService; 5 | 6 | /** 7 | * Used to extend functionality provided by {@link UserDetailsService} 8 | */ 9 | public interface IUserService extends UserDetailsService { 10 | 11 | /** 12 | * Verify if the given password matches with the one belongs to {@code userDetails}. 13 | * 14 | * @param passwordToVerify 15 | * Password to verify 16 | * @param userDetails 17 | * {@link UserDetails} which password will be compared 18 | * 19 | * @return {@code true} if {@code passwordToVerify} matches with {@link UserDetails#getPassword()}, {@code false} otherwise. 20 | */ 21 | boolean passwordsMatch(final String passwordToVerify, 22 | final UserDetails userDetails); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/java/com/security/jwt/repository/JwtClientDetailsRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.repository; 2 | 3 | import com.security.jwt.model.JwtClientDetails; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.lang.Nullable; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.Optional; 9 | 10 | @Repository 11 | public interface JwtClientDetailsRepository extends JpaRepository { 12 | 13 | Optional findByClientId(@Nullable String clientId); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | javax.validation.constraints.AssertFalse.message = must be false 2 | javax.validation.constraints.AssertTrue.message = must be true 3 | javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value} 4 | javax.validation.constraints.DecimalMin.message = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value} 5 | javax.validation.constraints.Digits.message = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected) 6 | javax.validation.constraints.Email.message = must be a well-formed email address 7 | javax.validation.constraints.Future.message = must be a future date 8 | javax.validation.constraints.FutureOrPresent.message = must be a date in the present or in the future 9 | javax.validation.constraints.Max.message = must be less than or equal to {value} 10 | javax.validation.constraints.Min.message = must be greater than or equal to {value} 11 | javax.validation.constraints.Negative.message = must be less than 0 12 | javax.validation.constraints.NegativeOrZero.message = must be less than or equal to 0 13 | javax.validation.constraints.NotBlank.message = must not be blank 14 | javax.validation.constraints.NotEmpty.message = must not be empty 15 | javax.validation.constraints.NotNull.message = must not be null 16 | javax.validation.constraints.Null.message = must be null 17 | javax.validation.constraints.Past.message = must be a past date 18 | javax.validation.constraints.PastOrPresent.message = must be a date in the past or in the present 19 | javax.validation.constraints.Pattern.message = must match "{regexp}" 20 | javax.validation.constraints.Positive.message = must be greater than 0 21 | javax.validation.constraints.PositiveOrZero.message = must be greater than or equal to 0 22 | javax.validation.constraints.Size.message = size must be between {min} and {max} -------------------------------------------------------------------------------- /security-jwt-service/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8180 3 | 4 | spring: 5 | config: 6 | import: configserver:http://localhost:8888 -------------------------------------------------------------------------------- /security-jwt-service/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:8180} 3 | 4 | spring: 5 | config: 6 | import: configserver:${CONFIG_SERVER:http://config-server:8888} 7 | -------------------------------------------------------------------------------- /security-jwt-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: security-jwt-service -------------------------------------------------------------------------------- /security-jwt-service/src/test/java/com/security/jwt/controller/BaseControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Qualifier; 5 | import org.springframework.boot.test.mock.mockito.MockBean; 6 | import org.springframework.cache.CacheManager; 7 | import org.springframework.context.ApplicationContext; 8 | 9 | public abstract class BaseControllerTest { 10 | 11 | @Autowired 12 | protected ApplicationContext context; 13 | 14 | // To avoid Hazelcast instance creation 15 | @MockBean 16 | @Qualifier("cacheManager") 17 | private CacheManager mockCacheManager; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /security-jwt-service/src/test/java/com/security/jwt/enums/AuthenticationConfigurationEnumTest.java: -------------------------------------------------------------------------------- 1 | package com.security.jwt.enums; 2 | 3 | import com.security.jwt.exception.ClientNotFoundException; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.Arguments; 7 | import org.junit.jupiter.params.provider.MethodSource; 8 | 9 | import java.util.stream.Stream; 10 | 11 | import static com.security.jwt.enums.AuthenticationConfigurationEnum.SPRING5_MICROSERVICES; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | 15 | public class AuthenticationConfigurationEnumTest { 16 | 17 | static Stream getByClientIdTestCases() { 18 | return Stream.of( 19 | //@formatter:off 20 | // clientId, expectedException, expectedResult 21 | Arguments.of( null, ClientNotFoundException.class, null ), 22 | Arguments.of( "NotFoundClientId", ClientNotFoundException.class, null ), 23 | Arguments.of( SPRING5_MICROSERVICES.getClientId(), null, SPRING5_MICROSERVICES ) 24 | ); //@formatter:on 25 | } 26 | 27 | @ParameterizedTest 28 | @MethodSource("getByClientIdTestCases") 29 | @DisplayName("getByClientId: test cases") 30 | public void getByClientId_testCases(String clientId, Class expectedException, AuthenticationConfigurationEnum expectedResult) { 31 | if (null != expectedException) { 32 | assertThrows(expectedException, () -> AuthenticationConfigurationEnum.getByClientId(clientId)); 33 | } 34 | else { 35 | assertEquals(expectedResult, AuthenticationConfigurationEnum.getByClientId(clientId)); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /security-jwt-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | # Disable configuration server for testing 4 | config: 5 | enabled: false 6 | datasource: 7 | url: jdbc:postgresql://localhost:5432/microservice_test 8 | username: microservice_test 9 | password: microservice_test 10 | jpa: 11 | properties: 12 | hibernate: 13 | dialect: org.hibernate.dialect.PostgreSQL10Dialect 14 | jdbc: 15 | lob: 16 | # With Java >= 9, to avoid: 17 | # SQLFeatureNotSupportedException: Method PgConnection.createClob() is not yet implemented 18 | non_contextual_creation: true 19 | format_sql: false 20 | show_sql: false 21 | 22 | # Disable eureka server connection request for testing 23 | eureka: 24 | client: 25 | enabled: false 26 | 27 | security: 28 | jwe: 29 | encryptionSecret: onlyForTests 30 | 31 | cache: 32 | jwtConfiguration: 33 | entryCapacity: 1 34 | expireInSeconds: 100 35 | name: onlyForTests 36 | 37 | springdoc: 38 | api-docs: 39 | path: onlyForTests 40 | documentation: 41 | apiVersion: 1.0 42 | title: Test Rest Api 43 | description: Test description 44 | security: 45 | authorization: Basic Auth 46 | schema: basic 47 | swagger-ui: 48 | path: testPath 49 | webjars: 50 | prefix: testPrefix 51 | 52 | # Show sql statement 53 | #logging: 54 | # level: 55 | # org: 56 | # hibernate: 57 | # SQL: debug 58 | # type: 59 | # descriptor: 60 | # sql: trace 61 | -------------------------------------------------------------------------------- /security-jwt-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | # To allow mocking final classes 2 | mock-maker-inline -------------------------------------------------------------------------------- /security-oauth-service/.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/** 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### NetBeans ### 17 | /nbproject/private/ 18 | /nbbuild/ 19 | /dist/ 20 | /nbdist/ 21 | /.nb-gradle/ 22 | /build/ 23 | 24 | ### General ### 25 | target/** 26 | .mvn/** 27 | mvnw 28 | mvnw.cmd 29 | -------------------------------------------------------------------------------- /security-oauth-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Source Docker image from https://hub.docker.com/ 2 | FROM eclipse-temurin:17-jdk-alpine as builder 3 | 4 | # Working directory in Docker 5 | WORKDIR /app 6 | 7 | # Copy the application to the Docker working directory 8 | COPY target/*.jar app.jar 9 | 10 | # Extract layers of application's jar 11 | RUN java -Djarmode=layertools -jar app.jar extract 12 | 13 | 14 | # Multi-Stage Build 15 | FROM eclipse-temurin:17-jre-alpine 16 | 17 | # Port used to connect with the application 18 | ENV SERVER_PORT 8181 19 | EXPOSE $SERVER_PORT 20 | 21 | # Default symmetric encryption key 22 | ARG ENCRYPT_KEY=ENCRYPT_KEY 23 | ENV ENCRYPT_KEY ${ENCRYPT_KEY} 24 | 25 | # Default Spring profile 26 | ENV SPRING_PROFILES_ACTIVE docker 27 | 28 | VOLUME /tmp 29 | 30 | ARG DEPENDENCY=/app 31 | 32 | COPY --from=builder ${DEPENDENCY}/dependencies/ ./ 33 | COPY --from=builder ${DEPENDENCY}/spring-boot-loader/ ./ 34 | COPY --from=builder ${DEPENDENCY}/snapshot-dependencies/ ./ 35 | COPY --from=builder ${DEPENDENCY}/application/ ./ 36 | 37 | # Copy the Spring Boot fat JarLauncher into the image and use it to run the application 38 | ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] 39 | 40 | 41 | # ------------------------------------------------------------------------------------------ 42 | # COMMANDS: 43 | # 44 | # 1. Build the image in local: 45 | # 46 | # docker build -t security-oauth-service . -f ./Dockerfile 47 | # 48 | # 2. Build and launch the container: 49 | # 50 | # docker run -p 8181:8181 --rm --name security-oauth-service --add-host=host.docker.internal:host-gateway --network Spring5Microservices security-oauth-service 51 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/SecurityOauthServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SecurityOauthServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SecurityOauthServiceApplication.class, args); 11 | 12 | /* 13 | ConfigurableApplicationContext context = SpringApplication.run(SecurityServiceApplication.class, args); 14 | PasswordEncoder passwordEncoder = context.getBean(PasswordEncoder.class); 15 | System.out.println("------------- PASSWORD " + passwordEncoder.encode("Spring5Microservices")); 16 | */ 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.configuration; 2 | 3 | /** 4 | * Global values used in different part of the application 5 | */ 6 | public class Constants { 7 | 8 | // Database schemas on which the entities have been included 9 | public static final class DATABASE_SCHEMA { 10 | public static final String EAT = "eat"; 11 | public static final String SECURITY = "security"; 12 | } 13 | 14 | // External path 15 | public static final class EXTERNAL_PATH { 16 | public static final String COMMON = "com.spring5microservices.common"; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/configuration/cache/CacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.configuration.cache; 2 | 3 | import com.security.oauth.configuration.Constants; 4 | import lombok.Getter; 5 | import org.cache2k.extra.spring.SpringCache2kCacheManager; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.cache.CacheManager; 8 | import org.springframework.cache.annotation.EnableCaching; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.ComponentScan; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | @Configuration 16 | @ComponentScan(basePackages = {Constants.EXTERNAL_PATH.COMMON}) 17 | @EnableCaching 18 | public class CacheConfiguration { 19 | 20 | @Value("${cache.oauthClient.entryCapacity}") 21 | private long oauthClientCacheEntryCapacity; 22 | 23 | @Value("${cache.oauthClient.expireInMinutes}") 24 | private long oauthClientCacheExpireInMinutes; 25 | 26 | @Value("${cache.oauthClient.name}") 27 | @Getter 28 | private String oauthClientCacheName; 29 | 30 | /** 31 | * Centralized cache configuration to manage the information we want to cache 32 | * 33 | * @return {@link CacheManager} 34 | */ 35 | @Bean 36 | public CacheManager cacheManager() { 37 | return new SpringCache2kCacheManager() 38 | .addCaches( 39 | c -> 40 | c.name(oauthClientCacheName) 41 | .entryCapacity(oauthClientCacheEntryCapacity) 42 | .expireAfterWrite( 43 | oauthClientCacheExpireInMinutes, 44 | TimeUnit.MINUTES 45 | ) 46 | .disableStatistics(true) 47 | ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/configuration/rest/RestRoutes.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.configuration.rest; 2 | 3 | import org.springframework.data.util.Pair; 4 | 5 | public final class RestRoutes { 6 | 7 | public static final class SECURITY_OAUTH { 8 | public static final String ROOT = "/security/oauth"; 9 | } 10 | 11 | /** 12 | * Includes all equivalences between Oauth2 default Urls and new ones in this project 13 | */ 14 | public final static Pair ACCESS_TOKEN_URI = Pair.of("/oauth/token", SECURITY_OAUTH.ROOT + "/token"); 15 | public final static Pair CHECK_TOKEN_URI = Pair.of("/oauth/check_token", SECURITY_OAUTH.ROOT + "/check_token"); 16 | public final static Pair USER_AUTHORIZATION_URI = Pair.of("/oauth/authorize", SECURITY_OAUTH.ROOT + "/authorize"); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/configuration/security/PasswordEncoderConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.configuration.security; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class PasswordEncoderConfiguration { 10 | 11 | /** 12 | * Encoder used to manage the passwords included in database. 13 | * 14 | * @return {@link PasswordEncoder} 15 | */ 16 | @Bean 17 | public PasswordEncoder passwordEncoder() { 18 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/enums/RoleEnum.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.enums; 2 | 3 | /** 4 | * Allowed roles of the users included in the application 5 | */ 6 | public enum RoleEnum { 7 | ADMIN, 8 | USER 9 | } 10 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/model/Role.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.model; 2 | 3 | import com.security.oauth.configuration.Constants; 4 | import com.security.oauth.enums.RoleEnum; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | 11 | import javax.persistence.Entity; 12 | import javax.persistence.EnumType; 13 | import javax.persistence.Enumerated; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.GenerationType; 16 | import javax.persistence.Id; 17 | import javax.persistence.SequenceGenerator; 18 | import javax.persistence.Table; 19 | import javax.validation.constraints.NotNull; 20 | import java.io.Serializable; 21 | 22 | @AllArgsConstructor 23 | @Builder 24 | @Data 25 | @Entity 26 | @EqualsAndHashCode(of = {"name"}) 27 | @NoArgsConstructor 28 | @Table(schema = Constants.DATABASE_SCHEMA.EAT) 29 | public class Role implements Serializable { 30 | 31 | private static final long serialVersionUID = 4833183317359581882L; 32 | 33 | @Id 34 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = Constants.DATABASE_SCHEMA.EAT + ".role_generator") 35 | @SequenceGenerator(name = Constants.DATABASE_SCHEMA.EAT + ".role_generator", sequenceName = Constants.DATABASE_SCHEMA.EAT + ".role_id_seq", allocationSize = 1) 36 | private Integer id; 37 | 38 | @NotNull 39 | @Enumerated(EnumType.STRING) 40 | private RoleEnum name; 41 | 42 | } -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.repository; 2 | 3 | import com.security.oauth.model.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface RoleRepository extends JpaRepository {} 9 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.repository; 2 | 3 | import com.security.oauth.model.Role; 4 | import com.security.oauth.model.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.lang.Nullable; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.Optional; 10 | 11 | @Repository 12 | public interface UserRepository extends JpaRepository { 13 | 14 | /** 15 | * Gets the {@link User} (including its {@link Role}s) which {@link User#username} matches with the given one. 16 | * 17 | * @param username 18 | * Username to search a coincidence in {@link User#username} 19 | * 20 | * @return {@link Optional} with the {@link User} which username matches with the given one. 21 | * {@link Optional#empty()} otherwise. 22 | */ 23 | Optional findByUsername(@Nullable String username); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/java/com/security/oauth/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.security.oauth.service; 2 | 3 | import com.security.oauth.model.User; 4 | import com.security.oauth.repository.UserRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.security.authentication.AccountStatusUserDetailsChecker; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | 13 | import static java.lang.String.format; 14 | import static java.util.Optional.ofNullable; 15 | 16 | @Service(value = "userDetailsService") 17 | public class UserService implements UserDetailsService { 18 | 19 | private final UserRepository userRepository; 20 | 21 | @Autowired 22 | public UserService(final @Lazy UserRepository userRepository) { 23 | this.userRepository = userRepository; 24 | } 25 | 26 | 27 | /** 28 | * Gets {@link UserDetails} information in database related with the given {@link User#getUsername()} 29 | * 30 | * @param username 31 | * Username to search a coincidence in {@link User#getUsername()} 32 | * 33 | * @return {@link UserDetails} 34 | * 35 | * @throws UsernameNotFoundException if the given username does not exist in database. 36 | * @see {@link AccountStatusUserDetailsChecker#check(UserDetails)} for more information about the other ones. 37 | */ 38 | @Override 39 | public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { 40 | return ofNullable(username) 41 | .flatMap(userRepository::findByUsername) 42 | .map(u -> { 43 | new AccountStatusUserDetailsChecker() 44 | .check(u); 45 | return u; 46 | }) 47 | .orElseThrow(() -> 48 | new UsernameNotFoundException( 49 | format("Username: %s not found in database", 50 | username) 51 | ) 52 | ); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /security-oauth-service/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8181 3 | 4 | spring: 5 | config: 6 | import: configserver:http://localhost:8888 -------------------------------------------------------------------------------- /security-oauth-service/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${SERVER_PORT:8181} 3 | 4 | spring: 5 | config: 6 | import: configserver:${CONFIG_SERVER:http://config-server:8888} -------------------------------------------------------------------------------- /security-oauth-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: security-oauth-service -------------------------------------------------------------------------------- /security-oauth-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | # Disable configuration server for testing 4 | config: 5 | enabled: false 6 | datasource: 7 | url: jdbc:postgresql://localhost:5432/microservice_test 8 | username: microservice_test 9 | password: microservice_test 10 | jpa: 11 | properties: 12 | hibernate: 13 | dialect: org.hibernate.dialect.PostgreSQL10Dialect 14 | jdbc: 15 | lob: 16 | # With Java >= 9, to avoid: 17 | # SQLFeatureNotSupportedException: Method PgConnection.createClob() is not yet implemented 18 | non_contextual_creation: true 19 | format_sql: false 20 | show_sql: false 21 | 22 | # Disable eureka server connection request for testing 23 | eureka: 24 | client: 25 | enabled: false 26 | 27 | #show sql statement 28 | #logging: 29 | # level: 30 | # org: 31 | # hibernate: 32 | # SQL: debug 33 | # type: 34 | # descriptor: 35 | # sql: trace 36 | --------------------------------------------------------------------------------