From 5c98648e5667b669a674474f55d51a3420987f7f Mon Sep 17 00:00:00 2001 From: Thomas Georgios Giannos Date: Fri, 9 Feb 2024 18:50:34 +0200 Subject: [PATCH] Adding new annotation service --- annotation-service/Dockerfile | 27 ++ annotation-service/annotation-web/.gitignore | 33 ++ annotation-service/annotation-web/pom.xml | 90 ++++ .../web/AnnotationApplication.java | 26 ++ .../notification/web/WebConfiguration.java | 40 ++ .../OwnedAuthorizationHandler.java | 44 ++ .../config/AppMessageSourceConfiguration.java | 25 ++ .../web/config/SecurityConfiguration.java | 142 +++++++ .../GlobalExceptionHandler.java | 193 +++++++++ .../web/controllers/PrincipalController.java | 91 ++++ .../TenantConfigurationController.java | 129 ++++++ .../error/GenericErrorHandler.java | 17 + .../web/interceptors/UserInterceptor.java | 101 +++++ .../UserInterceptorCacheOptions.java | 10 + .../UserInterceptorCacheService.java | 66 +++ .../cite/notification/web/model/Account.java | 160 +++++++ .../web/model/AccountBuilder.java | 83 ++++ .../notification/web/model/QueryResult.java | 37 ++ .../tenant/TenantByCodeCacheOptions.java | 10 + .../tenant/TenantByCodeCacheService.java | 76 ++++ .../scope/tenant/TenantByIdCacheOptions.java | 10 + .../scope/tenant/TenantByIdCacheService.java | 76 ++++ .../web/scope/tenant/TenantInterceptor.java | 166 ++++++++ .../tenant/TenantScopeClaimInterceptor.java | 196 +++++++++ .../tenant/TenantScopeConfiguration.java | 9 + .../tenant/TenantScopeHeaderInterceptor.java | 161 +++++++ .../scope/tenant/TenantScopeProperties.java | 43 ++ .../tenant/UserAllowedTenantCacheOptions.java | 10 + .../tenant/UserAllowedTenantCacheService.java | 90 ++++ .../web/scope/user/UserInterceptor.java | 154 +++++++ .../user/UserInterceptorCacheOptions.java | 10 + .../user/UserInterceptorCacheService.java | 77 ++++ .../src/main/resources/config/application.yml | 20 + .../src/main/resources/config/cache.yml | 77 ++++ .../src/main/resources/config/cipher.yml | 35 ++ .../src/main/resources/config/cors-devel.yml | 3 + .../src/main/resources/config/cors.yml | 7 + .../src/main/resources/config/db-devel.yml | 7 + .../src/main/resources/config/db.yml | 24 ++ .../src/main/resources/config/email.yml | 14 + .../src/main/resources/config/errors.yml | 43 ++ .../src/main/resources/config/formatting.yml | 6 + .../src/main/resources/config/idpclaims.yml | 39 ++ .../src/main/resources/config/locale.yml | 4 + .../main/resources/config/logging-devel.yml | 2 + .../src/main/resources/config/logging.yml | 35 ++ .../src/main/resources/config/permissions.yml | 182 ++++++++ .../src/main/resources/config/queue-devel.yml | 21 + .../src/main/resources/config/queue.yml | 56 +++ .../src/main/resources/config/security.yml | 20 + .../main/resources/config/server-devel.yml | 2 + .../src/main/resources/config/server.yml | 3 + .../src/main/resources/config/tenant.yml | 8 + .../main/resources/logging/logback-dev.xml | 61 +++ .../resources/messages/messages.properties | 10 + .../resources/messages/messages_el.properties | 6 + annotation-service/annotation/.gitignore | 33 ++ annotation-service/annotation/pom.xml | 99 +++++ .../notification/audit/AuditableAction.java | 48 +++ .../authorization/AuthorizationFlags.java | 8 + .../OwnedAuthorizationRequirement.java | 6 + .../authorization/OwnedResource.java | 26 ++ .../authorization/Permission.java | 48 +++ ...ormattingUserprofileCacheCacheService.java | 93 ++++ .../common/JsonHandlingService.java | 43 ++ .../cite/notification/common/StringUtils.java | 8 + .../common/XmlHandlingService.java | 117 ++++++ .../common/enums/ContactInfoType.java | 29 ++ .../notification/common/enums/EnumUtils.java | 16 + .../notification/common/enums/IsActive.java | 28 ++ .../common/enums/TenantConfigurationType.java | 29 ++ .../common/lock/LockByKeyManager.java | 58 +++ .../scope/fake/FakeRequestAttributes.java | 125 ++++++ .../common/scope/fake/FakeRequestScope.java | 40 ++ .../tenant/MultitenancyConfiguration.java | 9 + .../scope/tenant/MultitenancyProperties.java | 16 + .../common/scope/tenant/TenantScope.java | 86 ++++ .../common/scope/tenant/TenantScoped.java | 8 + .../common/scope/user/UserScope.java | 34 ++ .../common/types/notification/Attachment.java | 36 ++ .../types/notification/ContactPair.java | 32 ++ .../types/tenant/TenantConfigEntity.java | 35 ++ .../tenant/TenantDepositConfigEntity.java | 25 ++ .../TenantFileTransformersConfigEntity.java | 24 ++ .../types/tenant/TenantSourceEntity.java | 79 ++++ ...tUserLocaleConfigurationDataContainer.java | 37 ++ ...EmailClientConfigurationDataContainer.java | 87 ++++ .../types/user/AdditionalInfoEntity.java | 60 +++ .../common/types/xml/XmlBuilder.java | 87 ++++ .../common/types/xml/XmlSerializable.java | 11 + .../common/validation/BaseValidator.java | 63 +++ .../config/db/NamingStrategyProperties.java | 20 + .../db/PrefixPhysicalNamingStrategy.java | 28 ++ .../config/email/EmailConfig.java | 9 + .../config/email/EmailProperties.java | 17 + .../FormattingServiceConfiguration.java | 9 + .../FormattingServiceProperties.java | 38 ++ .../FormattingUserprofileCacheOptions.java | 10 + .../convention/ConventionService.java | 41 ++ .../convention/ConventionServiceImpl.java | 150 +++++++ .../notification/data/LanguageEntity.java | 94 +++++ .../notification/data/QueueInboxEntity.java | 194 +++++++++ .../notification/data/QueueOutboxEntity.java | 205 +++++++++ .../data/TenantConfigurationEntity.java | 113 +++++ .../cite/notification/data/TenantEntity.java | 81 ++++ .../data/TenantScopedEntityManager.java | 97 +++++ .../notification/data/TenantUserEntity.java | 81 ++++ .../data/UserContactInfoEntity.java | 125 ++++++ .../data/UserCredentialEntity.java | 97 +++++ .../gr/cite/notification/data/UserEntity.java | 96 +++++ .../notification/data/UserRoleEntity.java | 97 +++++ .../conventers/ContactInfoTypeConverter.java | 11 + .../data/conventers/DatabaseEnum.java | 8 + .../conventers/DatabaseEnumConverter.java | 21 + .../data/conventers/IsActiveConverter.java | 11 + .../TenantConfigurationTypeConverter.java | 11 + .../data/tenant/TenantFilterAspect.java | 49 +++ .../data/tenant/TenantListener.java | 59 +++ .../data/tenant/TenantScopedBaseEntity.java | 45 ++ .../notification/data/types/JsonSQLType.java | 80 ++++ .../errorcode/ErrorDescription.java | 22 + .../ErrorThesaurusConfiguration.java | 20 + .../errorcode/ErrorThesaurusProperties.java | 92 ++++ .../cite/notification/event/EventBroker.java | 37 ++ .../event/TenantTouchedEvent.java | 42 ++ .../event/UserAddedToTenantEvent.java | 32 ++ .../event/UserRemovedFromTenantEvent.java | 32 ++ .../notification/event/UserTouchedEvent.java | 42 ++ .../integrationevent/AppRabbitConfigurer.java | 52 +++ .../InboxIntegrationEventConfigurer.java | 33 ++ .../IntegrationEventContextImpl.java | 24 ++ .../OutboxIntegrationEventConfigurer.java | 68 +++ .../integrationevent/TrackedEvent.java | 18 + .../inbox/ConsistencyHandler.java | 7 + .../inbox/ConsistencyPredicates.java | 5 + .../inbox/EventProcessingStatus.java | 8 + .../inbox/InboxPrincipal.java | 60 +++ .../inbox/InboxProperties.java | 61 +++ .../inbox/InboxRepositoryImpl.java | 363 ++++++++++++++++ .../inbox/IntegrationEventHandler.java | 7 + .../inbox/IntegrationEventProperties.java | 25 ++ .../TenantRemovalConsistencyHandler.java | 26 ++ .../TenantRemovalConsistencyPredicates.java | 23 + .../TenantRemovalIntegrationEvent.java | 19 + .../TenantRemovalIntegrationEventHandler.java | 7 + ...antRemovalIntegrationEventHandlerImpl.java | 111 +++++ .../TenantTouchedIntegrationEvent.java | 29 ++ .../TenantTouchedIntegrationEventHandler.java | 7 + ...antTouchedIntegrationEventHandlerImpl.java | 107 +++++ .../UserRemovalConsistencyHandler.java | 25 ++ .../UserRemovalConsistencyPredicates.java | 23 + .../UserRemovalIntegrationEvent.java | 28 ++ .../UserRemovalIntegrationEventHandler.java | 7 + ...serRemovalIntegrationEventHandlerImpl.java | 141 +++++++ .../UserTouchedIntegrationEvent.java | 194 +++++++++ .../UserTouchedIntegrationEventHandler.java | 7 + ...serTouchedIntegrationEventHandlerImpl.java | 129 ++++++ .../outbox/OutboxIntegrationEvent.java | 19 + .../outbox/OutboxProperties.java | 58 +++ .../outbox/OutboxRepositoryImpl.java | 396 ++++++++++++++++++ .../locale/LocaleConfiguration.java | 9 + .../notification/locale/LocaleProperties.java | 32 ++ .../notification/locale/LocaleService.java | 18 + .../locale/LocaleServiceImpl.java | 85 ++++ .../gr/cite/notification/model/Tenant.java | 82 ++++ .../model/TenantConfiguration.java | 132 ++++++ .../java/gr/cite/notification/model/User.java | 80 ++++ .../notification/model/UserCredential.java | 81 ++++ .../model/builder/BaseBuilder.java | 95 +++++ .../model/builder/TenantBuilder.java | 68 +++ .../builder/TenantConfigurationBuilder.java | 117 ++++++ .../model/builder/UserBuilder.java | 69 +++ .../tenantconfig/TenantConfigBuilder.java | 60 +++ .../TenantDepositConfigBuilder.java | 61 +++ .../TenantFileTransformersBuilder.java | 61 +++ .../tenantconfig/TenantSourceBuilder.java | 62 +++ .../model/censorship/BaseCensor.java | 22 + .../model/censorship/TenantCensor.java | 32 ++ .../censorship/TenantConfigurationCensor.java | 32 ++ .../model/censorship/UserCensor.java | 50 +++ .../deleter/TenantConfigurationDeleter.java | 78 ++++ .../model/deleter/TenantDeleter.java | 75 ++++ .../model/deleter/UserCredentialDeleter.java | 74 ++++ .../model/deleter/UserDeleter.java | 90 ++++ .../TenantTouchedIntegrationEventPersist.java | 78 ++++ .../UserTouchedIntegrationEventPersist.java | 80 ++++ ...TenantConfigurationEmailClientPersist.java | 177 ++++++++ ...igurationUserLocaleIntegrationPersist.java | 92 ++++ .../model/tenantconfig/TenantConfig.java | 28 ++ .../tenantconfig/TenantDepositConfig.java | 17 + .../TenantFileTransformersConfig.java | 17 + .../model/tenantconfig/TenantSource.java | 73 ++++ .../notification/model/user/PublicUser.java | 29 ++ .../notification/query/QueueInboxQuery.java | 219 ++++++++++ .../notification/query/QueueOutboxQuery.java | 235 +++++++++++ .../query/TenantConfigurationQuery.java | 129 ++++++ .../cite/notification/query/TenantQuery.java | 131 ++++++ .../query/UserCredentialQuery.java | 236 +++++++++++ .../gr/cite/notification/query/UserQuery.java | 159 +++++++ .../lookup/TenantConfigurationLookup.java | 52 +++ .../query/lookup/TenantLookup.java | 51 +++ .../notification/query/lookup/UserLookup.java | 52 +++ .../notification/service/AuthnSandbox.java | 29 ++ .../service/formatting/FormattingService.java | 26 ++ .../formatting/FormattingServiceImpl.java | 171 ++++++++ .../service/tenant/TenantService.java | 21 + .../service/tenant/TenantServiceImpl.java | 105 +++++ .../TenantConfigurationService.java | 20 + .../TenantConfigurationServiceImpl.java | 207 +++++++++ .../service/user/UserService.java | 21 + .../service/user/UserServiceImpl.java | 155 +++++++ annotation-service/pom.xml | 184 ++++++++ annotation-service/settings.xml | 12 + 213 files changed, 13344 insertions(+) create mode 100644 annotation-service/Dockerfile create mode 100644 annotation-service/annotation-web/.gitignore create mode 100644 annotation-service/annotation-web/pom.xml create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/AnnotationApplication.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/WebConfiguration.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/authorization/OwnedAuthorizationHandler.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/AppMessageSourceConfiguration.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllerhandler/GlobalExceptionHandler.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/PrincipalController.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/error/GenericErrorHandler.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/Account.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/AccountBuilder.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/QueryResult.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheOptions.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheService.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheOptions.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheService.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeConfiguration.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeProperties.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheOptions.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheService.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java create mode 100644 annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java create mode 100644 annotation-service/annotation-web/src/main/resources/config/application.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/cache.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/cipher.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/cors-devel.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/cors.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/db-devel.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/db.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/email.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/errors.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/formatting.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/idpclaims.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/locale.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/logging-devel.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/logging.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/permissions.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/queue-devel.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/queue.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/security.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/server-devel.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/server.yml create mode 100644 annotation-service/annotation-web/src/main/resources/config/tenant.yml create mode 100644 annotation-service/annotation-web/src/main/resources/logging/logback-dev.xml create mode 100644 annotation-service/annotation-web/src/main/resources/messages/messages.properties create mode 100644 annotation-service/annotation-web/src/main/resources/messages/messages_el.properties create mode 100644 annotation-service/annotation/.gitignore create mode 100644 annotation-service/annotation/pom.xml create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/audit/AuditableAction.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/authorization/AuthorizationFlags.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedAuthorizationRequirement.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedResource.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/authorization/Permission.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/JsonHandlingService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/StringUtils.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/XmlHandlingService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/EnumUtils.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/IsActive.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/TenantConfigurationType.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestAttributes.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestScope.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyConfiguration.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScoped.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/user/UserScope.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/Attachment.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/ContactPair.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/DefaultUserLocaleConfigurationDataContainer.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/EmailClientConfigurationDataContainer.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlSerializable.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/common/validation/BaseValidator.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/db/NamingStrategyProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/db/PrefixPhysicalNamingStrategy.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailConfig.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceConfiguration.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingUserprofileCacheOptions.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionServiceImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/LanguageEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueInboxEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantUserEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/UserCredentialEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/UserEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/UserRoleEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnum.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantFilterAspect.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantListener.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/data/types/JsonSQLType.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorDescription.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusConfiguration.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/event/EventBroker.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/event/TenantTouchedEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/event/UserAddedToTenantEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/event/UserRemovedFromTenantEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/event/UserTouchedEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/AppRabbitConfigurer.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/InboxIntegrationEventConfigurer.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/IntegrationEventContextImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/OutboxIntegrationEventConfigurer.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/TrackedEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyPredicates.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/EventProcessingStatus.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxPrincipal.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxRepositoryImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyPredicates.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandlerImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandlerImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyPredicates.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandlerImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandler.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandlerImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxIntegrationEvent.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxRepositoryImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleConfiguration.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleProperties.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleServiceImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/Tenant.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/TenantConfiguration.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/User.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/UserCredential.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/BaseBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/UserBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantConfigBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantDepositConfigBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantFileTransformersBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantSourceBuilder.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/BaseCensor.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantCensor.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantConfigurationCensor.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/UserCensor.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantConfigurationDeleter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantDeleter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserCredentialDeleter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/TenantTouchedIntegrationEventPersist.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantDepositConfig.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantFileTransformersConfig.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantSource.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/model/user/PublicUser.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueInboxQuery.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantQuery.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/UserCredentialQuery.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/UserQuery.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantLookup.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/UserLookup.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/AuthnSandbox.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantServiceImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserService.java create mode 100644 annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserServiceImpl.java create mode 100644 annotation-service/pom.xml create mode 100644 annotation-service/settings.xml diff --git a/annotation-service/Dockerfile b/annotation-service/Dockerfile new file mode 100644 index 000000000..31d5b22f6 --- /dev/null +++ b/annotation-service/Dockerfile @@ -0,0 +1,27 @@ +####################################### Build stage ####################################### +FROM maven:3.9-eclipse-temurin-17-alpine AS build-stage + +ARG CITE_MAVEN_ACCOUNT_USR +ARG CITE_MAVEN_ACCOUNT_PSW + +COPY pom.xml /build/ +COPY annotation /build/annotation/ +COPY annotation-web /build/annotation-web/ +COPY settings.xml /root/.m2/settings.xml +RUN rm -f /build/annotation-web/src/main/resources/config/app.env +RUN rm -f /build/annotation-web/src/main/resources/config/*-dev.yml +RUN rm -f /build/annotation-web/src/main/resources/logging/*.xml +RUN rm -f /build/annotation-web/src/main/resources/certificates/*.crt + +WORKDIR /build/ + +RUN mvn -Dserver.username=${CITE_MAVEN_ACCOUNT_USR} -Dserver.password=${CITE_MAVEN_ACCOUNT_PSW} dependency:go-offline +# Build project +RUN mvn -Dserver.username=${CITE_MAVEN_ACCOUNT_USR} -Dserver.password=${CITE_MAVEN_ACCOUNT_PSW} clean package + +######################################## Run Stage ######################################## +FROM eclipse-temurin:17-jre-ubi9-minimal + +COPY --from=build-stage /build/annotation-web/target/sti-viewer-annotation-web.jar /app/sti-viewer-annotation-web.jar + +ENTRYPOINT ["java","-Dspring.config.additional-location=file:/config/","-Dspring.profiles.active=${PROFILE}","-Djava.security.egd=file:/dev/./urandom","-jar","/app/sti-viewer-annotation-web.jar"] \ No newline at end of file diff --git a/annotation-service/annotation-web/.gitignore b/annotation-service/annotation-web/.gitignore new file mode 100644 index 000000000..5eac309eb --- /dev/null +++ b/annotation-service/annotation-web/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ \ No newline at end of file diff --git a/annotation-service/annotation-web/pom.xml b/annotation-service/annotation-web/pom.xml new file mode 100644 index 000000000..3d4ae5451 --- /dev/null +++ b/annotation-service/annotation-web/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + + gr.cite + annotation-service-parent + 1.0.0 + ../pom.xml + + + annotation-web + jar + + + 21 + 21 + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.postgresql + postgresql + + + org.hibernate.orm + hibernate-core + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + gr.cite + annotation + 1.0.0 + + + + gr.cite + oidc-authz + 2.1.0 + + + gr.cite + cache + 2.1.0 + + + gr.cite + exceptions-web + 1.0.0 + + + gr.cite + cors-web + 1.0.0 + + + + + argos-annotation-web + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/AnnotationApplication.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/AnnotationApplication.java new file mode 100644 index 000000000..c001b9329 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/AnnotationApplication.java @@ -0,0 +1,26 @@ +package gr.cite.notification.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.scheduling.annotation.EnableAsync; + +@SpringBootApplication( + scanBasePackages = { + "eu.eudat", + "gr.cite", + "gr.cite.queueoutbox", + "gr.cite.queueinbox", + "gr.cite.notification.integrationevent", + "gr.cite.tools", + "gr.cite.commons"}) +@EntityScan({ + "gr.cite.notification.data"}) +@EnableAsync +public class AnnotationApplication { + + public static void main(String[] args) { + SpringApplication.run(AnnotationApplication.class, args); + } + +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/WebConfiguration.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/WebConfiguration.java new file mode 100644 index 000000000..a72bf0588 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/WebConfiguration.java @@ -0,0 +1,40 @@ +package gr.cite.notification.web; + +import gr.cite.notification.web.interceptors.UserInterceptor; +import gr.cite.notification.web.scope.tenant.TenantInterceptor; +import gr.cite.notification.web.scope.tenant.TenantScopeClaimInterceptor; +import gr.cite.notification.web.scope.tenant.TenantScopeHeaderInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfiguration implements WebMvcConfigurer { + private final TenantInterceptor tenantInterceptor; + private final TenantScopeHeaderInterceptor scopeHeaderInterceptor; + private final TenantScopeClaimInterceptor scopeClaimInterceptor; + private final UserInterceptor userInterceptor; + + @Autowired + public WebConfiguration( + TenantInterceptor tenantInterceptor, + TenantScopeHeaderInterceptor scopeHeaderInterceptor, + TenantScopeClaimInterceptor scopeClaimInterceptor, + UserInterceptor userInterceptor + ) { + this.tenantInterceptor = tenantInterceptor; + this.scopeHeaderInterceptor = scopeHeaderInterceptor; + this.scopeClaimInterceptor = scopeClaimInterceptor; + this.userInterceptor = userInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + int order = 1; + registry.addWebRequestInterceptor(scopeHeaderInterceptor).order(order++); + registry.addWebRequestInterceptor(scopeClaimInterceptor).order(order++); + registry.addWebRequestInterceptor(userInterceptor).order(order++); + registry.addWebRequestInterceptor(tenantInterceptor).order(order++); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/authorization/OwnedAuthorizationHandler.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/authorization/OwnedAuthorizationHandler.java new file mode 100644 index 000000000..e95eb1556 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/authorization/OwnedAuthorizationHandler.java @@ -0,0 +1,44 @@ +package gr.cite.notification.web.authorization; + +import gr.cite.commons.web.authz.handler.AuthorizationHandler; +import gr.cite.commons.web.authz.handler.AuthorizationHandlerContext; +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import gr.cite.notification.authorization.OwnedAuthorizationRequirement; +import gr.cite.notification.authorization.OwnedResource; +import gr.cite.notification.common.scope.user.UserScope; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("ownedAuthorizationHandler") +public class OwnedAuthorizationHandler extends AuthorizationHandler { + + private final UserScope userScope; + + @Autowired + public OwnedAuthorizationHandler(UserScope userScope) { + this.userScope = userScope; + } + + @Override + public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) { + OwnedAuthorizationRequirement req = (OwnedAuthorizationRequirement) requirement; + + OwnedResource rs = (OwnedResource) resource; + + boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated(); + if (!isAuthenticated) return ACCESS_NOT_DETERMINED; + + if (this.userScope.getUserIdSafe() == null) return ACCESS_NOT_DETERMINED; + + if (rs != null && rs.getUserIds() != null && rs.getUserIds().contains(this.userScope.getUserIdSafe())) return ACCESS_GRANTED; + + return ACCESS_NOT_DETERMINED; + } + + @Override + public Class supporting() { + return OwnedAuthorizationRequirement.class; + } + +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/AppMessageSourceConfiguration.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/AppMessageSourceConfiguration.java new file mode 100644 index 000000000..66adc10da --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/AppMessageSourceConfiguration.java @@ -0,0 +1,25 @@ +package gr.cite.notification.web.config; + +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +@Configuration +public class AppMessageSourceConfiguration { + @Bean + public MessageSource messageSource() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasename("classpath:messages/messages"); + messageSource.setDefaultEncoding("UTF-8"); + return messageSource; + } + + @Bean + public LocalValidatorFactoryBean getValidator() { + LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); + bean.setValidationMessageSource(messageSource()); + return bean; + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java new file mode 100644 index 000000000..439186b82 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java @@ -0,0 +1,142 @@ +package gr.cite.notification.web.config; + +import gr.cite.commons.web.authz.handler.AuthorizationHandler; +import gr.cite.commons.web.authz.handler.PermissionClientAuthorizationHandler; +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; +import gr.cite.commons.web.authz.policy.AuthorizationRequirementMapper; +import gr.cite.commons.web.authz.policy.AuthorizationResource; +import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyConfigurer; +import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyResolverStrategy; +import gr.cite.commons.web.oidc.configuration.WebSecurityProperties; +import gr.cite.notification.authorization.OwnedAuthorizationRequirement; +import gr.cite.notification.authorization.OwnedResource; +import gr.cite.notification.web.authorization.OwnedAuthorizationHandler; +import jakarta.servlet.Filter; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; + +import java.util.List; +import java.util.Set; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + private final WebSecurityProperties webSecurityProperties; + private final AuthenticationManagerResolver authenticationManagerResolver; + private final Filter apiKeyFilter; + private final OwnedAuthorizationHandler ownedAuthorizationHandler; + + @Autowired + public SecurityConfiguration(WebSecurityProperties webSecurityProperties, + @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, + @Qualifier("apiKeyFilter") Filter apiKeyFilter, + @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler) { + this.webSecurityProperties = webSecurityProperties; + this.authenticationManagerResolver = authenticationManagerResolver; + this.apiKeyFilter = apiKeyFilter; + this.ownedAuthorizationHandler = ownedAuthorizationHandler; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + HttpSecurity tempHttp = http + .csrf(AbstractHttpConfigurer::disable) + .cors(httpSecurityCorsConfigurer -> {}) + .headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + .addFilterBefore(apiKeyFilter, AbstractPreAuthenticatedProcessingFilter.class) + .authorizeHttpRequests(authRequest -> + authRequest.requestMatchers(buildAntPatterns(webSecurityProperties.getAllowedEndpoints())).anonymous() + .requestMatchers(buildAntPatterns(webSecurityProperties.getAuthorizedEndpoints())).authenticated()) + .sessionManagement( sessionManagementConfigurer-> sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.NEVER)) + .oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); + return tempHttp.build(); + } + + @Bean + AuthorizationPolicyConfigurer authorizationPolicyConfigurer() { + return new AuthorizationPolicyConfigurer() { + + @Override + public AuthorizationPolicyResolverStrategy strategy() { + return AuthorizationPolicyResolverStrategy.STRICT_CONSENSUS_BASED; + } + + //Here you can register your custom authorization handlers, which will get used as well as the existing ones + //This is optional and can be omitted + //If not set / set to null, only the default authorization handlers will be used + @Override + public List> addCustomHandlers() { + return List.of(ownedAuthorizationHandler); + } + + //Here you can register your custom authorization requirements (if any) + //This is optional and can be omitted + //If not set / set to null, only the default authorization requirements will be used + @Override + public List extendRequirements() { + return List.of( +// new TimeOfDayAuthorizationRequirement(new TimeOfDay("08:00","16:00"), true) + ); + } + + //Here you can select handlers you want to disable by providing the classes they are implemented by + //You can disable any handler (including any custom one) + //This is optional and can be omitted + //If not set / set to null, all the handlers will be invoked, based on their requirement support + //In the example below, the default client handler will be ignored by the resolver + @Override + public List>> disableHandlers() { + return List.of(PermissionClientAuthorizationHandler.class); + } + }; + } + + @Bean + AuthorizationRequirementMapper authorizationRequirementMapper() { + return new AuthorizationRequirementMapper() { + @Override + public AuthorizationRequirement map(AuthorizationResource resource, boolean matchAll, String[] permissions) { + Class type = resource.getClass(); + if (!AuthorizationResource.class.isAssignableFrom(type)) throw new IllegalArgumentException("resource"); + + if (OwnedResource.class.equals(type)) { + return new OwnedAuthorizationRequirement(); + } + throw new IllegalArgumentException("resource"); + } + }; + } + + private String[] buildAntPatterns(Set endpoints) { + if (endpoints == null) { + return new String[0]; + } + return endpoints.stream() + .filter(endpoint -> endpoint != null && !endpoint.isBlank()) + .map(endpoint -> "/" + stripUnnecessaryCharacters(endpoint) + "/**") + .toArray(String[]::new); + } + + private String stripUnnecessaryCharacters(String endpoint) { + endpoint = endpoint.strip(); + if (endpoint.startsWith("/")) { + endpoint = endpoint.substring(1); + } + if (endpoint.endsWith("/")) { + endpoint = endpoint.substring(0, endpoint.length() - 1); + } + return endpoint; + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllerhandler/GlobalExceptionHandler.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllerhandler/GlobalExceptionHandler.java new file mode 100644 index 000000000..b8a3b685c --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllerhandler/GlobalExceptionHandler.java @@ -0,0 +1,193 @@ +package gr.cite.notification.web.controllerhandler; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.tools.exception.*; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; + +import java.text.MessageFormat; +import java.util.Map; + +@RestControllerAdvice +@ControllerAdvice +public class GlobalExceptionHandler { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(GlobalExceptionHandler.class)); + + private final JsonHandlingService jsonHandlingService; + + public GlobalExceptionHandler(JsonHandlingService jsonHandlingService) { + this.jsonHandlingService = jsonHandlingService; + } + + + @ExceptionHandler(Exception.class) + public ResponseEntity handleUnexpectedErrors(Exception exception, WebRequest request) throws Exception { + HandledException handled = this.handleException(exception, request); + this.log(handled.getLevel(), exception, MessageFormat.format("returning code {0} and payload {1}", handled.getStatusCode(), handled.getMessage())); + return new ResponseEntity<>(handled.getMessage(), handled.getStatusCode()); + } + + public void log(System.Logger.Level level, Exception e, String message) { + if (level != null) { + switch (level) { + case TRACE: + logger.trace(message, e); + break; + case DEBUG: + logger.debug(message, e); + break; + case INFO: + logger.info(message, e); + break; + case WARNING: + logger.warn(message, e); + break; + case ERROR: + logger.error(message, e); + break; + } + } + } + + public HandledException handleException(Exception exception, WebRequest request) throws Exception { + HttpStatus statusCode; + Map result; + System.Logger.Level logLevel; + + switch (exception){ + case MyNotFoundException myNotFoundException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.NOT_FOUND; + int code = myNotFoundException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myNotFoundException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myNotFoundException.getMessage()) + ); + } + } + case MyUnauthorizedException myUnauthorizedException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.UNAUTHORIZED; + int code = myUnauthorizedException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myUnauthorizedException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myUnauthorizedException.getMessage()) + ); + } + } + case MyForbiddenException myForbiddenException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.FORBIDDEN; + int code = myForbiddenException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myForbiddenException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myForbiddenException.getMessage()) + ); + } + } + case MyValidationException myValidationException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.BAD_REQUEST; + int code = myValidationException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myValidationException.getMessage()), + Map.entry("message", myValidationException.getErrors()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myValidationException.getMessage()), + Map.entry("message", myValidationException.getErrors()) + ); + } + } + case MyApplicationException myApplicationException -> { + logLevel = System.Logger.Level.ERROR; + statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + int code = myApplicationException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myApplicationException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myApplicationException.getMessage()) + ); + } + } + default -> { + logLevel = System.Logger.Level.ERROR; + statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + result = Map.ofEntries( + Map.entry("error", "System error") + ); + } + }; + String serialization = this.jsonHandlingService.toJsonSafe(result); + return new HandledException(statusCode, serialization, logLevel); + } + + public static class HandledException{ + public HttpStatus statusCode; + public String message; + public System.Logger.Level level; + + public HandledException(HttpStatus statusCode, String message, System.Logger.Level level) { + this.statusCode = statusCode; + this.message = message; + this.level = level; + } + + public HttpStatus getStatusCode() { + return statusCode; + } + + public void setStatusCode(HttpStatus statusCode) { + this.statusCode = statusCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public System.Logger.Level getLevel() { + return level; + } + + public void setLevel(System.Logger.Level level) { + this.level = level; + } + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/PrincipalController.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/PrincipalController.java new file mode 100644 index 000000000..c8ff2443d --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/PrincipalController.java @@ -0,0 +1,91 @@ +package gr.cite.notification.web.controllers; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.web.model.Account; +import gr.cite.notification.web.model.AccountBuilder; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping(path = "api/principal", produces = MediaType.APPLICATION_JSON_VALUE) +public class PrincipalController { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class)); + private final AuditService auditService; + + private final CurrentPrincipalResolver currentPrincipalResolver; + private final AccountBuilder accountBuilder; + private final ClaimExtractor claimExtractor; + + @Autowired + public PrincipalController( + CurrentPrincipalResolver currentPrincipalResolver, + AccountBuilder accountBuilder, + AuditService auditService, + ClaimExtractor claimExtractor) { + this.currentPrincipalResolver = currentPrincipalResolver; + this.accountBuilder = accountBuilder; + this.auditService = auditService; + this.claimExtractor = claimExtractor; + } + + @GetMapping("me") + public Account me(FieldSet fieldSet) { + logger.debug("me"); + + + if (fieldSet == null || fieldSet.isEmpty()) { + fieldSet = new BaseFieldSet( + Account._isAuthenticated, + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._subject), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._userId), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._name), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._scope), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._client), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._issuedAt), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._notBefore), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more), + Account._permissions); + } + + MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); + + Account me = this.accountBuilder.build(fieldSet, principal); + + this.auditService.track(AuditableAction.Principal_Lookup); + //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return me; + + } + + @GetMapping("my-tenants") + public List myTenants() { + logger.debug("my-tenants"); + + MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); + List tenants = this.claimExtractor.asStrings(principal, TenantScope.TenantCodesClaimName); + + this.auditService.track(AuditableAction.Tenants_Lookup); + //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return tenants == null ? null : tenants.stream().distinct().collect(Collectors.toList()); + } + +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java new file mode 100644 index 000000000..5680757be --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java @@ -0,0 +1,129 @@ +package gr.cite.notification.web.controllers; + +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.notification.model.builder.TenantConfigurationBuilder; +import gr.cite.notification.model.censorship.TenantConfigurationCensor; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationEmailClientPersist; +import gr.cite.notification.query.TenantConfigurationQuery; +import gr.cite.notification.query.lookup.TenantConfigurationLookup; +import gr.cite.notification.service.tenantconfiguration.TenantConfigurationService; +import gr.cite.notification.web.model.QueryResult; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.censor.CensorFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import gr.cite.tools.validation.ValidationFilterAnnotation; +import jakarta.transaction.Transactional; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.web.bind.annotation.*; + +import javax.management.InvalidApplicationException; +import java.util.*; + +@RestController +@RequestMapping(path = "api/notification/tenant-configuration") +public class TenantConfigurationController { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationController.class)); + + private final BuilderFactory builderFactory; + private final AuditService auditService; + private final TenantConfigurationService tenantConfigurationService; + private final CensorFactory censorFactory; + private final QueryFactory queryFactory; + private final MessageSource messageSource; + + @Autowired + public TenantConfigurationController(BuilderFactory builderFactory, + AuditService auditService, + TenantConfigurationService tenantConfigurationService, CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource) { + this.builderFactory = builderFactory; + this.auditService = auditService; + this.tenantConfigurationService = tenantConfigurationService; + this.censorFactory = censorFactory; + this.queryFactory = queryFactory; + this.messageSource = messageSource; + } + + @PostMapping("query") + public QueryResult query(@RequestBody TenantConfigurationLookup lookup) throws MyApplicationException, MyForbiddenException { + logger.debug("querying {}", TenantConfiguration.class.getSimpleName()); + + this.censorFactory.censor(TenantConfigurationCensor.class).censor(lookup.getProject()); + + TenantConfigurationQuery query = lookup.enrich(this.queryFactory); + List data = query.collectAs(lookup.getProject()); + List models = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data); + long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); + + this.auditService.track(AuditableAction.Tenant_Configuration_Query, "lookup", lookup); + + return new QueryResult<>(models, count); + } + + @GetMapping("{id}") + @Transactional + public TenantConfiguration get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("id", id).And("fields", fieldSet)); + + this.censorFactory.censor(TenantConfigurationCensor.class).censor(fieldSet); + + TenantConfigurationQuery query = this.queryFactory.query(TenantConfigurationQuery.class).ids(id); + TenantConfiguration model = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet)); + if (model == null) + throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, TenantConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.Tenant_Configuration_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("id", id), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return model; + } + + @PostMapping("persist/email-client") + @Transactional + @ValidationFilterAnnotation(validator = TenantConfigurationEmailClientPersist.TenantConfigurationEmailClientPersistValidator.ValidatorName, argumentName = "model") + public TenantConfiguration persist(@RequestBody TenantConfigurationEmailClientPersist model, FieldSet fieldSet) + { + logger.debug(new MapLogEntry("persisting").And("type", TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION).And("model", model).And("fields", fieldSet)); + + TenantConfiguration persisted = this.tenantConfigurationService.persist(model, fieldSet); + + this.auditService.track(AuditableAction.Tenant_Configuration_Persist, Map.of( + "type", TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION, + "model", model, + "fields", fieldSet + )); + //this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action); + + return persisted; + } + + @DeleteMapping("{id}") + @Transactional + public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug(new MapLogEntry("deleting" + TenantConfiguration.class.getSimpleName()).And("id", id)); + + this.tenantConfigurationService.deleteAndSave(id); + + this.auditService.track(AuditableAction.Tenant_Configuration_Delete, "id", id); + + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/error/GenericErrorHandler.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/error/GenericErrorHandler.java new file mode 100644 index 000000000..1a7ebfc3a --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/controllers/error/GenericErrorHandler.java @@ -0,0 +1,17 @@ +//package gr.cite.intelcomp.stiviewer.web.controllers.error; +// +//import gr.cite.tools.exception.MyValidationException; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.ControllerAdvice; +//import org.springframework.web.bind.annotation.ExceptionHandler; +//import org.springframework.web.context.request.WebRequest; +// +//@ControllerAdvice +//public class GenericErrorHandler { +// +// @ExceptionHandler(MyValidationException.class) +// public ResponseEntity handleValidationException(MyValidationException e, WebRequest webRequest) { +// return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(e); +// } +//} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java new file mode 100644 index 000000000..7d3d4c03e --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java @@ -0,0 +1,101 @@ +package gr.cite.notification.web.interceptors; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.lock.LockByKeyManager; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.model.UserCredential; +import gr.cite.notification.query.UserCredentialQuery; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +import java.util.UUID; + +@Component +public class UserInterceptor implements WebRequestInterceptor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class)); + private final UserScope userScope; + private final ClaimExtractor claimExtractor; + private final CurrentPrincipalResolver currentPrincipalResolver; + private final PlatformTransactionManager transactionManager; + private final UserInterceptorCacheService userInterceptorCacheService; + private final JsonHandlingService jsonHandlingService; + private final QueryFactory queryFactory; + private final LockByKeyManager lockByKeyManager; + @PersistenceContext + public EntityManager entityManager; + + @Autowired + public UserInterceptor( + UserScope userScope, + ClaimExtractor claimExtractor, + CurrentPrincipalResolver currentPrincipalResolver, + PlatformTransactionManager transactionManager, + UserInterceptorCacheService userInterceptorCacheService, + JsonHandlingService jsonHandlingService, + QueryFactory queryFactory, + LockByKeyManager lockByKeyManager) { + this.userScope = userScope; + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractor = claimExtractor; + this.transactionManager = transactionManager; + this.userInterceptorCacheService = userInterceptorCacheService; + this.jsonHandlingService = jsonHandlingService; + this.queryFactory = queryFactory; + this.lockByKeyManager = lockByKeyManager; + } + + @Override + public void preHandle(WebRequest request) { + UUID userId = null; + if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) { + String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal()); + if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed"); + + UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId)); + if (cacheValue != null) { + userId = cacheValue.getUserId(); + } else { + userId = this.findExistingUserFromDbForce(subjectId); + + cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId); + + this.userInterceptorCacheService.put(cacheValue); + } + } + this.userScope.setUserId(userId); + } + + private UUID findExistingUserFromDbForce(String subjectId){ + UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user)); + if (userCredential != null) { + return userCredential.getUserId(); + } else { + throw new MyForbiddenException("User not created try again."); + } + } + + @Override + public void postHandle(@NonNull WebRequest request, ModelMap model) { + this.userScope.setUserId(null); + } + + @Override + public void afterCompletion(@NonNull WebRequest request, Exception ex) { + } +} + diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java new file mode 100644 index 000000000..adc161299 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.web.interceptors; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.user-by-subject-id") +public class UserInterceptorCacheOptions extends CacheOptions { +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java new file mode 100644 index 000000000..543003902 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java @@ -0,0 +1,66 @@ +package gr.cite.notification.web.interceptors; + +import gr.cite.tools.cache.CacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.UUID; + +@Service +public class UserInterceptorCacheService extends CacheService { + + public static class UserInterceptorCacheValue { + + public UserInterceptorCacheValue() { + } + + public UserInterceptorCacheValue(String subjectId, UUID userId) { + this.subjectId = subjectId; + this.userId = userId; + } + + private String subjectId; + + public String getSubjectId() { + return subjectId; + } + + public void setSubjectId(String subjectId) { + this.subjectId = subjectId; + } + + private UUID userId; + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + } + + + @Autowired + public UserInterceptorCacheService(UserInterceptorCacheOptions options) { + super(options); + } + + @Override + protected Class valueClass() { + return UserInterceptorCacheValue.class; + } + + @Override + public String keyOf(UserInterceptorCacheValue value) { + return this.buildKey(value.getSubjectId()); + } + + + public String buildKey(String subject) { + HashMap keyParts = new HashMap<>(); + keyParts.put("$subject$", subject); + return this.generateKey(keyParts); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/Account.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/Account.java new file mode 100644 index 000000000..601dc19b0 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/Account.java @@ -0,0 +1,160 @@ +package gr.cite.notification.web.model; + +import gr.cite.tools.logging.annotation.LogSensitive; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class Account { + + public static class PrincipalInfo { + + public static final String _userId = "userId"; + public UUID userId; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public static final String _subject = "subject"; + public UUID subject; + + public UUID getSubject() { + return subject; + } + + public void setSubject(UUID subject) { + this.subject = subject; + } + + public static final String _name = "name"; + @LogSensitive + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static final String _scope = "scope"; + public List scope; + + public List getScope() { + return scope; + } + + public void setScope(List scope) { + this.scope = scope; + } + + public static final String _client = "client"; + public String client; + + public String getClient() { + return client; + } + + public void setClient(String client) { + this.client = client; + } + + public static final String _notBefore = "notBefore"; + public Instant notBefore; + + public Instant getNotBefore() { + return notBefore; + } + + public void setNotBefore(Instant notBefore) { + this.notBefore = notBefore; + } + + public static final String _issuedAt = "issuedAt"; + public Instant issuedAt; + + public Instant getIssuedAt() { + return issuedAt; + } + + public void setIssuedAt(Instant issuedAt) { + this.issuedAt = issuedAt; + } + + public static final String _authenticatedAt = "authenticatedAt"; + public Instant authenticatedAt; + + public Instant getAuthenticatedAt() { + return authenticatedAt; + } + + public void setAuthenticatedAt(Instant authenticatedAt) { + this.authenticatedAt = authenticatedAt; + } + + public static final String _expiresAt = "expiresAt"; + public Instant expiresAt; + + public Instant getExpiresAt() { + return expiresAt; + } + + public void setExpiresAt(Instant expiresAt) { + this.expiresAt = expiresAt; + } + + public static final String _more = "more"; + @LogSensitive + public Map> more; + + public Map> getMore() { + return more; + } + + public void setMore(Map> more) { + this.more = more; + } + } + + + public static final String _isAuthenticated = "isAuthenticated"; + private Boolean isAuthenticated; + + public Boolean getIsAuthenticated() { + return isAuthenticated; + } + + public void setIsAuthenticated(Boolean authenticated) { + isAuthenticated = authenticated; + } + + public static final String _principal = "principal"; + private PrincipalInfo principal; + + public PrincipalInfo getPrincipal() { + return principal; + } + + public void setPrincipal(PrincipalInfo principal) { + this.principal = principal; + } + + public static final String _permissions = "permissions"; + private List permissions; + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List permissions) { + this.permissions = permissions; + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/AccountBuilder.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/AccountBuilder.java new file mode 100644 index 000000000..17deb15fb --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/AccountBuilder.java @@ -0,0 +1,83 @@ +package gr.cite.notification.web.model; + +import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration; +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorKeys; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class AccountBuilder { + + private final ClaimExtractor claimExtractor; + private final Set excludeMoreClaim; + private final AuthorizationConfiguration authorizationConfiguration; + private final CurrentPrincipalResolver currentPrincipalResolver; + private final UserScope userScope; + + public AccountBuilder(ClaimExtractor claimExtractor, AuthorizationConfiguration authorizationConfiguration, CurrentPrincipalResolver currentPrincipalResolver, UserScope userScope) { + this.claimExtractor = claimExtractor; + this.authorizationConfiguration = authorizationConfiguration; + this.currentPrincipalResolver = currentPrincipalResolver; + this.userScope = userScope; + this.excludeMoreClaim = Set.of( + ClaimExtractorKeys.Subject, + ClaimExtractorKeys.Name, + ClaimExtractorKeys.Scope, + ClaimExtractorKeys.Client, + ClaimExtractorKeys.IssuedAt, + ClaimExtractorKeys.NotBefore, + ClaimExtractorKeys.AuthenticatedAt, + ClaimExtractorKeys.ExpiresAt); + } + + public Account build(FieldSet fields, MyPrincipal principal) { + Account model = new Account(); + if (principal == null || !principal.isAuthenticated()) { + model.setIsAuthenticated(false); + return model; + } + model.setIsAuthenticated(true); + + FieldSet principalFields = fields.extractPrefixed(BaseFieldSet.asIndexerPrefix(Account._principal)); + if (!principalFields.isEmpty()) model.setPrincipal(new Account.PrincipalInfo()); + if (principalFields.hasField(Account.PrincipalInfo._subject)) model.getPrincipal().setSubject(this.claimExtractor.subjectUUID(principal)); + if (principalFields.hasField(Account.PrincipalInfo._userId)) model.getPrincipal().setUserId(this.userScope.getUserIdSafe()); + if (principalFields.hasField(Account.PrincipalInfo._name)) model.getPrincipal().setName(this.claimExtractor.name(principal)); + if (principalFields.hasField(Account.PrincipalInfo._scope)) model.getPrincipal().setScope(this.claimExtractor.scope(principal)); + if (principalFields.hasField(Account.PrincipalInfo._client)) model.getPrincipal().setClient(this.claimExtractor.client(principal)); + if (principalFields.hasField(Account.PrincipalInfo._issuedAt)) model.getPrincipal().setIssuedAt(this.claimExtractor.issuedAt(principal)); + if (principalFields.hasField(Account.PrincipalInfo._notBefore)) model.getPrincipal().setNotBefore(this.claimExtractor.notBefore(principal)); + if (principalFields.hasField(Account.PrincipalInfo._authenticatedAt)) model.getPrincipal().setAuthenticatedAt(this.claimExtractor.authenticatedAt(principal)); + if (principalFields.hasField(Account.PrincipalInfo._expiresAt)) model.getPrincipal().setExpiresAt(this.claimExtractor.expiresAt(principal)); + if (principalFields.hasField(Account.PrincipalInfo._more)) { + model.getPrincipal().setMore(new HashMap<>()); + for (String key : this.claimExtractor.knownPublicKeys()) { + if (this.excludeMoreClaim.contains(key)) continue; + List values = this.claimExtractor.asStrings(principal, key); + if (values == null || values.size() == 0) continue; + if (!model.getPrincipal().getMore().containsKey(key)) model.getPrincipal().getMore().put(key, new ArrayList<>()); + model.getPrincipal().getMore().get(key).addAll(values); + } + } + + if (fields.hasField(Account._permissions)) { + List roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + Set permissions = authorizationConfiguration.permissionsOfRoles(roles); + model.setPermissions(new ArrayList<>(permissions)); + } + return model; + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/QueryResult.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/QueryResult.java new file mode 100644 index 000000000..471a27f4e --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/model/QueryResult.java @@ -0,0 +1,37 @@ +package gr.cite.notification.web.model; + +import java.util.ArrayList; +import java.util.List; + +public class QueryResult { + public QueryResult() { } + public QueryResult(List items, long count) + { + this.items = items; + this.count = count; + } + + public List items; + public long count; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + public static QueryResult Empty() + { + return new QueryResult<>(new ArrayList<>(), 0L); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheOptions.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheOptions.java new file mode 100644 index 000000000..752c8965f --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.tenant-by-code") +public class TenantByCodeCacheOptions extends CacheOptions { +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheService.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheService.java new file mode 100644 index 000000000..6c2d2107a --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheService.java @@ -0,0 +1,76 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.event.TenantTouchedEvent; +import gr.cite.tools.cache.CacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.UUID; + +@Service +public class TenantByCodeCacheService extends CacheService { + + public static class TenantByCodeCacheValue { + + public TenantByCodeCacheValue() { + } + + public TenantByCodeCacheValue(String tenantCode, UUID tenantId) { + this.tenantCode = tenantCode; + this.tenantId = tenantId; + } + + private String tenantCode; + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + private UUID tenantId; + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + } + + private final ConventionService conventionService; + + @Autowired + public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) { + super(options); + this.conventionService = conventionService; + } + + @EventListener + public void handleTenantTouchedEvent(TenantTouchedEvent event) { + if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantCode())); + if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode())) this.evict(this.buildKey(event.getPreviousTenantCode())); + } + + @Override + protected Class valueClass() { + return TenantByCodeCacheValue.class; + } + + @Override + public String keyOf(TenantByCodeCacheValue value) { + return this.buildKey(value.getTenantCode()); + } + + public String buildKey(String code) { + return this.generateKey(new HashMap<>() {{ + put("$code$", code); + }}); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheOptions.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheOptions.java new file mode 100644 index 000000000..1c5f69520 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.tenant-by-id") +public class TenantByIdCacheOptions extends CacheOptions { +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheService.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheService.java new file mode 100644 index 000000000..89ab6c35d --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheService.java @@ -0,0 +1,76 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.event.TenantTouchedEvent; +import gr.cite.tools.cache.CacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Locale; +import java.util.UUID; + +@Service +public class TenantByIdCacheService extends CacheService { + + public static class TenantByIdCacheValue { + + public TenantByIdCacheValue() { + } + + public TenantByIdCacheValue(String tenantCode, UUID tenantId) { + this.tenantCode = tenantCode; + this.tenantId = tenantId; + } + + private String tenantCode; + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + private UUID tenantId; + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + } + + private final ConventionService conventionService; + + @Autowired + public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) { + super(options); + this.conventionService = conventionService; + } + + @EventListener + public void handleTenantTouchedEvent(TenantTouchedEvent event) { + if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantId())); + } + + @Override + protected Class valueClass() { + return TenantByIdCacheValue.class; + } + + @Override + public String keyOf(TenantByIdCacheValue value) { + return this.buildKey(value.getTenantId()); + } + + public String buildKey(UUID id) { + return this.generateKey(new HashMap<>() {{ + put("$tenantId$", id.toString().toLowerCase(Locale.ROOT)); + }}); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java new file mode 100644 index 000000000..950039cc6 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java @@ -0,0 +1,166 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.TenantUserEntity; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; +import org.hibernate.Session; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.Locale; + +@Component +public class TenantInterceptor implements WebRequestInterceptor { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantInterceptor.class)); + private final TenantScope tenantScope; + private final UserScope userScope; + private final CurrentPrincipalResolver currentPrincipalResolver; + private final ClaimExtractor claimExtractor; + private final ApplicationContext applicationContext; + private final ErrorThesaurusProperties errorThesaurusProperties; + private final TenantScopeProperties tenantScopeProperties; + private final UserAllowedTenantCacheService userAllowedTenantCacheService; + private final ErrorThesaurusProperties errors; + + @PersistenceContext + public EntityManager entityManager; + + @Autowired + public TenantInterceptor( + TenantScope tenantScope, + UserScope userScope, + CurrentPrincipalResolver currentPrincipalResolver, + ClaimExtractor claimExtractor, + ApplicationContext applicationContext, + ErrorThesaurusProperties errorThesaurusProperties, + TenantScopeProperties tenantScopeProperties, + UserAllowedTenantCacheService userAllowedTenantCacheService, + ErrorThesaurusProperties errors) { + this.tenantScope = tenantScope; + this.userScope = userScope; + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractor = claimExtractor; + this.applicationContext = applicationContext; + this.errorThesaurusProperties = errorThesaurusProperties; + this.tenantScopeProperties = tenantScopeProperties; + this.userAllowedTenantCacheService = userAllowedTenantCacheService; + this.errors = errors; + } + + @Override + public void preHandle(WebRequest request) throws InvalidApplicationException { + if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; + if (!this.tenantScope.isMultitenant()) return; + + boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant); + if (tenantScope.isSet() && this.entityManager != null) { + List currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), TenantScope.TenantCodesClaimName); + if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) { + logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); + //throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); + } + + boolean isUserAllowedTenant = false; + UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant())); + if (cacheValue != null) { + isUserAllowedTenant = cacheValue.isAllowed(); + } else { + isUserAllowedTenant = this.isUserAllowedTenant(); + this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant)); + } + + if (isUserAllowedTenant) { + this.entityManager + .unwrap(Session.class) + .enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString()); + } else { + if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) { + tenantScope.setTenant(null, null); + } else { + logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); + //throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); + } + } + } else { + if (!isAllowedNoTenant) { + if (!this.isWhiteListedEndpoint(request)) { + logger.warn("tenant scope not provided"); + throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage()); + } + } + } + } + + private boolean isWhiteListedEndpoint(WebRequest request) { + String servletPath = ((ServletWebRequest) request).getRequest().getServletPath(); + if (this.tenantScopeProperties.getWhiteListedEndpoints() != null) { + for (String whiteListedEndpoint : this.tenantScopeProperties.getWhiteListedEndpoints()) { + if (servletPath.toLowerCase(Locale.ROOT).startsWith(whiteListedEndpoint.toLowerCase(Locale.ROOT))) { + return true; + } + } + } + return false; + } + + private boolean isUserAllowedTenant() throws InvalidApplicationException { + if (userScope.isSet()) { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + Root root = query.from(UserEntity.class); + Subquery subQuery = query.subquery(TenantUserEntity.class); + Root subQueryRoot = subQuery.from(TenantUserEntity.class); + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active), + criteriaBuilder.in(root.get(UserEntity._id)).value(subQuery.where( + criteriaBuilder.and( + criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._tenantId), this.tenantScope.getTenant()), + criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._userId), this.userScope.getUserId()), + criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._isActive), IsActive.Active) + )).select(subQueryRoot.get(TenantUserEntity._userId)).distinct(true) + ) + )); + query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); + List results = this.entityManager.createQuery(query).getResultList(); + if (results.size() > 0) return true; + } + + return false; + } + + @Override + public void postHandle(@NonNull WebRequest request, ModelMap model) { + this.tenantScope.setTenant(null, null); + } + + @Override + public void afterCompletion(@NonNull WebRequest request, Exception ex) { + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java new file mode 100644 index 000000000..293190b4f --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java @@ -0,0 +1,196 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.jetbrains.annotations.NotNull; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.UUID; + +@Component +public class TenantScopeClaimInterceptor implements WebRequestInterceptor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class)); + private final TenantScope tenantScope; + private final ConventionService conventionService; + private final TenantScopeProperties tenantScopeProperties; + private final ErrorThesaurusProperties errorThesaurusProperties; + private final ClaimExtractor claimExtractor; + private final CurrentPrincipalResolver currentPrincipalResolver; + private final String clientTenantClaimName; + private final ClaimExtractorContext claimExtractorContext; + private final TenantByCodeCacheService tenantByCodeCacheService; + private final TenantByIdCacheService tenantByIdCacheService; + @PersistenceContext + public EntityManager entityManager; + + @Autowired + public TenantScopeClaimInterceptor( + TenantScope tenantScope, + ConventionService conventionService, + ClaimExtractor claimExtractor, + CurrentPrincipalResolver currentPrincipalResolver, + ErrorThesaurusProperties errorThesaurusProperties, + TenantScopeProperties tenantScopeProperties, + ClaimExtractorContext claimExtractorContext, + TenantByCodeCacheService tenantByCodeCacheService, + TenantByIdCacheService tenantByIdCacheService + ) { + this.tenantScope = tenantScope; + this.conventionService = conventionService; + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractor = claimExtractor; + this.errorThesaurusProperties = errorThesaurusProperties; + this.tenantScopeProperties = tenantScopeProperties; + this.claimExtractorContext = claimExtractorContext; + this.tenantByCodeCacheService = tenantByCodeCacheService; + this.tenantByIdCacheService = tenantByIdCacheService; + this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName; + } + + @Override + public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException { + if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; + if (!this.tenantScope.isMultitenant()) return; + + MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); + if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) { + Boolean scoped = this.scopeByPrincipal(this.tenantScope, principal); + if (!scoped) scoped = this.scopeByClient(this.tenantScope, principal); + if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant()) throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage()); + } + } + + private Boolean scopeByPrincipal(TenantScope scope, MyPrincipal principal) { + String tenantCode = this.claimExtractor.tenantString(principal); + if (tenantCode == null || tenantCode.isBlank()) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName); + + UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode); + if (tenantId == null && tenantCode == null) return false; + if (tenantId == null) { + TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode)); + if (cacheValue != null) { + tenantId = cacheValue.getTenantId(); + } else { + tenantId = this.getTenantIdFromDatabase(tenantCode); + this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId)); + this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId)); + } + } else { + logger.debug("tenant claim was set to {}", tenantId); + TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId)); + + if (cacheValue != null) { + tenantCode = cacheValue.getTenantCode(); + } else { + tenantCode = this.getTenantCodeFromDatabase(tenantId); + this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId)); + this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId)); + } + } + + if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) { + logger.debug("parsed tenant header and set tenant id to {}", tenantId); + this.tenantScope.setTenant(tenantId, tenantCode); + this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode); + } + return tenantId != null; + } + + private Boolean scopeByClient(TenantScope scope, MyPrincipal principal) throws InvalidApplicationException { + String client = this.claimExtractor.client(principal); + + Boolean isWhiteListed = this.tenantScopeProperties.getWhiteListedClients() != null && !this.conventionService.isNullOrEmpty(client) && this.tenantScopeProperties.getWhiteListedClients().contains(client); + logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, scope.isSet(), (scope.isSet() ? scope.getTenant() : null)); + + return isWhiteListed && scope.isSet(); + } + + private UUID getTenantIdFromDatabase(String tenantCode) { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + Root root = query.from(TenantEntity.class); + query = query.where( + criteriaBuilder.and( + criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode), + criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) + ) + ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); + List results = this.entityManager.createQuery(query).getResultList(); + if (results.size() == 1) { + Object o; + try { + o = results.get(0).get(TenantEntity._id); + } catch (IllegalArgumentException e) { + return null; + } + if (o == null) return null; + try { + return (UUID) o; + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + private String getTenantCodeFromDatabase(UUID tenantId) { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + Root root = query.from(TenantEntity.class); + query = query.where( + criteriaBuilder.and( + criteriaBuilder.equal(root.get(TenantEntity._id), tenantId), + criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) + ) + ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); + List results = this.entityManager.createQuery(query).getResultList(); + if (results.size() == 1) { + Object o; + try { + o = results.get(0).get(TenantEntity._code); + } catch (IllegalArgumentException e) { + return null; + } + if (o == null) return null; + try { + return (String) o; + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + @Override + public void postHandle(@NonNull WebRequest request, ModelMap model) { + this.tenantScope.setTenant(null, null); + this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter); + } + + @Override + public void afterCompletion(@NonNull WebRequest request, Exception ex) { + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeConfiguration.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeConfiguration.java new file mode 100644 index 000000000..13d77c9ad --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeConfiguration.java @@ -0,0 +1,9 @@ +package gr.cite.notification.web.scope.tenant; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(TenantScopeProperties.class) +public class TenantScopeConfiguration { +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java new file mode 100644 index 000000000..5fdf3455c --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java @@ -0,0 +1,161 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantEntity; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.UUID; + +@Component +public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeHeaderInterceptor.class)); + private final TenantScope tenantScope; + private final ConventionService conventionService; + private final TenantByCodeCacheService tenantByCodeCacheService; + private final TenantByIdCacheService tenantByIdCacheService; + private final ClaimExtractorContext claimExtractorContext; + private final CurrentPrincipalResolver currentPrincipalResolver; + @PersistenceContext + public EntityManager entityManager; + + @Autowired + public TenantScopeHeaderInterceptor( + TenantScope tenantScope, + ConventionService conventionService, + TenantByCodeCacheService tenantByCodeCacheService, + TenantByIdCacheService tenantByIdCacheService, + ClaimExtractorContext claimExtractorContext, + CurrentPrincipalResolver currentPrincipalResolver + ) { + this.tenantScope = tenantScope; + this.conventionService = conventionService; + this.tenantByCodeCacheService = tenantByCodeCacheService; + this.tenantByIdCacheService = tenantByIdCacheService; + this.claimExtractorContext = claimExtractorContext; + this.currentPrincipalResolver = currentPrincipalResolver; + } + + @Override + public void preHandle(WebRequest request) throws InvalidApplicationException { + if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; + if (!this.tenantScope.isMultitenant()) return; + + String tenantCode = request.getHeader(TenantScope.TenantClaimName); + logger.debug("retrieved request tenant header is: {header}", tenantCode); + if (this.conventionService.isNullOrEmpty(tenantCode)) return; + + UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode); + if (tenantId == null && tenantCode == null) return; + if (tenantId == null) { + TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode)); + if (cacheValue != null) { + tenantId = cacheValue.getTenantId(); + } else { + tenantId = this.getTenantIdFromDatabase(tenantCode); + this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId)); + this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId)); + } + } else { + TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId)); + if (cacheValue != null) { + tenantCode = cacheValue.getTenantCode(); + } else { + tenantCode = this.getTenantCodeFromDatabase(tenantId); + this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId)); + this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId)); + } + } + + if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) { + logger.debug("parsed tenant header and set tenant id to {tenant}", tenantId); + this.tenantScope.setTenant(tenantId, tenantCode); + this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode); + } + } + + private UUID getTenantIdFromDatabase(String tenantCode) { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + Root root = query.from(TenantEntity.class); + query = query.where( + criteriaBuilder.and( + criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode), + criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) + ) + ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); + List results = this.entityManager.createQuery(query).getResultList(); + if (results.size() == 1) { + Object o; + try { + o = results.get(0).get(TenantEntity._id); + } catch (IllegalArgumentException e) { + return null; + } + if (o == null) return null; + try { + return UUID.class.cast(o); + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + private String getTenantCodeFromDatabase(UUID tenantId) { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + Root root = query.from(TenantEntity.class); + query = query.where( + criteriaBuilder.and( + criteriaBuilder.equal(root.get(TenantEntity._id), tenantId), + criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) + ) + ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); + List results = this.entityManager.createQuery(query).getResultList(); + if (results.size() == 1) { + Object o; + try { + o = results.get(0).get(TenantEntity._code); + } catch (IllegalArgumentException e) { + return null; + } + if (o == null) return null; + try { + return String.class.cast(o); + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + @Override + public void postHandle(@NonNull WebRequest request, ModelMap model) { + + this.tenantScope.setTenant(null, null); + this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter); + } + + @Override + public void afterCompletion(@NonNull WebRequest request, Exception ex) { + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeProperties.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeProperties.java new file mode 100644 index 000000000..6ed29645b --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeProperties.java @@ -0,0 +1,43 @@ +package gr.cite.notification.web.scope.tenant; + + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.HashSet; +import java.util.List; + +@ConfigurationProperties(prefix = "tenant.interceptor") +public class TenantScopeProperties { + + private String clientClaimsPrefix; + public String getClientClaimsPrefix() { + return clientClaimsPrefix; + } + public void setClientClaimsPrefix(String clientClaimsPrefix) { + this.clientClaimsPrefix = clientClaimsPrefix; + } + + private HashSet whiteListedClients; + public HashSet getWhiteListedClients() { + return whiteListedClients; + } + public void setWhiteListedClients(HashSet whiteListedClients) { + this.whiteListedClients = whiteListedClients; + } + + private List whiteListedEndpoints; + public List getWhiteListedEndpoints() { + return whiteListedEndpoints; + } + public void setWhiteListedEndpoints(List whiteListedEndpoints) { + this.whiteListedEndpoints = whiteListedEndpoints; + } + + private Boolean enforceTrustedTenant; + public Boolean getEnforceTrustedTenant() { + return enforceTrustedTenant; + } + public void setEnforceTrustedTenant(Boolean enforceTrustedTenant) { + this.enforceTrustedTenant = enforceTrustedTenant; + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheOptions.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheOptions.java new file mode 100644 index 000000000..30606754c --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.user-allowed-tenant") +public class UserAllowedTenantCacheOptions extends CacheOptions { +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheService.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheService.java new file mode 100644 index 000000000..0cebe00ea --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheService.java @@ -0,0 +1,90 @@ +package gr.cite.notification.web.scope.tenant; + +import gr.cite.notification.event.UserAddedToTenantEvent; +import gr.cite.notification.event.UserRemovedFromTenantEvent; +import gr.cite.tools.cache.CacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Locale; +import java.util.UUID; + +@Service +public class UserAllowedTenantCacheService extends CacheService { + + public static class UserAllowedTenantCacheValue { + + public UserAllowedTenantCacheValue() { + } + + public UserAllowedTenantCacheValue(UUID userId, UUID tenantId, boolean isAllowed) { + this.userId = userId; + this.tenantId = tenantId; + this.isAllowed = isAllowed; + } + + private UUID userId; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + private UUID tenantId; + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + private boolean isAllowed; + + public boolean isAllowed() { + return isAllowed; + } + + public void setAllowed(boolean allowed) { + isAllowed = allowed; + } + } + + @Autowired + public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) { + super(options); + } + + @EventListener + public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) { + this.evict(this.buildKey(event.getUserId(), event.getTenantId())); + } + + @EventListener + public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) { + this.evict(this.buildKey(event.getUserId(), event.getTenantId())); + } + + @Override + protected Class valueClass() { + return UserAllowedTenantCacheValue.class; + } + + @Override + public String keyOf(UserAllowedTenantCacheValue value) { + return this.buildKey(value.getUserId(), value.getTenantId()); + } + + public String buildKey(UUID userId, UUID tenantId) { + return this.generateKey(new HashMap<>() {{ + put("$user_id$", userId.toString().toLowerCase(Locale.ROOT)); + put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT)); + }}); + } +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java new file mode 100644 index 000000000..63ac88829 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java @@ -0,0 +1,154 @@ +//package gr.cite.notification.web.scope.user; +// +// +//import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +//import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +//import gr.cite.notification.common.enums.IsActive; +//import gr.cite.notification.common.scope.user.UserScope; +//import gr.cite.notification.data.UserEntity; +//import gr.cite.notification.locale.LocaleService; +//import gr.cite.tools.logging.LoggerService; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.lang.NonNull; +//import org.springframework.stereotype.Component; +//import org.springframework.transaction.PlatformTransactionManager; +//import org.springframework.transaction.TransactionDefinition; +//import org.springframework.transaction.TransactionStatus; +//import org.springframework.transaction.support.DefaultTransactionDefinition; +//import org.springframework.ui.ModelMap; +//import org.springframework.web.context.request.WebRequest; +//import org.springframework.web.context.request.WebRequestInterceptor; +// +//import javax.management.InvalidApplicationException; +//import jakarta.persistence.EntityManager; +//import jakarta.persistence.PersistenceContext; +//import jakarta.persistence.Tuple; +//import jakarta.persistence.criteria.CriteriaBuilder; +//import jakarta.persistence.criteria.CriteriaQuery; +//import jakarta.persistence.criteria.Root; +//import java.time.Instant; +//import java.util.List; +//import java.util.UUID; +// +//@Component +//public class UserInterceptor implements WebRequestInterceptor { +// private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class)); +// private final UserScope userScope; +// private final ClaimExtractor claimExtractor; +// private final CurrentPrincipalResolver currentPrincipalResolver; +// private final LocaleService localeService; +// private final PlatformTransactionManager transactionManager; +// private final UserInterceptorCacheService userInterceptorCacheService; +// @PersistenceContext +// public EntityManager entityManager; +// +// @Autowired +// public UserInterceptor( +// UserScope userScope, +// LocaleService localeService, +// ClaimExtractor claimExtractor, +// CurrentPrincipalResolver currentPrincipalResolver, +// PlatformTransactionManager transactionManager, +// UserInterceptorCacheService userInterceptorCacheService +// ) { +// this.userScope = userScope; +// this.localeService = localeService; +// this.currentPrincipalResolver = currentPrincipalResolver; +// this.claimExtractor = claimExtractor; +// this.transactionManager = transactionManager; +// this.userInterceptorCacheService = userInterceptorCacheService; +// } +// +// @Override +// public void preHandle(WebRequest request) throws InvalidApplicationException { +// UUID userId = null; +// if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) { +// String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal()); +// +// UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId)); +// if (cacheValue != null) { +// userId = cacheValue.getUserId(); +// } else { +// userId = this.getUserIdFromDatabase(subjectId); +// if (userId == null) userId = this.createUser(subjectId); +// +// this.userInterceptorCacheService.put(new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId)); +// } +// } +// this.userScope.setUserId(userId); +// } +// +// private UUID getUserIdFromDatabase(String subjectId) throws InvalidApplicationException { +// CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); +// CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); +// Root root = query.from(UserEntity.class); +// query.where( +// criteriaBuilder.and( +//// criteriaBuilder.equal(root.get(UserEntity._subjectId), subjectId), +// criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active) +// )); +// +// query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); +// +// List results = this.entityManager.createQuery(query).getResultList(); +// if (results.size() == 1) { +// Object o; +// try { +// o = results.get(0).get(UserEntity._id); +// } catch (IllegalArgumentException e) { +// return null; +// } +// if (o == null) return null; +// try { +// return UUID.class.cast(o); +// } catch (ClassCastException e) { +// return null; +// } +// } +// return null; +// } +// +// private UUID createUser(String subjectId) { +// String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal()); +// String familyName = this.claimExtractor.familyName(this.currentPrincipalResolver.currentPrincipal()); +// if (name == null) name = subjectId; +// UserEntity user = new UserEntity(); +// user.setId(UUID.randomUUID()); +// user.setCreatedAt(Instant.now()); +// user.setUpdatedAt(Instant.now()); +// user.setName(name); +//// user.setLastName(familyName == null ? name : familyName); +// user.setIsActive(IsActive.Active); +//// user.setSubjectId(subjectId); +//// user.setCulture(this.localeService.cultureName()); +//// user.setTimezone(this.localeService.timezoneName()); +//// user.setLanguage(this.localeService.language()); +// +// DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); +// definition.setName(UUID.randomUUID().toString()); +// definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); +// definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); +// TransactionStatus status = null; +// try { +// status = transactionManager.getTransaction(definition); +// this.entityManager.persist(user); +// +// this.entityManager.flush(); +// transactionManager.commit(status); +// } catch (Exception ex) { +// if (status != null) transactionManager.rollback(status); +// throw ex; +// } +// return user.getId(); +// } +// +// @Override +// public void postHandle(@NonNull WebRequest request, ModelMap model) { +// this.userScope.setUserId(null); +// } +// +// @Override +// public void afterCompletion(@NonNull WebRequest request, Exception ex) { +// } +//} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java new file mode 100644 index 000000000..ceaf7996e --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java @@ -0,0 +1,10 @@ +//package gr.cite.notification.web.scope.user; +// +//import gr.cite.tools.cache.CacheOptions; +//import org.springframework.boot.context.properties.ConfigurationProperties; +//import org.springframework.context.annotation.Configuration; +// +//@Configuration +//@ConfigurationProperties(prefix = "cache.user-by-subject-id") +//public class UserInterceptorCacheOptions extends CacheOptions { +//} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java new file mode 100644 index 000000000..338a69f03 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java @@ -0,0 +1,77 @@ +//package gr.cite.notification.web.scope.user; +// +//import gr.cite.notification.convention.ConventionService; +//import gr.cite.notification.event.UserTouchedEvent; +//import gr.cite.tools.cache.CacheService; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.event.EventListener; +//import org.springframework.stereotype.Service; +// +//import java.util.HashMap; +//import java.util.UUID; +// +//@Service +//public class UserInterceptorCacheService extends CacheService { +// +// public static class UserInterceptorCacheValue { +// +// public UserInterceptorCacheValue() { +// } +// +// public UserInterceptorCacheValue(String subjectId, UUID userId) { +// this.subjectId = subjectId; +// this.userId = userId; +// } +// +// private String subjectId; +// +// public String getSubjectId() { +// return subjectId; +// } +// +// public void setSubjectId(String subjectId) { +// this.subjectId = subjectId; +// } +// +// private UUID userId; +// +// public UUID getUserId() { +// return userId; +// } +// +// public void setUserId(UUID userId) { +// this.userId = userId; +// } +// } +// +// private final ConventionService conventionService; +// +// @Autowired +// public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) { +// super(options); +// this.conventionService = conventionService; +// } +// +// @EventListener +// public void handleUserTouchedEvent(UserTouchedEvent event) { +// if (!this.conventionService.isNullOrEmpty(event.getSubjectId())) this.evict(this.buildKey(event.getSubjectId())); +// if (!this.conventionService.isNullOrEmpty(event.getPreviousSubjectId())) this.evict(this.buildKey(event.getPreviousSubjectId())); +// } +// +// @Override +// protected Class valueClass() { +// return UserInterceptorCacheValue.class; +// } +// +// @Override +// public String keyOf(UserInterceptorCacheValue value) { +// return this.buildKey(value.getSubjectId()); +// } +// +// +// public String buildKey(String subject) { +// return this.generateKey(new HashMap<>() {{ +// put("$subject$", subject); +// }}); +// } +//} diff --git a/annotation-service/annotation-web/src/main/resources/config/application.yml b/annotation-service/annotation-web/src/main/resources/config/application.yml new file mode 100644 index 000000000..fd750fd10 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/application.yml @@ -0,0 +1,20 @@ +spring: + jackson: + default-property-inclusion: non_null + config: + import: optional:classpath:config/app.env[.properties], optional:file:../config/app.env[.properties], + optional:classpath:config/db.yml[.yml], optional:classpath:config/db-${spring.profiles.active}.yml[.yml], optional:file:../config/db-${spring.profiles.active}.yml[.yml], + optional:classpath:config/permissions.yml[.yml], optional:classpath:config/permissions-${spring.profiles.active}.yml[.yml], optional:file:../config/permissions-${spring.profiles.active}.yml[.yml], + optional:classpath:config/errors.yml[.yml], optional:classpath:config/errors-${spring.profiles.active}.yml[.yml], optional:file:../config/errors-${spring.profiles.active}.yml[.yml], + optional:classpath:config/security.yml[.yml], optional:classpath:config/security-${spring.profiles.active}.yml[.yml], optional:file:../config/security-${spring.profiles.active}.yml[.yml], + optional:classpath:config/server.yml[.yml], optional:classpath:config/server-${spring.profiles.active}.yml[.yml], optional:file:../config/server-${spring.profiles.active}.yml[.yml], + optional:classpath:config/logging.yml[.yml], optional:classpath:config/logging-${spring.profiles.active}.yml[.yml], optional:file:../config/logging-${spring.profiles.active}.yml[.yml], + optional:classpath:config/idpclaims.yml[.yml], optional:classpath:config/idpclaims-${spring.profiles.active}.yml[.yml], optional:file:../config/idpclaims-${spring.profiles.active}.yml[.yml], + optional:classpath:config/cache.yml[.yml], optional:classpath:config/cache-${spring.profiles.active}.yml[.yml], optional:file:../config/cache-${spring.profiles.active}.yml[.yml], + optional:classpath:config/tenant.yml[.yml], optional:classpath:config/tenant-${spring.profiles.active}.yml[.yml], optional:file:../config/tenant-${spring.profiles.active}.yml[.yml], + optional:classpath:config/locale.yml[.yml], optional:classpath:config/locale-${spring.profiles.active}.yml[.yml], optional:file:../config/locale-${spring.profiles.active}.yml[.yml], + optional:classpath:config/cors.yml[.yml], optional:classpath:config/cors-${spring.profiles.active}.yml[.yml], optional:file:../config/cors-${spring.profiles.active}.yml[.yml], + optional:classpath:config/email.yml[.yml], optional:classpath:config/email-${spring.profiles.active}.yml[.yml], optional:file:../config/email-${spring.profiles.active}.yml[.yml], + optional:classpath:config/queue.yml[.yml], optional:classpath:config/queue-${spring.profiles.active}.yml[.yml], optional:file:../config/queue-${spring.profiles.active}.yml[.yml], + optional:classpath:config/cipher.yml[.yml], optional:classpath:config/cipher-${spring.profiles.active}.yml[.yml], optional:file:../config/cipher-${spring.profiles.active}.yml[.yml], + optional:classpath:config/formatting.yml[.yml], optional:classpath:config/formatting-${spring.profiles.active}.yml[.yml], optional:file:../config/formatting-${spring.profiles.active}.yml[.yml] diff --git a/annotation-service/annotation-web/src/main/resources/config/cache.yml b/annotation-service/annotation-web/src/main/resources/config/cache.yml new file mode 100644 index 000000000..5b30dee77 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/cache.yml @@ -0,0 +1,77 @@ +cache: + manager: + fallbackToNoOpCache: true + caffeineCaches: + - names: [ apikey ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 10 + expireAfterAccessMinutes: 10 + refreshAfterWriteMinutes: 10 + - names: [ tenantByCode ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 10 + expireAfterAccessMinutes: 10 + refreshAfterWriteMinutes: 10 + - names: [ tenantById ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 10 + expireAfterAccessMinutes: 10 + refreshAfterWriteMinutes: 10 + - names: [ userBySubjectId ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 5 + expireAfterAccessMinutes: 5 + refreshAfterWriteMinutes: 5 + - names: [ userAccessTenant ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 5 + expireAfterAccessMinutes: 5 + refreshAfterWriteMinutes: 5 + - names: [ formattingUserProfile ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 1 + expireAfterAccessMinutes: 1 + refreshAfterWriteMinutes: 1 + mapCaches: + - names: [ cacheB ] + allowNullValues: true + storeByValue: true + apiKey: + name: apikey + keyPattern: resolve_$keyhash$:v0 + userBySubjectId: + name: userBySubjectId + keyPattern: user_by_subject_$subject$:v0 + tenantByCode: + name: tenantByCode + keyPattern: tenant_by_code_$code$:v0 + tenantById: + name: tenantById + keyPattern: tenant_by_id_$tenantId$:v0 + userAllowedTenant: + name: userAccessTenant + keyPattern: user_access_tenant_$user_id$_$tenant_id$:v0 + formattingUserProfileCache: + name: formattingUserProfile + keyPattern: formatting_user_profile$user_id$:v0 + template: + name: template + key-pattern: ------ diff --git a/annotation-service/annotation-web/src/main/resources/config/cipher.yml b/annotation-service/annotation-web/src/main/resources/config/cipher.yml new file mode 100644 index 000000000..caa559296 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/cipher.yml @@ -0,0 +1,35 @@ +cipher-profiles: + profile-map: + configuration-profile-name: "configuration" + queue-profile-name: "queue" + notification-profile-name: "queue" + +cipher: +# salted-hash: +# default-o: null +# options: null + symetric-encryption: + default-o: null + options: + configuration: + aes: + key: ${CIPHER_SYMETRIC_ENCRYPTION_CONFIGURATION_AES_KEY:} + iv: ${CIPHER_SYMETRIC_ENCRYPTION_CONFIGURATION_AES_IV:} + queue: + aes: + key: ${CIPHER_SYMETRIC_ENCRYPTION_QUEUE_AES_KEY:} + iv: ${CIPHER_SYMETRIC_ENCRYPTION_QUEUE_AES_IV:} + masking: + default: null + options: + configuration: + character: "*" + clear-begining: 2 + clear-ending: 4 + at-least-percentage: 70 + digital-signature: + default: null + options: + configuration: + certificate-path: null + certificate-password: null \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/cors-devel.yml b/annotation-service/annotation-web/src/main/resources/config/cors-devel.yml new file mode 100644 index 000000000..3e9227ef3 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/cors-devel.yml @@ -0,0 +1,3 @@ +web: + cors: + allowed-origins: [ http://localhost, http://localhost:4200 ] diff --git a/annotation-service/annotation-web/src/main/resources/config/cors.yml b/annotation-service/annotation-web/src/main/resources/config/cors.yml new file mode 100644 index 000000000..3774f14d4 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/cors.yml @@ -0,0 +1,7 @@ +web: + cors: + enabled: true + allowed-methods: [ HEAD, GET, POST, PUT, DELETE, PATCH ] + allowed-headers: [ Authorization, Cache-Control, Content-Type, Content-Disposition, x-tenant ] + exposed-headers: [ Authorization, Cache-Control, Content-Type, Content-Disposition ] + allow-credentials: false diff --git a/annotation-service/annotation-web/src/main/resources/config/db-devel.yml b/annotation-service/annotation-web/src/main/resources/config/db-devel.yml new file mode 100644 index 000000000..22a089fbd --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/db-devel.yml @@ -0,0 +1,7 @@ +spring: + jpa: + show-sql: true + properties: + hibernate: + show_sql: true + format_sql: false diff --git a/annotation-service/annotation-web/src/main/resources/config/db.yml b/annotation-service/annotation-web/src/main/resources/config/db.yml new file mode 100644 index 000000000..12f21a0f9 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/db.yml @@ -0,0 +1,24 @@ +spring: + jpa: + properties: + hibernate: + ddl-auto: validate + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate: + naming: + physical-strategy: gr.cite.notification.config.db.PrefixPhysicalNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + datasource: + url: ${DB_CONNECTION_STRING} + username: ${DB_USER} + password: ${DB_PASSWORD} + driver-class-name: org.postgresql.Driver + hikari: + connection-timeout: 30000 + minimum-idle: 3 + maximum-pool-size: 5 + idle-timeout: 600000 + max-lifetime: 1800000 + +naming-strategy: + prefix: ant_ diff --git a/annotation-service/annotation-web/src/main/resources/config/email.yml b/annotation-service/annotation-web/src/main/resources/config/email.yml new file mode 100644 index 000000000..fb7c94ba0 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/email.yml @@ -0,0 +1,14 @@ +spring: + mail: + host: ${MAIL_HOST:} + port: ${MAIL_PORT:} + username: ${MAIL_USERNAME:} + password: ${MAIL_PASSWORD:} + properties: + mail: + smtp: + auth: ${MAIL_AUTH:} + starttls: + enable: ${MAIL_TLS:} +email: + address: ${MAIL_ADDRESS:} \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/errors.yml b/annotation-service/annotation-web/src/main/resources/config/errors.yml new file mode 100644 index 000000000..79762a9e7 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/errors.yml @@ -0,0 +1,43 @@ +error-thesaurus: + hash-conflict: + code: 100 + message: there is a hash conflict for the item modified. please reload to get the latest changes + forbidden: + code: 101 + message: insufficient rights + system-error: + code: 102 + message: an unexpected system error occurred + missing-tenant: + code: 103 + message: tenant scope not provided + invalid-api-key: + code: 104 + message: provided APIKey not valid + stale-api-key: + code: 105 + message: there was a problem authorizing you with your API key. Please try again. Contact the system administrator if the problem persists + model-validation: + code: 106 + message: validation error + sensitive-info: + code: 107 + message: you are attempting to access sensitive information. please don't do that + non-person-principal: + code: 108 + message: the operation is available only to person users + blocking-consent: + code: 113 + message: user consents are not sufficient to complete the operation + single-tenant-configuration-per-type-supported: + code: 116 + message: a single tenant configuration entry per config type is supported + incompatible-tenant-configuration-types: + code: 117 + message: the provided tenant configuration type is incompatible + missing-totp-token: + code: 118 + message: totp token not provided + overlapping-tenant-configuration-notifier-list: + code: 119 + message: Overlapping Tenant Configuration Notifier List diff --git a/annotation-service/annotation-web/src/main/resources/config/formatting.yml b/annotation-service/annotation-web/src/main/resources/config/formatting.yml new file mode 100644 index 000000000..abdc31184 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/formatting.yml @@ -0,0 +1,6 @@ +formatting: + options: + integer-format: "%,d" + decimal-digits-round: 2 + decimal-format: "#0.00" + date-time-format: "YYYY-MM-dd'T'HH:mm:ss" diff --git a/annotation-service/annotation-web/src/main/resources/config/idpclaims.yml b/annotation-service/annotation-web/src/main/resources/config/idpclaims.yml new file mode 100644 index 000000000..3372e4ca7 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/idpclaims.yml @@ -0,0 +1,39 @@ +idpclient: + claims: + mapping: + Subject: + - type: sub + Name: + - type: name + Client: + - type: client_id + AuthenticationMethod: + - type: amr + NotBefore: + - type: nbf + AuthenticatedAt: + - type: auth_time + ExpiresAt: + - type: exp + Email: + - type: email + Roles: + - type: resource_access + path: dmp_web.roles + Scope: + - type: scope + AccessToken: + - type: x-access-token + visibility: SENSITIVE + IssuedAt: + - type: iat + Issuer: + - type: iss + Audience: + - type: aud + TokenType: + - type: typ + AuthorizedParty: + - type: azp + Authorities: + - type: authorities diff --git a/annotation-service/annotation-web/src/main/resources/config/locale.yml b/annotation-service/annotation-web/src/main/resources/config/locale.yml new file mode 100644 index 000000000..b09c59e53 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/locale.yml @@ -0,0 +1,4 @@ +locale: + timezone: UTC + language: en + culture: en-US diff --git a/annotation-service/annotation-web/src/main/resources/config/logging-devel.yml b/annotation-service/annotation-web/src/main/resources/config/logging-devel.yml new file mode 100644 index 000000000..2f565c0d7 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/logging-devel.yml @@ -0,0 +1,2 @@ +logging: + config: classpath:logging/logback-dev.xml diff --git a/annotation-service/annotation-web/src/main/resources/config/logging.yml b/annotation-service/annotation-web/src/main/resources/config/logging.yml new file mode 100644 index 000000000..56e152d3f --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/logging.yml @@ -0,0 +1,35 @@ +logging: + context: + request: + requestIdKey: req.id + requestRemoteHostKey: req.remoteHost + requestUriKey: req.requestURI + requestQueryStringKey: req.queryString + requestUrlKey : req.requestURL + requestMethodKey: req.method + requestUserAgentKey: req.userAgent + requestForwardedForKey: req.xForwardedFor + requestSchemeKey: req.scheme + requestRemoteAddressKey: req.remoteAddr + requestRemotePortKey: req.remotePort + requestRemoteUserKey: req.remoteUser + principal: + subjectKey: usr.subject + nameKey: usr.name + clientKey: usr.client +audit: + enable: true + requestRemoteHostKey: req.remoteHost + requestUriKey: req.requestURI + requestQueryStringKey: req.queryString + requestUrlKey : req.requestURL + requestMethodKey: req.method + requestUserAgentKey: req.userAgent + requestForwardedForKey: req.xForwardedFor + requestSchemeKey: req.scheme + requestRemoteAddressKey: req.remoteAddr + requestRemotePortKey: req.remotePort + requestRemoteUserKey: req.remoteUser + principalSubjectKey: usr.subject + principalNameKey: usr.name + principalClientKey: usr.client diff --git a/annotation-service/annotation-web/src/main/resources/config/permissions.yml b/annotation-service/annotation-web/src/main/resources/config/permissions.yml new file mode 100644 index 000000000..d7247f8d8 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/permissions.yml @@ -0,0 +1,182 @@ +permissions: + extendedClaims: [ ] + policies: + + # Tenants + BrowseTenant: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + EditTenant: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteTenant: + roles: + - ic-sti-superuser + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + AllowNoTenant: + roles: + - ic-sti-superuser + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + # Users + BrowseUser: + roles: + - ic-sti-superuser + - tenantadmin + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditUser: + roles: + - admin + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteUser: + roles: + - ic-sti-superuser + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + # UserContactInfo + BrowseUserContactInfo: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditUserContactInfo: + roles: + - ic-sti-superuser + - user + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteUserContactInfo: + roles: + - ic-sti-superuser + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + #Notification + BrowseNotification: + roles: + - Admin + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditNotification: + roles: + - Admin + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + DeleteNotification: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + #Tenant Configuration + BrowseTenantConfiguration: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + EditTenantConfiguration: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + #User Notification Preference + BrowseUserNotificationPreference: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditUserNotificationPreference: + roles: + - ic-sti-superuser + - user + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + + # ViewPage Permissions + ViewNotificationPage: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewNotificationEventRulePage: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewInAppNotificationPage: + roles: + - ic-sti-superuser + - tenantadmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewNotificationTemplatePage: + roles: + - ic-sti-superuser + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + + # Notification Template Permissions + BrowseNotificationTemplate: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + EditNotificationTemplate: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteNotificationTemplate: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + + # In App Notification Permissions + BrowseInAppNotification: + roles: + - Admin + - User + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteInAppNotification: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/queue-devel.yml b/annotation-service/annotation-web/src/main/resources/config/queue-devel.yml new file mode 100644 index 000000000..1c761d9e5 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/queue-devel.yml @@ -0,0 +1,21 @@ +queue: + rabbitmq: + enable: true + durable: true + queue: cite_dmp_devel_notification_inbox_queue + exchange: cite_dmp_devel_queue + listenerEnabled: true + publisherEnabled: true + task: + publisher: + enable: true + options: + exchange: cite_dmp_devel_queue + rabbitmq: + enable: true + listener: + enable: true + options: + exchange: cite_dmp_devel_queue + rabbitmq: + enable: true diff --git a/annotation-service/annotation-web/src/main/resources/config/queue.yml b/annotation-service/annotation-web/src/main/resources/config/queue.yml new file mode 100644 index 000000000..6a5cdd471 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/queue.yml @@ -0,0 +1,56 @@ +spring: + rabbitmq: + host: ${RABBIT_HOST} + port: ${RABBIT_PORT} + username: ${RABBIT_USER} + password: ${RABBIT_PASS} + ssl: + enabled: false +queue: + rabbitmq: + enable: false + app-id: ${QUEUE_APP_ID} + durable: null + queue: null + exchange: null + listenerEnabled: true + publisherEnabled: true + #TODO + connection-recovery: + enable: true + network-recovery-interval: 5000 + unreachable-recovery-interval: 5000 + task: + publisher: + enable: false + options: + exchange: null + forget-me-completed-topic: forgetme.completed + what-you-know-about-me-completed-topic: whatyouknowaboutme.completed + generate-file-topic: generate.file + rabbitmq: + enable: false + interval-seconds: 30 + options: + retry-threashold: 100 + retry-delay-step-seconds: 300 + max-retry-delay-seconds: 10800 + too-old-to-send-seconds: 604800 + confirm-timeout-seconds: 30 + listener: + enable: false + options: + exchange: null + notify-topic: notification.notify + tenant-removal-topic: tenant.remove + tenant-touched-topic: tenant.touch + user-removal-topic: user.remove + user-touched-topic: user.touch + rabbitmq: + enable: false + interval-seconds: 30 + options: + retry-threashold: 100 + retry-delay-step-seconds: 300 + max-retry-delay-seconds: 10800 + too-old-to-send-seconds: 604800 \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/security.yml b/annotation-service/annotation-web/src/main/resources/config/security.yml new file mode 100644 index 000000000..3024921aa --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/security.yml @@ -0,0 +1,20 @@ +web: + security: + enabled: true + authorized-endpoints: [ api ] + allowed-endpoints: [ public, dataset, master-item, test ] + idp: + api-key: + enabled: true + authorization-header: Authorization + client-id: ${IDP_APIKEY_CLIENT_ID} + client-secret: ${IDP_APIKEY_CLIENT_SECRET} + scope: ${IDP_APIKEY_SCOPE} + resource: + token-type: JWT + opaque: + client-id: ${IDP_OPAQUE_CLIENT_ID} + client-secret: ${IDP_OPAQUE_CLIENT_SECRET} + jwt: + claims: [ role, x-role ] + issuer-uri: ${IDP_ISSUER_URI:} \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/server-devel.yml b/annotation-service/annotation-web/src/main/resources/config/server-devel.yml new file mode 100644 index 000000000..2a628f82f --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/server-devel.yml @@ -0,0 +1,2 @@ +server: + forward-headers-strategy: FRAMEWORK \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/server.yml b/annotation-service/annotation-web/src/main/resources/config/server.yml new file mode 100644 index 000000000..bc9a15d9f --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/server.yml @@ -0,0 +1,3 @@ +server: + port: ${WEB_PORT} + forward-headers-strategy: NONE \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/tenant.yml b/annotation-service/annotation-web/src/main/resources/config/tenant.yml new file mode 100644 index 000000000..7fbc2f91c --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/config/tenant.yml @@ -0,0 +1,8 @@ +tenant: + multitenancy: + is-multitenant: false + interceptor: + client-claims-prefix: client_ + white-listed-clients: [ ] + enforce-trusted-tenant: false + white-listed-endpoints: [ '/api/principal/my-tenants', '/api/principal/me','/api/user/user-settings', '/error', '/api/tenant-request' ] diff --git a/annotation-service/annotation-web/src/main/resources/logging/logback-dev.xml b/annotation-service/annotation-web/src/main/resources/logging/logback-dev.xml new file mode 100644 index 000000000..0aaad5f31 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/logging/logback-dev.xml @@ -0,0 +1,61 @@ + + + + + %date{ISO8601} [%thread] %-5level %logger{36} [%X{req.id}] - %message%n + + + + + logs/logging.log + + logs/logging.%d{yyyy-MM-dd}.%i.log + + 100MB + + 15 + + + %date{ISO8601} [%thread] %-5level %logger{36} [%X{req.id}] - %message%n + + + + + logs/auditing.log + + logs/auditing.%d{yyyy-MM-dd}.%i.log + + 100MB + + 15 + + + %date{ISO8601} - %X{req.id} - %message%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotation-service/annotation-web/src/main/resources/messages/messages.properties b/annotation-service/annotation-web/src/main/resources/messages/messages.properties new file mode 100644 index 000000000..93d6cd27a --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/messages/messages.properties @@ -0,0 +1,10 @@ +validation.empty=Value cannot be empty +validation.hashempty=Hash must be set +validation.lowerthanmin=Value must be larger than {value} +validation.largerthanmax=Value must be less than {value} +validation.invalidid=Not valid id +General_ItemNotFound=Item {0} of type {1} not found +Validation_Required={0} is required +Validation_OverPosting=Too much info +Validation_MaxLength={0} too long +Validation_UnexpectedValue=Unexpected value in field {0} \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/messages/messages_el.properties b/annotation-service/annotation-web/src/main/resources/messages/messages_el.properties new file mode 100644 index 000000000..4d060cc58 --- /dev/null +++ b/annotation-service/annotation-web/src/main/resources/messages/messages_el.properties @@ -0,0 +1,6 @@ +validation.empty=el-Value cannot be empty +validation.hashempty=el-Hash must be set +validation.lowerthanmin=el-Value must be larger than {value} +validation.largerthanmax=el-Value must be less than {value} +validation.invalidid=el-Not valid id +General_ItemNotFound=el-Item {0} of type {1} not found \ No newline at end of file diff --git a/annotation-service/annotation/.gitignore b/annotation-service/annotation/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/annotation-service/annotation/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/annotation-service/annotation/pom.xml b/annotation-service/annotation/pom.xml new file mode 100644 index 000000000..5e7fb3f45 --- /dev/null +++ b/annotation-service/annotation/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + + gr.cite + annotation-service-parent + 1.0.0 + ../pom.xml + + + annotation + + + 21 + 21 + 21 + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-mail + + + org.apache.poi + poi + 5.2.2 + + + org.apache.poi + poi-ooxml + 5.2.2 + + + + gr.cite + data-tools + 2.1.2 + + + gr.cite + field-set + 2.1.0 + + + gr.cite + oidc-authn + 1.0.0 + + + gr.cite + logging + 1.0.0 + + + gr.cite + oidc-authz + 1.0.0 + + + gr.cite + exceptions + 1.0.0 + + + gr.cite + validation + 3.0.2 + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + gr.cite + cipher + 1.0.0 + compile + + + + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/audit/AuditableAction.java b/annotation-service/annotation/src/main/java/gr/cite/notification/audit/AuditableAction.java new file mode 100644 index 000000000..0ad1baed7 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/audit/AuditableAction.java @@ -0,0 +1,48 @@ +package gr.cite.notification.audit; + +import gr.cite.tools.logging.EventId; + +public class AuditableAction { + public static final EventId Tenant_Available_Notifiers_Query = new EventId(2006, "Tenant_Available_Notifiers_Query"); + public static final EventId Principal_Lookup = new EventId(6000, "Principal_Lookup"); + public static final EventId Tenants_Lookup = new EventId(6001, "Tenants_Lookup"); + + public static final EventId User_Available_Notifiers_Query = new EventId(10004, "User_Available_Notifiers_Query"); + + public static final EventId User_Query = new EventId(11000, "User_Query"); + public static final EventId User_Lookup = new EventId(11001, "User_Lookup"); + public static final EventId User_Persist = new EventId(11002, "User_Persist"); + public static final EventId User_Delete = new EventId(11003, "User_Delete"); + + public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query"); + public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup"); + public static final EventId Tenant_Persist = new EventId(12002, "Tenant_Persist"); + public static final EventId Tenant_Delete = new EventId(12003, "Tenant_Delete"); + + public static final EventId Notification_Query = new EventId(19000, "Notification_Query"); + public static final EventId Notification_Lookup = new EventId(19001, "Notification_Lookup"); + public static final EventId Notification_Persist = new EventId(19002, "Notification_Persist"); + public static final EventId Notification_Delete = new EventId(19003, "Notification_Delete"); + + public static final EventId InApp_Notification_Query = new EventId(20000, "InApp_Notification_Query"); + public static final EventId InApp_Notification_Lookup = new EventId(20001, "InApp_Notification_Lookup"); + public static final EventId InApp_Notification_Persist = new EventId(20002, "InApp_Notification_Persist"); + public static final EventId InApp_Notification_Delete = new EventId(20003, "InApp_Notification_Delete"); + public static final EventId InApp_Notification_Read = new EventId(20003, "InApp_Notification_Read"); + public static final EventId InApp_Notification_Read_All = new EventId(20003, "InApp_Notification_Read_All"); + + public static final EventId Tenant_Configuration_Query = new EventId(21000, "Tenant_Configuration_Query"); + public static final EventId Tenant_Configuration_Lookup = new EventId(21001, "Tenant_Configuration_Lookup"); + public static final EventId Tenant_Configuration_Persist = new EventId(21002, "Tenant_Configuration_Persist"); + public static final EventId Tenant_Configuration_Delete = new EventId(21003, "Tenant_Configuration_Delete"); + + public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query"); + public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup"); + public static final EventId User_Notification_Preference_Persist = new EventId(22002, "User_Notification_Preference_Persist"); + public static final EventId User_Notification_Preference_Delete = new EventId(22003, "User_Notification_Preference_Delete"); + + public static final EventId Notification_Template_Query = new EventId(23000, "Notification_Template_Query"); + public static final EventId Notification_Template_Lookup = new EventId(23001, "Notification_Template_Lookup"); + public static final EventId Notification_Template_Persist = new EventId(23002, "Notification_Template_Persist"); + public static final EventId Notification_Template_Delete = new EventId(23003, "Notification_Template_Delete"); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/AuthorizationFlags.java b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/AuthorizationFlags.java new file mode 100644 index 000000000..2cba3132b --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/AuthorizationFlags.java @@ -0,0 +1,8 @@ +package gr.cite.notification.authorization; + +import java.util.EnumSet; + +public enum AuthorizationFlags { + None, Permission, Owner; + public static final EnumSet OwnerOrPermission = EnumSet.of(Owner, Permission); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedAuthorizationRequirement.java b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedAuthorizationRequirement.java new file mode 100644 index 000000000..8171339da --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedAuthorizationRequirement.java @@ -0,0 +1,6 @@ +package gr.cite.notification.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; + +public class OwnedAuthorizationRequirement implements AuthorizationRequirement { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedResource.java b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedResource.java new file mode 100644 index 000000000..124f7ba2b --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/OwnedResource.java @@ -0,0 +1,26 @@ +package gr.cite.notification.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationResource; + +import java.util.List; +import java.util.UUID; + +public class OwnedResource extends AuthorizationResource { + private List userIds; + + public OwnedResource(UUID userId) { + this(List.of(userId)); + } + + public OwnedResource(List userIds) { + this.userIds = userIds; + } + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/Permission.java b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/Permission.java new file mode 100644 index 000000000..59b247dec --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/authorization/Permission.java @@ -0,0 +1,48 @@ +package gr.cite.notification.authorization; + +public final class Permission { + //User + public static String BrowseUser = "BrowseUser"; + public static String EditUser = "EditUser"; + public static String DeleteUser = "DeleteUser"; + + //UserContactInfo + public static String BrowseUserContactInfo = "BrowseUserContactInfo"; + public static String EditUserContactInfo = "EditUserContactInfo"; + public static String DeleteUserContactInfo = "DeleteUserContactInfo"; + + //Tenant + public static String BrowseTenant = "BrowseTenant"; + public static String EditTenant = "EditTenant"; + public static String DeleteTenant = "DeleteTenant"; + public static String AllowNoTenant = "AllowNoTenant"; + + //Notification + public static final String BrowseNotification = "BrowseNotification"; + public static String EditNotification = "EditNotification"; + public static String DeleteNotification = "DeleteNotification"; + + public static final String BrowseTenantConfiguration = "BrowseTenantConfiguration"; + public static final String EditTenantConfiguration = "EditTenantConfiguration"; + + //Notification Preference + public static final String BrowseUserNotificationPreference = "BrowseUserNotificationPreference"; + public static final String EditUserNotificationPreference = "EditUserNotificationPreference"; + + //Notification Template + public static final String BrowseNotificationTemplate = "BrowseNotificationTemplate"; + public static String EditNotificationTemplate = "EditNotificationTemplate"; + public static String DeleteNotificationTemplate = "DeleteNotificationTemplate"; + + //InApp Notification + public static final String BrowseInAppNotification = "BrowseInAppNotification"; + public static String DeleteInAppNotification = "DeleteInAppNotification"; + + // UI Pages + public static String ViewTenantConfigurationPage = "ViewTenantConfigurationPage"; + public static String ViewNotificationPage = "ViewNotificationPage"; + public static String ViewNotificationEventRulePage = "ViewNotificationEventRulePage"; + public static String ViewInAppNotificationPage = "ViewInAppNotificationPage"; + public static String ViewNotificationTemplatePage = "ViewNotificationTemplatePage"; + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java new file mode 100644 index 000000000..c4de4eb45 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java @@ -0,0 +1,93 @@ +package gr.cite.notification.cache; + +import gr.cite.notification.config.formatting.FormattingUserprofileCacheOptions; +import gr.cite.notification.event.UserTouchedEvent; +import gr.cite.tools.cache.CacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Locale; +import java.util.UUID; + +@Service +public class FormattingUserprofileCacheCacheService extends CacheService { + + public static class UserFormattingProfileCacheValue { + + public UserFormattingProfileCacheValue() { + } + + public UserFormattingProfileCacheValue(UUID userId, String zone, String culture, String language) { + this.userId = userId; + this.zone = zone; + this.culture = culture; + this.language = language; + } + + private UUID userId; + private String zone; + private String culture; + private String language; + + public String getZone() { + return zone; + } + + public void setZone(String zone) { + this.zone = zone; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + } + + @Autowired + public FormattingUserprofileCacheCacheService(FormattingUserprofileCacheOptions options) { + super(options); + } + + @EventListener + public void handleUserTouchedEvent(UserTouchedEvent event) { + if (event.getUserId() != null) this.evict(this.buildKey(event.getUserId())); + } + + @Override + protected Class valueClass() { + return UserFormattingProfileCacheValue.class; + } + + @Override + public String keyOf(UserFormattingProfileCacheValue value) { + return this.buildKey(value.getUserId()); + } + + + public String buildKey(UUID userId) { + return this.generateKey(new HashMap<>() {{ + put("$user_id$", userId.toString().toLowerCase(Locale.ROOT)); + }}); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/JsonHandlingService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/JsonHandlingService.java new file mode 100644 index 000000000..f9dd4accc --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/JsonHandlingService.java @@ -0,0 +1,43 @@ +package gr.cite.notification.common; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class JsonHandlingService { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(JsonHandlingService.class)); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public String toJson(Object item) throws JsonProcessingException { + if (item == null) return null; + return objectMapper.writeValueAsString(item); + } + + public String toJsonSafe(Object item) { + if (item == null) return null; + try { + return objectMapper.writeValueAsString(item); + } catch (Exception ex) { + logger.error("Json Parsing Error: " + ex.getLocalizedMessage(), ex); + return null; + } + } + + public T fromJson(Class type, String json) throws JsonProcessingException { + if (json == null) return null; + return objectMapper.readValue(json, type); + } + + public T fromJsonSafe(Class type, String json) { + if (json == null) return null; + try { + return objectMapper.readValue(json, type); + } catch (Exception ex) { + logger.error("Json Parsing Error: " + ex.getLocalizedMessage(), ex); + return null; + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/StringUtils.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/StringUtils.java new file mode 100644 index 000000000..0a73b94ef --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/StringUtils.java @@ -0,0 +1,8 @@ +package gr.cite.notification.common; + +public class StringUtils { + + public static Boolean isNullOrEmpty(String string) { + return string == null || string.isEmpty(); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/XmlHandlingService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/XmlHandlingService.java new file mode 100644 index 000000000..cb65cfb54 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/XmlHandlingService.java @@ -0,0 +1,117 @@ +package gr.cite.notification.common; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.notification.common.types.xml.XmlSerializable; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; +import jakarta.xml.bind.Unmarshaller; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.management.InvalidApplicationException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) +public class XmlHandlingService { + + public String generateXml(Document doc) throws TransformerException { + TransformerFactory tFact = TransformerFactory.newInstance(); + Transformer trans = tFact.newTransformer(); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + DOMSource source = new DOMSource(doc); + trans.setOutputProperty(OutputKeys.INDENT, "yes"); + trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + trans.transform(source, result); + return writer.toString(); + } + + public String toXml(Object item) throws JsonProcessingException, JAXBException, ParserConfigurationException, InvalidApplicationException, TransformerException { + if (XmlSerializable.class.isAssignableFrom(item.getClass())){ + Document document = this.getDocument(); + if (document == null) throw new InvalidApplicationException("Can not create document"); + document.appendChild(((XmlSerializable)item).toXml(document)); + return this.generateXml(document); + } + + JAXBContext context = JAXBContext.newInstance(item.getClass()); + Marshaller marshaller = context.createMarshaller(); + StringWriter out = new StringWriter(); + marshaller.marshal(item, out); + return out.toString(); + } + + public String toXmlSafe(Object item) { + if (item == null) return null; + try { + return this.toXml(item); + } catch (Exception ex) { + return null; + } + } + + public T fromXml(Class type, String xmlString) throws JAXBException, InstantiationException, IllegalAccessException, ParserConfigurationException, IOException, SAXException { + if (XmlSerializable.class.isAssignableFrom(type)){ + XmlSerializable object = (XmlSerializable)type.newInstance(); + return (T) object.fromXml(this.getDocument(xmlString).getDocumentElement()); + } else { + JAXBContext jaxbContext = JAXBContext.newInstance(type); + Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + + return (T) jaxbUnmarshaller.unmarshal(new StringReader(xmlString)); + } + } + + public T fromXmlSafe(Class type, String xmlString) { + if (xmlString == null) return null; + try { + return this.fromXml(type, xmlString); + } catch (Exception ex) { + return null; + } + } + +// public > T xmlSerializableFromXml(Class type, String xmlString) throws JAXBException, InstantiationException, IllegalAccessException, ParserConfigurationException, IOException, SAXException { +// T object = type.newInstance(); +// return (T) object.fromXml(this.getDocument(xmlString).getDocumentElement()); +// } +// +// public > T xmlSerializableFromXmlSafe(Class type, String xmlString) { +// if (xmlString == null) return null; +// try { +// return this.xmlSerializableFromXml(type, xmlString); +// } catch (Exception ex) { +// return null; +// } +// } + + public Document getDocument(String xml) throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + InputSource inputStream = new InputSource(new StringReader(xml)); + return docBuilder.parse(inputStream); + } + + public Document getDocument() throws ParserConfigurationException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + return docBuilder.newDocument(); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java new file mode 100644 index 000000000..292ec6b33 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java @@ -0,0 +1,29 @@ +package gr.cite.notification.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import gr.cite.notification.data.conventers.DatabaseEnum; + +import java.util.Map; + +public enum ContactInfoType implements DatabaseEnum { + Email((short) 0), + MobilePhone((short) 1), + LandLinePhone((short) 2); + private final Short value; + + ContactInfoType(Short value) { + this.value = value; + } + + @Override + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(ContactInfoType.class); + + public static ContactInfoType of(Short i) { + return map.get(i); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/EnumUtils.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/EnumUtils.java new file mode 100644 index 000000000..fa581f847 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/EnumUtils.java @@ -0,0 +1,16 @@ +package gr.cite.notification.common.enums; + +import gr.cite.notification.data.conventers.DatabaseEnum; + +import java.util.HashMap; +import java.util.Map; + +public class EnumUtils { + public static & DatabaseEnum, EnumValue> Map getEnumValueMap(Class enumType){ + HashMap map = new HashMap<>(); + for (EnumType v : enumType.getEnumConstants()) { + map.put(v.getValue(), v); + } + return map; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/IsActive.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/IsActive.java new file mode 100644 index 000000000..10b9dc528 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/IsActive.java @@ -0,0 +1,28 @@ +package gr.cite.notification.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import gr.cite.notification.data.conventers.DatabaseEnum; + +import java.util.Map; + +public enum IsActive implements DatabaseEnum { + Inactive((short) 0), + Active((short) 1); + + private final Short value; + + IsActive(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(IsActive.class); + + public static IsActive of(Short i) { + return map.get(i); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/TenantConfigurationType.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/TenantConfigurationType.java new file mode 100644 index 000000000..799a59c7f --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/enums/TenantConfigurationType.java @@ -0,0 +1,29 @@ +package gr.cite.notification.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import gr.cite.notification.data.conventers.DatabaseEnum; + +import java.util.Map; + +public enum TenantConfigurationType implements DatabaseEnum { + EMAIL_CLIENT_CONFIGURATION((short)1), + DEFAULT_USER_LOCALE((short)3), + NOTIFIER_LIST((short)4); + + private final Short value; + + TenantConfigurationType(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(TenantConfigurationType.class); + + public static TenantConfigurationType of(Short i) { + return map.get(i); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java new file mode 100644 index 000000000..8c33cb9f7 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java @@ -0,0 +1,58 @@ +package gr.cite.notification.common.lock; + +import org.springframework.stereotype.Service; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +@Service +public class LockByKeyManager { + + private static class LockWrapper { + private final ReentrantLock lock = new ReentrantLock(); + private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1); + + private LockWrapper addThreadInQueue() { + numberOfThreadsInQueue.incrementAndGet(); + return this; + } + + private int removeThreadFromQueue() { + return numberOfThreadsInQueue.decrementAndGet(); + } + + } + + private static ConcurrentHashMap locks = new ConcurrentHashMap(); + + public void lock(String key) { + LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); + lockWrapper.lock.lock(); + } + + public boolean tryLock(String key, long timeout, TimeUnit unit) throws InterruptedException { + LockWrapper lockWrapper = null; + try { + lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); + return lockWrapper.lock.tryLock(timeout, unit); + } catch (Exception ex){ + if (lockWrapper != null && lockWrapper.removeThreadFromQueue() == 0) { + // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal + locks.remove(key, lockWrapper); + } + throw ex; + } + } + + public void unlock(String key) { + LockWrapper lockWrapper = locks.get(key); + lockWrapper.lock.unlock(); + if (lockWrapper.removeThreadFromQueue() == 0) { + // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal + locks.remove(key, lockWrapper); + } + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestAttributes.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestAttributes.java new file mode 100644 index 000000000..f8f2df085 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestAttributes.java @@ -0,0 +1,125 @@ +package gr.cite.notification.common.scope.fake; + +import org.springframework.util.Assert; +import org.springframework.web.context.request.RequestAttributes; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class FakeRequestAttributes implements RequestAttributes { + private Map requestAttributeMap = new HashMap<>(); + private final Map requestDestructionCallbacks = new LinkedHashMap<>(8); + private volatile boolean requestActive = true; + + @Override + public Object getAttribute(String name, int scope) { + if (scope == RequestAttributes.SCOPE_REQUEST) { + if (!isRequestActive()) { + throw new IllegalStateException("Cannot ask for request attribute - request is not active anymore!"); + } + return this.requestAttributeMap.get(name); + } else { + throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName()); + } + } + + @Override + public void setAttribute(String name, Object value, int scope) { + if (scope == RequestAttributes.SCOPE_REQUEST) { + if (!isRequestActive()) { + throw new IllegalStateException("Cannot set request attribute - request is not active anymore!"); + } + this.requestAttributeMap.put(name, value); + } else { + throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName()); + } + } + + @Override + public void removeAttribute(String name, int scope) { + if (scope == RequestAttributes.SCOPE_REQUEST) { + if (isRequestActive()) { + removeRequestDestructionCallback(name); + this.requestAttributeMap.remove(name); + } + } else { + throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName()); + } + } + + @Override + public String[] getAttributeNames(int scope) { + if (scope == RequestAttributes.SCOPE_REQUEST) { + if (!isRequestActive()) { + throw new IllegalStateException("Cannot ask for request attributes - request is not active anymore!"); + } + return this.requestAttributeMap.keySet().toArray(new String[0]); + } else { + throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName()); + } + //return new String[0]; + } + + @Override + public void registerDestructionCallback(String name, Runnable callback, int scope) { + if (scope == SCOPE_REQUEST) { + registerRequestDestructionCallback(name, callback); + } else { + throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName()); + } + } + + protected final void registerRequestDestructionCallback(String name, Runnable callback) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(callback, "Callback must not be null"); + synchronized (this.requestDestructionCallbacks) { + this.requestDestructionCallbacks.put(name, callback); + } + } + + @Override + public Object resolveReference(String key) { + // Not supported + return null; + } + + @Override + public String getSessionId() { + return null; + } + + @Override + public Object getSessionMutex() { + return null; + } + + public void requestCompleted() { + executeRequestDestructionCallbacks(); + for (String name : getAttributeNames(RequestAttributes.SCOPE_REQUEST)) { + this.removeAttribute(name, RequestAttributes.SCOPE_REQUEST); + } + this.requestActive = false; + } + + private final boolean isRequestActive() { + return this.requestActive; + } + + + private final void removeRequestDestructionCallback(String name) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.requestDestructionCallbacks) { + this.requestDestructionCallbacks.remove(name); + } + } + + private void executeRequestDestructionCallbacks() { + synchronized (this.requestDestructionCallbacks) { + for (Runnable runnable : this.requestDestructionCallbacks.values()) { + runnable.run(); + } + this.requestDestructionCallbacks.clear(); + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestScope.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestScope.java new file mode 100644 index 000000000..e8b6716e2 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestScope.java @@ -0,0 +1,40 @@ +package gr.cite.notification.common.scope.fake; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.io.Closeable; + +public class FakeRequestScope implements Closeable { + private RequestAttributes initialRequestAttributes = null; + private FakeRequestAttributes currentRequestAttributes = null; + boolean isInUse = false; + + public FakeRequestScope() { + this.reset(); + } + + public void reset() { + this.close(); + this.isInUse = true; + + this.initialRequestAttributes = RequestContextHolder.getRequestAttributes(); + this.currentRequestAttributes = new FakeRequestAttributes(); + RequestContextHolder.setRequestAttributes(this.currentRequestAttributes); + } + + @Override + public void close() { + if (!this.isInUse) return; + this.isInUse = false; + + if (initialRequestAttributes != null) RequestContextHolder.setRequestAttributes(initialRequestAttributes); + else RequestContextHolder.resetRequestAttributes(); + + if (currentRequestAttributes != null) currentRequestAttributes.requestCompleted(); + + this.initialRequestAttributes = null; + this.currentRequestAttributes = null; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyConfiguration.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyConfiguration.java new file mode 100644 index 000000000..52ed9fa9f --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyConfiguration.java @@ -0,0 +1,9 @@ +package gr.cite.notification.common.scope.tenant; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(MultitenancyProperties.class) +public class MultitenancyConfiguration { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyProperties.java new file mode 100644 index 000000000..56f8dd353 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyProperties.java @@ -0,0 +1,16 @@ +package gr.cite.notification.common.scope.tenant; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "tenant.multitenancy") +public class MultitenancyProperties { + private boolean isMultitenant; + + public boolean isMultitenant() { + return isMultitenant; + } + + public void setIsMultitenant(boolean multitenant) { + isMultitenant = multitenant; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java new file mode 100644 index 000000000..a2f8c7dee --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java @@ -0,0 +1,86 @@ +package gr.cite.notification.common.scope.tenant; + +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import org.hibernate.Session; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +@Component +@RequestScope +public class TenantScope { + public static final String TenantReplaceParameter = "::TenantCode::"; + public static final String TenantCodesClaimName = "TenantCodes"; + public static final String TenantClaimName = "x-tenant"; + + private MultitenancyProperties multitenancy; + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScope.class)); + private UUID tenant = null; + private String tenantCode = null; + private UUID initialTenant = null; + private String initialTenantCode = null; + + @Autowired + public TenantScope(MultitenancyProperties multitenancy) { + this.multitenancy = multitenancy; + } + + public Boolean isMultitenant() { + return multitenancy.isMultitenant(); + } + + public Boolean isSet() { + if (!this.isMultitenant()) return true; + return this.tenant != null; + } + + public UUID getTenant() throws InvalidApplicationException { + if (!this.isMultitenant()) return null; + if (this.tenant == null) throw new InvalidApplicationException("tenant not set"); + return this.tenant; + } + + public String getTenantCode() throws InvalidApplicationException { + if (!this.isMultitenant()) return null; + if (this.tenant == null) throw new InvalidApplicationException("tenant not set"); + return this.tenantCode; + } + + public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) { + this.tenant = tenant; + + if(this.tenant != null) { + entityManager + .unwrap(Session.class) + .enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.tenant.toString()); + } + } + + public void removeTempTenant(EntityManager entityManager) { + this.tenant = this.initialTenant; + this.tenantCode = this.initialTenantCode; + if(this.initialTenant != null) { + entityManager + .unwrap(Session.class) + .enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.initialTenant.toString()); + } + } + + public void setTenant(UUID tenant, String tenantCode) { + if (this.isMultitenant()) { + this.tenant = tenant; + this.initialTenant = tenant; + this.tenantCode = tenantCode; + this.initialTenantCode = tenantCode; + } + } +} + + + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScoped.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScoped.java new file mode 100644 index 000000000..77ecacaea --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/tenant/TenantScoped.java @@ -0,0 +1,8 @@ +package gr.cite.notification.common.scope.tenant; + +import java.util.UUID; + +public interface TenantScoped { + void setTenantId(UUID tenantId); + UUID getTenantId(); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/user/UserScope.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/user/UserScope.java new file mode 100644 index 000000000..0315f7dac --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/scope/user/UserScope.java @@ -0,0 +1,34 @@ +package gr.cite.notification.common.scope.user; + +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +@Component +@RequestScope +public class UserScope { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserScope.class)); + private UUID userId = null; + + public Boolean isSet(){ + return this.userId != null; + } + + public UUID getUserId() throws InvalidApplicationException { + if (this.userId == null) throw new InvalidApplicationException("user not set"); + return this.userId; + } + + public UUID getUserIdSafe() { + return this.userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/Attachment.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/Attachment.java new file mode 100644 index 000000000..da6249e93 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/Attachment.java @@ -0,0 +1,36 @@ +package gr.cite.notification.common.types.notification; + +public class Attachment { + + private String fileRef, fileName, mimeType; + + public Attachment(String fileRef, String fileName, String mimeType) { + this.fileRef = fileRef; + this.fileName = fileName; + this.mimeType = mimeType; + } + + public String getFileRef() { + return fileRef; + } + + public void setFileRef(String fileRef) { + this.fileRef = fileRef; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/ContactPair.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/ContactPair.java new file mode 100644 index 000000000..dcb9adac2 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/notification/ContactPair.java @@ -0,0 +1,32 @@ +package gr.cite.notification.common.types.notification; + +import gr.cite.notification.common.enums.ContactInfoType; + +public class ContactPair { + private ContactInfoType type; + private String contact; + + public ContactPair(ContactInfoType type, String contact) { + this.type = type; + this.contact = contact; + } + + public ContactPair() { + } + + public ContactInfoType getType() { + return type; + } + + public void setType(ContactInfoType type) { + this.type = type; + } + + public String getContact() { + return contact; + } + + public void setContact(String contact) { + this.contact = contact; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java new file mode 100644 index 000000000..4d5a0a7db --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java @@ -0,0 +1,35 @@ +package gr.cite.notification.common.types.tenant; + + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "config") +@XmlAccessorType(XmlAccessType.FIELD) +public class TenantConfigEntity { + + @XmlElement(name = "deposit-configuration") + private TenantDepositConfigEntity deposit; + + @XmlElement(name = "file-transformers-configuration") + private TenantFileTransformersConfigEntity fileTransformers; + + public TenantDepositConfigEntity getDeposit() { + return deposit; + } + + public void setDeposit(TenantDepositConfigEntity deposit) { + this.deposit = deposit; + } + + public TenantFileTransformersConfigEntity getFileTransformers() { + return fileTransformers; + } + + public void setFileTransformers(TenantFileTransformersConfigEntity fileTransformers) { + this.fileTransformers = fileTransformers; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java new file mode 100644 index 000000000..675520200 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java @@ -0,0 +1,25 @@ +package gr.cite.notification.common.types.tenant; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElementWrapper; + +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class TenantDepositConfigEntity { + + @XmlElementWrapper(name = "sources") + @XmlElement(name = "source") + private List sources; + + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java new file mode 100644 index 000000000..6ce32c8b8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java @@ -0,0 +1,24 @@ +package gr.cite.notification.common.types.tenant; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElementWrapper; + +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class TenantFileTransformersConfigEntity { + + @XmlElementWrapper(name = "sources") + @XmlElement(name = "source") + private List sources; + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java new file mode 100644 index 000000000..ff46bba7a --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java @@ -0,0 +1,79 @@ +package gr.cite.notification.common.types.tenant; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElementWrapper; + +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class TenantSourceEntity { + + @XmlElement(name = "url") + private String url; + + @XmlElementWrapper(name = "codes") + @XmlElement(name = "code") + private List codes; + + @XmlElement(name = "issuer-url") + private String issuerUrl; + + @XmlElement(name = "client-id") + private String clientId; + + @XmlElement(name = "client-secret") + private String clientSecret; + + @XmlElement(name = "scope") + private String scope; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public List getCodes() { + return codes; + } + + public void setCodes(List codes) { + this.codes = codes; + } + + public String getIssuerUrl() { + return issuerUrl; + } + + public void setIssuerUrl(String issuerUrl) { + this.issuerUrl = issuerUrl; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/DefaultUserLocaleConfigurationDataContainer.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/DefaultUserLocaleConfigurationDataContainer.java new file mode 100644 index 000000000..2724f54c1 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/DefaultUserLocaleConfigurationDataContainer.java @@ -0,0 +1,37 @@ +package gr.cite.notification.common.types.tenantconfiguration; + +public class DefaultUserLocaleConfigurationDataContainer { + + public static class Field { + public static final String LANGUAGE = "language"; + public static final String TIME_ZONE = "timeZone"; + public static final String CULTURE = "culture"; + } + private String language; + private String timeZone; + private String culture; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/EmailClientConfigurationDataContainer.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/EmailClientConfigurationDataContainer.java new file mode 100644 index 000000000..71d70a997 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/tenantconfiguration/EmailClientConfigurationDataContainer.java @@ -0,0 +1,87 @@ +package gr.cite.notification.common.types.tenantconfiguration; + +public class EmailClientConfigurationDataContainer { + + public static class Field { + public static final String REQUIRE_CREDENTIALS = "requireCredentials"; + public static final String ENABLE_SSL = "enableSSL"; + public static final String CERTIFICATE_PATH = "certificatePath"; + public static final String HOST_SERVER = "hostServer"; + public static final String HOST_PORT_NO = "hostPortNo"; + public static final String EMAIL_ADDRESS = "emailAddress"; + public static final String EMAIL_USER_NAME = "emailUserName"; + public static final String EMAIL_PASSWORD = "emailPassword"; + } + private Boolean requireCredentials; + private Boolean enableSSL; + private String certificatePath; + private String hostServer; + private Integer hostPortNo; + private String emailAddress; + private String emailUserName; + private String emailPassword; + + public Boolean getRequireCredentials() { + return requireCredentials; + } + + public void setRequireCredentials(Boolean requireCredentials) { + this.requireCredentials = requireCredentials; + } + + public Boolean getEnableSSL() { + return enableSSL; + } + + public void setEnableSSL(Boolean enableSSL) { + this.enableSSL = enableSSL; + } + + public String getCertificatePath() { + return certificatePath; + } + + public void setCertificatePath(String certificatePath) { + this.certificatePath = certificatePath; + } + + public String getHostServer() { + return hostServer; + } + + public void setHostServer(String hostServer) { + this.hostServer = hostServer; + } + + public Integer getHostPortNo() { + return hostPortNo; + } + + public void setHostPortNo(Integer hostPortNo) { + this.hostPortNo = hostPortNo; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getEmailUserName() { + return emailUserName; + } + + public void setEmailUserName(String emailUserName) { + this.emailUserName = emailUserName; + } + + public String getEmailPassword() { + return emailPassword; + } + + public void setEmailPassword(String emailPassword) { + this.emailPassword = emailPassword; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java new file mode 100644 index 000000000..1c8c6f4b1 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java @@ -0,0 +1,60 @@ +package gr.cite.notification.common.types.user; + +import java.util.UUID; + +public class AdditionalInfoEntity { + private String avatarUrl; + private String timezone; + private String culture; + private String language; + private String roleOrganization; + private UUID organizationId; + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public UUID getOrganizationId() { + return organizationId; + } + + public void setOrganizationId(UUID organizationId) { + this.organizationId = organizationId; + } + + public String getRoleOrganization() { + return roleOrganization; + } + + public void setRoleOrganization(String roleOrganization) { + this.roleOrganization = roleOrganization; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlBuilder.java new file mode 100644 index 000000000..356bbd0a1 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlBuilder.java @@ -0,0 +1,87 @@ +package gr.cite.notification.common.types.xml; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; + + +public class XmlBuilder { + private static final Logger logger = LoggerFactory.getLogger(XmlBuilder.class); + + public static Document getDocument() { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + try { + docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + return doc; + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage(), e); + return null; + } + } + + public static String generateXml(Document doc) { + TransformerFactory tFact = TransformerFactory.newInstance(); + Transformer trans; + try { + trans = tFact.newTransformer(); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + DOMSource source = new DOMSource(doc); + trans.setOutputProperty(OutputKeys.INDENT, "yes"); + trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + trans.transform(source, result); + return writer.toString(); + } catch (TransformerException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage(), e); + return null; + } + } + + public static Document fromXml(String xml) { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + try { + docBuilder = docFactory.newDocumentBuilder(); + InputSource inputStream = new InputSource(new StringReader(xml)); + Document doc = docBuilder.parse(inputStream); + return doc; + } catch (ParserConfigurationException | SAXException | IOException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage(), e); + return null; + } + } + + + public static Element getNodeFromListByTagName(NodeList list, String tagName) { + for (int temp = 0; temp < list.getLength(); temp++) { + Node element = list.item(temp); + if (element.getNodeType() == Node.ELEMENT_NODE) { + if (element.getNodeName().equals(tagName)) return (Element) element; + } + } + return null; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlSerializable.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlSerializable.java new file mode 100644 index 000000000..8a9d2bc7c --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/types/xml/XmlSerializable.java @@ -0,0 +1,11 @@ +package gr.cite.notification.common.types.xml; + + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public interface XmlSerializable { + Element toXml(Document doc); + + T fromXml(Element item); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/common/validation/BaseValidator.java b/annotation-service/annotation/src/main/java/gr/cite/notification/common/validation/BaseValidator.java new file mode 100644 index 000000000..97f72042a --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/common/validation/BaseValidator.java @@ -0,0 +1,63 @@ +package gr.cite.notification.common.validation; + +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.validation.AbstractValidator; +import gr.cite.tools.validation.ValidationResult; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public abstract class BaseValidator extends AbstractValidator { + protected final ConventionService conventionService; + protected final ErrorThesaurusProperties errors; + + protected BaseValidator(ConventionService conventionService, ErrorThesaurusProperties errors) { + this.conventionService = conventionService; + this.errors = errors; + } + + @Override + public void validateForce(Object target) { + this.validate(target); + ValidationResult result = result(); + if (!result.isValid()) { + List>> errorsMap = this.flattenValidationResult(); + throw new MyValidationException(this.errors.getModelValidation().getCode(), errorsMap); + } + } + + protected Boolean isValidGuid(UUID guid) { + return this.conventionService.isValidGuid(guid); + } + + protected Boolean isValidHash(String hash) { + return this.conventionService.isValidHash(hash); + } + + protected Boolean isEmpty(String value) { + return this.conventionService.isNullOrEmpty(value); + } + protected Boolean isListNullOrEmpty(List value) { + return this.conventionService.isListNullOrEmpty(value); + } + protected Boolean isNull(Object value) { + return value == null; + } + + protected Boolean isNull(Collection value) { + return value == null; + } + + protected Boolean lessEqualLength(String value, int size) { + return value.length() <= size; + } + + protected Boolean lessEqual(Integer value, int target) { + return value <= target; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/db/NamingStrategyProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/db/NamingStrategyProperties.java new file mode 100644 index 000000000..60d731cf9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/db/NamingStrategyProperties.java @@ -0,0 +1,20 @@ +package gr.cite.notification.config.db; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "naming-strategy") +public class NamingStrategyProperties { + + private final String prefix; + + @ConstructorBinding + public NamingStrategyProperties(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/db/PrefixPhysicalNamingStrategy.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/db/PrefixPhysicalNamingStrategy.java new file mode 100644 index 000000000..d058a1fa5 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/db/PrefixPhysicalNamingStrategy.java @@ -0,0 +1,28 @@ +package gr.cite.notification.config.db; + +import gr.cite.notification.common.StringUtils; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@EnableConfigurationProperties({NamingStrategyProperties.class}) +public class PrefixPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl { + + private final NamingStrategyProperties namingStrategyProperties; + + public PrefixPhysicalNamingStrategy(NamingStrategyProperties namingStrategyProperties) { + this.namingStrategyProperties = namingStrategyProperties; + } + + @Override + public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment context) { + if (StringUtils.isNullOrEmpty(namingStrategyProperties.getPrefix())) + return super.toPhysicalTableName(logicalName, context); + Identifier identifier = new Identifier(namingStrategyProperties.getPrefix() + logicalName.getText(), logicalName.isQuoted()); + return super.toPhysicalTableName(identifier, context); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailConfig.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailConfig.java new file mode 100644 index 000000000..d3d210f40 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailConfig.java @@ -0,0 +1,9 @@ +package gr.cite.notification.config.email; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(EmailProperties.class) +public class EmailConfig { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailProperties.java new file mode 100644 index 000000000..505b2bd96 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/email/EmailProperties.java @@ -0,0 +1,17 @@ +package gr.cite.notification.config.email; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "email") +public class EmailProperties { + + private final String address; + + public EmailProperties(String address) { + this.address = address; + } + + public String getAddress() { + return address; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceConfiguration.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceConfiguration.java new file mode 100644 index 000000000..2287c3638 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceConfiguration.java @@ -0,0 +1,9 @@ +package gr.cite.notification.config.formatting; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(FormattingServiceProperties.class) +public class FormattingServiceConfiguration { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceProperties.java new file mode 100644 index 000000000..39f517869 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingServiceProperties.java @@ -0,0 +1,38 @@ +package gr.cite.notification.config.formatting; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "formatting.options") +public class FormattingServiceProperties { + + private final String integerFormat; + + private final String decimalFormat; + + private final String dateTimeFormat; + + private final Integer decimalDigitsRound; + + public FormattingServiceProperties(String integerFormat, String decimalFormat, String dateTimeFormat, Integer decimalDigitsRound) { + this.integerFormat = integerFormat; + this.decimalFormat = decimalFormat; + this.dateTimeFormat = dateTimeFormat; + this.decimalDigitsRound = decimalDigitsRound; + } + + public String getIntegerFormat() { + return integerFormat; + } + + public String getDecimalFormat() { + return decimalFormat; + } + + public String getDateTimeFormat() { + return dateTimeFormat; + } + + public Integer getDecimalDigitsRound() { + return decimalDigitsRound; + } +} \ No newline at end of file diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingUserprofileCacheOptions.java b/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingUserprofileCacheOptions.java new file mode 100644 index 000000000..9363efbd6 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/config/formatting/FormattingUserprofileCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.config.formatting; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.formatting-user-profile-cache") +public class FormattingUserprofileCacheOptions extends CacheOptions { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionService.java new file mode 100644 index 000000000..cfc2c3a02 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionService.java @@ -0,0 +1,41 @@ +package gr.cite.notification.convention; + +import gr.cite.tools.exception.MyApplicationException; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +public interface ConventionService { + Boolean isValidId(Integer id); + + Boolean isValidGuid(UUID guid); + + Boolean isValidUUID(String str); + UUID parseUUIDSafe(String str); + + Boolean isValidHash(String hash); + + String hashValue(Object value) throws MyApplicationException; + + String limit(String text, int maxLength); + + String truncate(String text, int maxLength); + + UUID getEmptyUUID(); + + boolean isNullOrEmpty(String value); + + boolean isListNullOrEmpty(List value); + + String stringEmpty(); + + String asPrefix(String name); + + String asIndexerPrefix(String part); + + String asIndexer(String... names); + + Map> toDictionaryOfList(List items, Function keySelector); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionServiceImpl.java new file mode 100644 index 000000000..2ae59083c --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/convention/ConventionServiceImpl.java @@ -0,0 +1,150 @@ +package gr.cite.notification.convention; + +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) +public class ConventionServiceImpl implements ConventionService { + private final static Pattern UUID_REGEX_PATTERN = Pattern.compile("^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$"); + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(ConventionServiceImpl.class)); + private final ErrorThesaurusProperties errors; + + @Autowired + public ConventionServiceImpl(ErrorThesaurusProperties errors) { + this.errors = errors; + } + + @Override + public Boolean isValidId(Integer id) { + return id != null && id > 0; + } + + @Override + public Boolean isValidGuid(UUID guid) { + return guid != null && !guid.equals(this.getEmptyUUID()); + } + + @Override + public Boolean isValidUUID(String str) { + if (this.isNullOrEmpty(str)) { + return false; + } + return UUID_REGEX_PATTERN.matcher(str).matches(); + } + + @Override + public UUID parseUUIDSafe(String str) { + if (!this.isValidUUID(str)) { + return null; + } + try { + return UUID.fromString(str); + } catch (Exception ex){ + logger.warn("invalid uuid" + str, ex); + return null; + } + } + + @Override + public Boolean isValidHash(String hash) { + return !this.isNullOrEmpty(hash); + } + + @Override + public String hashValue(Object value) throws MyApplicationException { + if (value == null) return this.stringEmpty(); + if (value instanceof Instant) return String.format("%ts", (Instant) value); + throw new MyApplicationException(this.errors.getSystemError().getCode(), this.errors.getSystemError().getMessage()); + } + + @Override + public String limit(String text, int maxLength) { + if (this.isNullOrEmpty(text)) return text; + if (text.length() > maxLength) return String.format("%s...", text.substring(0, maxLength)); + else return text; + } + + @Override + public String truncate(String text, int maxLength) { + String truncated = text; + if (text.length() < maxLength) return text; + + truncated = truncated.trim(); + truncated = truncated.replaceAll("\\s+", " ");//remove multiple spaces + if (truncated.length() < maxLength) return truncated; + truncated = truncated.replaceAll("([.!@#$%^&-=':;,<>?*\\\"/|])+", "");//remove multiple spaces + if (truncated.length() < maxLength) return truncated; + truncated = truncated.replaceAll("([aeiou])+", "");//remove multiple spaces + if (truncated.length() < maxLength) return truncated; + truncated = truncated.replaceAll("([AEIOU])+", "");//remove multiple spaces + if (truncated.length() < maxLength) return truncated; + + if (text.length() > maxLength) return String.format("%s...", text.substring(0, maxLength)); + return text; + + } + + @Override + public UUID getEmptyUUID() { + return new UUID(0L, 0L); + } + + @Override + public boolean isNullOrEmpty(String value) { + return value == null || value.isBlank(); + } + + @Override + public boolean isListNullOrEmpty(List value) { + if(value == null) return true; + return value.size() <= 0; + } + + @Override + public String stringEmpty() { + return ""; + } + + @Override + public String asPrefix(String name) { + if (name == null) return null; + return name + "."; + } + + @Override + public String asIndexer(String... names) { + if (names == null) return null; + return String.join(".", Arrays.stream(names).filter(x -> !this.isNullOrEmpty(x)).collect(Collectors.toList())); + } + + @Override + public String asIndexerPrefix(String part) { + if (part == null) return null; + return part + "."; + } + + @Override + public Map> toDictionaryOfList(List items, Function keySelector) { + Map> map = new HashMap<>(); + for (V model : items) { + K key = keySelector.apply(model); + if (!map.containsKey(key)) map.put(key, new ArrayList()); + map.get(key).add(model); + } + return map; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/LanguageEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/LanguageEntity.java new file mode 100644 index 000000000..d7ef04cec --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/LanguageEntity.java @@ -0,0 +1,94 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"Language\"") +public class LanguageEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "code", length = 20, nullable = false) + private String code; + + public static final String _code = "code"; + + @Column(name = "ordinal") + private Integer ordinal; + + public static final String _ordinal = "ordinal"; + + @Column(name = "\"created_at\"", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "\"updated_at\"", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Integer getOrdinal() { + return ordinal; + } + + public void setOrdinal(Integer ordinal) { + this.ordinal = ordinal; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueInboxEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueInboxEntity.java new file mode 100644 index 000000000..c59bccb11 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueInboxEntity.java @@ -0,0 +1,194 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.types.JsonSQLType; +import gr.cite.queueinbox.entity.QueueInbox; +import gr.cite.queueinbox.entity.QueueInboxStatus; +import jakarta.persistence.*; +import org.hibernate.annotations.Type; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"QueueInbox\"") +public class QueueInboxEntity implements QueueInbox { + + @Id + @Column(name = "\"id\"", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "\"queue\"", nullable = false, length = 50) + private String queue; + + public static final String _queue = "queue"; + + @Column(name = "\"exchange\"", nullable = false, length = 50) + private String exchange; + + public static final String _exchange = "exchange"; + + @Column(name = "\"route\"", nullable = false, length = 50) + private String route; + + public static final String _route = "route"; + + @Column(name = "\"application_id\"", nullable = false, length = 100) + private String applicationId; + + public static final String _applicationId = "applicationId"; + + @Column(name = "\"message_id\"", columnDefinition = "uuid", nullable = false) + private UUID messageId; + + public static final String _messageId = "messageId"; + + @Type(JsonSQLType.class) + @Column(name = "\"message\"", columnDefinition = "json", nullable = false) + private String message; + + public static final String _message = "message"; + + @Column(name = "\"retry_count\"") + private Integer retryCount; + + public static final String _retryCount = "retryCount"; + + @Column(name = "\"tenant\"", columnDefinition = "uuid") + private UUID tenantId; + + public static final String _tenantId = "tenantId"; + + @Column(name = "\"status\"", length = 50, nullable = false) + @Enumerated(EnumType.STRING) + private QueueInboxStatus status; + + public static final String _status = "status"; + + @Column(name = "\"created_at\"", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "\"updated_at\"", nullable = false) + @Version + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "\"is_active\"", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getQueue() { + return queue; + } + + public void setQueue(String queue) { + this.queue = queue; + } + + public String getExchange() { + return exchange; + } + + public void setExchange(String exchange) { + this.exchange = exchange; + } + + public String getRoute() { + return route; + } + + public void setRoute(String route) { + this.route = route; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public UUID getMessageId() { + return messageId; + } + + public void setMessageId(UUID messageId) { + this.messageId = messageId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + public QueueInboxStatus getStatus() { + return status; + } + + public void setStatus(QueueInboxStatus status) { + this.status = status; + } + + @Override + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java new file mode 100644 index 000000000..e62f2ec4d --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java @@ -0,0 +1,205 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.queueoutbox.entity.QueueOutbox; +import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"QueueOutbox\"") +public class QueueOutboxEntity implements QueueOutbox { + + @Id + @Column(name = "\"id\"", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "\"exchange\"", nullable = false, length = 50) + private String exchange; + + public static final String _exchange = "exchange"; + + @Column(name = "\"route\"", length = 50) + private String route; + + public static final String _route = "route"; + + @Column(name = "\"message_id\"", columnDefinition = "uuid", nullable = false) + private UUID messageId; + + public static final String _messageId = "messageId"; + + @Column(name = "\"message\"", columnDefinition = "json", nullable = false) + private String message; + + public static final String _message = "message"; + + @Column(name = "\"notify_status\"", length = 20, nullable = false) + @Enumerated(EnumType.STRING) + private QueueOutboxNotifyStatus notifyStatus; + + public static final String _notifyStatus = "notifyStatus"; + + @Column(name = "\"retry_count\"", nullable = false) + private Integer retryCount; + + public static final String _retryCount = "retryCount"; + + @Column(name = "\"published_at\"") + private Instant publishedAt; + + public static final String _publishedAt = "publishedAt"; + + @Column(name = "\"confirmed_at\"") + private Instant confirmedAt; + + public static final String _confirmedAt = "confirmedAt"; + + @Column(name = "\"tenant\"", columnDefinition = "uuid") + private UUID tenantId; + + public static final String _tenantId = "tenantId"; + + @Column(name = "\"created_at\"", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "\"updated_at\"", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "\"is_active\"", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + @Override + public UUID getId() { + return id; + } + + @Override + public void setId(UUID id) { + this.id = id; + } + + public String getExchange() { + return exchange; + } + + public void setExchange(String exchange) { + this.exchange = exchange; + } + + @Override + public String getRoute() { + return route; + } + + @Override + public void setRoute(String route) { + this.route = route; + } + + @Override + public UUID getMessageId() { + return messageId; + } + + @Override + public void setMessageId(UUID messageId) { + this.messageId = messageId; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public void setMessage(String message) { + this.message = message; + } + + @Override + public QueueOutboxNotifyStatus getNotifyStatus() { + return notifyStatus; + } + + @Override + public void setNotifyStatus(QueueOutboxNotifyStatus notifyStatus) { + this.notifyStatus = notifyStatus; + } + + @Override + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + @Override + public Instant getPublishedAt() { + return publishedAt; + } + + @Override + public void setPublishedAt(Instant publishedAt) { + this.publishedAt = publishedAt; + } + + @Override + public Instant getConfirmedAt() { + return confirmedAt; + } + + @Override + public void setConfirmedAt(Instant confirmedAt) { + this.confirmedAt = confirmedAt; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + @Override + public Instant getCreatedAt() { + return createdAt; + } + + @Override + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java new file mode 100644 index 000000000..44c8d6030 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java @@ -0,0 +1,113 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.conventers.TenantConfigurationTypeConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"TenantConfiguration\"") +public class TenantConfigurationEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "type", nullable = false) + @Convert(converter = TenantConfigurationTypeConverter.class) + private TenantConfigurationType type; + + public static final String _type = "type"; + + @Column(name = "value", nullable = false) + private String value; + + public static final String _value = "value"; + + @Column(name = "\"tenant\"", columnDefinition = "uuid") + private UUID tenantId; + + public static final String _tenantId = "tenantId"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public TenantConfigurationType getType() { + return type; + } + + public void setType(TenantConfigurationType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public UUID getTenantId() { + return tenantId; + } + + @Override + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantEntity.java new file mode 100644 index 000000000..f893aaa8c --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantEntity.java @@ -0,0 +1,81 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"Tenant\"") +public class TenantEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "code", length = 200, nullable = false) + private String code; + + public static final String _code = "code"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "is_active", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java new file mode 100644 index 000000000..a6580bbbe --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java @@ -0,0 +1,97 @@ +package gr.cite.notification.data; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.common.scope.tenant.TenantScoped; +import gr.cite.tools.exception.MyForbiddenException; +import jakarta.persistence.EntityManager; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; +import org.hibernate.Session; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +@Service +@Scope +public class TenantScopedEntityManager { + @PersistenceContext + private EntityManager entityManager; + private final AuthorizationService authorizationService; + + private final TenantScope tenantScope; + + public TenantScopedEntityManager(AuthorizationService authorizationService, TenantScope tenantScope) { + this.authorizationService = authorizationService; + this.tenantScope = tenantScope; + } + + public int getBulkSize() { + Session session = this.entityManager.unwrap(Session.class); + return session.getJdbcBatchSize(); + } + + public void setBulkSize(int size) { + Session session = this.entityManager.unwrap(Session.class); + session.setJdbcBatchSize(size); + } + public Query createQuery(String query){ + return this.entityManager.createQuery(query); + } + public void persist(Object entity) { + this.entityManager.persist(entity); + } + + public T merge(T entity) throws InvalidApplicationException { + if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) { + boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant); + + final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null; + if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering"); + } + return this.entityManager.merge(entity); + } + + public void remove(Object entity) throws InvalidApplicationException { + if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) { + final UUID tenantId = tenantScope.getTenant(); + if (!tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering"); + } + this.entityManager.remove(entity); + } + + public T find(Class entityClass, Object primaryKey) throws InvalidApplicationException { + T entity = this.entityManager.find(entityClass, primaryKey); + + if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) { + boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant); + + final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null; + if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) return null; + } + return entity; + } + + public void flush() { + this.entityManager.flush(); + } + + + public void setFlushMode(FlushModeType flushMode) { + this.entityManager.setFlushMode(flushMode); + + } + + public FlushModeType getFlushMode() { + return this.entityManager.getFlushMode(); + } + + public void clear() { + this.entityManager.clear(); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantUserEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantUserEntity.java new file mode 100644 index 000000000..3ff3e610e --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/TenantUserEntity.java @@ -0,0 +1,81 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"TenantUser\"") +public class TenantUserEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "user", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID userId; + + public static final String _userId = "userId"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java new file mode 100644 index 000000000..bfd2acc6b --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java @@ -0,0 +1,125 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.ContactInfoType; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.ContactInfoTypeConverter; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"UserContactInfo\"") +public class UserContactInfoEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "\"user\"", nullable = false) + private UUID userId; + + public static final String _userId = "userId"; + + @Column(name = "\"ordinal\"", nullable = false) + private Integer ordinal; + + public static final String _ordinal = "ordinal"; + + @Column(name = "type", length = 100, nullable = false) + @Convert(converter = ContactInfoTypeConverter.class) + private ContactInfoType type; + + public static final String _type = "type"; + + @Column(name = "value", nullable = false) + private String value; + + public static final String _value = "value"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "\"is_active\"", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public ContactInfoType getType() { + return type; + } + + public void setType(ContactInfoType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Integer getOrdinal() { + return ordinal; + } + + public void setOrdinal(Integer ordinal) { + this.ordinal = ordinal; + } + + public void setValue(String value) { + this.value = value; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserCredentialEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserCredentialEntity.java new file mode 100644 index 000000000..51470a896 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserCredentialEntity.java @@ -0,0 +1,97 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"UserCredential\"") +public class UserCredentialEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "\"user\"", columnDefinition = "uuid", nullable = false) + private UUID userId; + + public static final String _userId = "userId"; + + @Column(name = "\"external_id\"", length = UserCredentialEntity._externalIdLength, nullable = false) + private String externalId; + + public static final String _externalId = "externalId"; + + public static final int _externalIdLength = 512; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "\"is_active\"", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserEntity.java new file mode 100644 index 000000000..477695f65 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserEntity.java @@ -0,0 +1,96 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"User\"") +public class UserEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "name", length = UserEntity._nameLength) + private String name = null; + + public static final String _name = "name"; + + public static final int _nameLength = 250; + + @Column(name = "additional_info") + private String additionalInfo; + + public static final String _additionalInfo = "additionalInfo"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(String additionalInfo) { + this.additionalInfo = additionalInfo; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserRoleEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserRoleEntity.java new file mode 100644 index 000000000..ba3e9baf1 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/UserRoleEntity.java @@ -0,0 +1,97 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import jakarta.persistence.*; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"UserRole\"") +public class UserRoleEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + public static final String _id = "id"; + + @Column(name = "role", length = UserRoleEntity._roleLength, nullable = false) + private String role; + + public static final String _role = "role"; + + public static final int _roleLength = 512; + + @Column(name = "\"user\"", nullable = false) + private UUID userId; + + public static final String _userId = "userId"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + @Column(name = "\"is_active\"", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java new file mode 100644 index 000000000..15d74a2f6 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java @@ -0,0 +1,11 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.ContactInfoType; +import jakarta.persistence.Converter; + +@Converter +public class ContactInfoTypeConverter extends DatabaseEnumConverter{ + protected ContactInfoType of(Short i) { + return ContactInfoType.of(i); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnum.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnum.java new file mode 100644 index 000000000..4dd378c02 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnum.java @@ -0,0 +1,8 @@ +package gr.cite.notification.data.conventers; + +import com.fasterxml.jackson.annotation.JsonValue; + +public interface DatabaseEnum { + @JsonValue + T getValue(); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java new file mode 100644 index 000000000..6cb0dd82c --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java @@ -0,0 +1,21 @@ +package gr.cite.notification.data.conventers; + + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public abstract class DatabaseEnumConverter, T> implements AttributeConverter { + protected abstract EnumType of(T dbData); + + @Override + public T convertToDatabaseColumn(EnumType value) { + if (value == null) throw new IllegalArgumentException("value"); + return value.getValue(); + } + + @Override + public EnumType convertToEntityAttribute(T dbData) { + return this.of(dbData); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java new file mode 100644 index 000000000..0374a9a57 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java @@ -0,0 +1,11 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.IsActive; +import jakarta.persistence.Converter; + +@Converter +public class IsActiveConverter extends DatabaseEnumConverter { + public IsActive of(Short i) { + return IsActive.of(i); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java new file mode 100644 index 000000000..4c06472e0 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java @@ -0,0 +1,11 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.TenantConfigurationType; +import jakarta.persistence.Converter; + +@Converter +public class TenantConfigurationTypeConverter extends DatabaseEnumConverter { + public TenantConfigurationType of(Short i) { + return TenantConfigurationType.of(i); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantFilterAspect.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantFilterAspect.java new file mode 100644 index 000000000..d48cea78c --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantFilterAspect.java @@ -0,0 +1,49 @@ +package gr.cite.notification.data.tenant; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.notification.common.scope.tenant.TenantScope; +import jakarta.persistence.EntityManager; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.hibernate.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; + +@Aspect +@Component +public class TenantFilterAspect { + + private final TenantScope tenantScope; + private final ClaimExtractor claimExtractor; + private final CurrentPrincipalResolver currentPrincipalResolver; + private final ApplicationContext applicationContext; + + @Autowired + public TenantFilterAspect( + TenantScope tenantScope, + ClaimExtractor claimExtractor, + CurrentPrincipalResolver currentPrincipalResolver, + ApplicationContext applicationContext + ) { + this.tenantScope = tenantScope; + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractor = claimExtractor; + this.applicationContext = applicationContext; + } + + @AfterReturning( + pointcut="bean(entityManagerFactory) && execution(* createEntityManager(..))", + returning="retVal") + public void getSessionAfter(JoinPoint joinPoint, Object retVal) throws InvalidApplicationException { + if (retVal != null && EntityManager.class.isInstance(retVal) && tenantScope.isSet() && tenantScope.isMultitenant()) { + Session session = ((EntityManager) retVal).unwrap(Session.class); + session.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString()); + } + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantListener.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantListener.java new file mode 100644 index 000000000..76cfef99e --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantListener.java @@ -0,0 +1,59 @@ +package gr.cite.notification.data.tenant; + +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.common.scope.tenant.TenantScoped; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreRemove; +import jakarta.persistence.PreUpdate; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +public class TenantListener { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantListener.class)); + private final TenantScope tenantScope; + + @Autowired + public TenantListener( + TenantScope tenantScope + ) { + this.tenantScope = tenantScope; + } + + @PrePersist + public void setTenantOnCreate(TenantScoped entity) throws InvalidApplicationException { + if (tenantScope.isMultitenant()) { + final UUID tenantId = tenantScope.getTenant(); + entity.setTenantId(tenantId); + } else { + entity.setTenantId(null); + } + } + + @PreUpdate + @PreRemove + public void setTenantOnUpdate(TenantScoped entity) throws InvalidApplicationException { + if (tenantScope.isMultitenant()) { + if (entity.getTenantId() == null) { + logger.error("somebody tried to set null tenant"); + throw new MyForbiddenException("tenant tampering"); + } + if (entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) { + logger.error("somebody tried to change an entries tenant"); + throw new MyForbiddenException("tenant tampering"); + } + + final UUID tenantId = tenantScope.getTenant(); + entity.setTenantId(tenantId); + } else { + if (entity.getTenantId() != null) { + logger.error("somebody tried to set non null tenant"); + throw new MyForbiddenException("tenant tampering"); + } + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java new file mode 100644 index 000000000..79728dc37 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java @@ -0,0 +1,45 @@ +package gr.cite.notification.data.tenant; + +import gr.cite.notification.common.scope.tenant.TenantScoped; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.ParamDef; + +import java.io.Serial; +import java.io.Serializable; +import java.util.UUID; + +@MappedSuperclass +//@Getter +//@Setter +//@NoArgsConstructor +@FilterDef(name = TenantScopedBaseEntity.tenantFilter, parameters = {@ParamDef(name = TenantScopedBaseEntity.tenantFilterTenantParam, type = String.class)}) +@Filter(name = "tenantFilter", condition = "tenant = (cast(:tenantId as uuid))") +@EntityListeners(TenantListener.class) +public abstract class TenantScopedBaseEntity implements TenantScoped, Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + public static final String tenantFilter = "tenantFilter"; + + public static final String tenantFilterTenantParam = "tenantId"; + + @Column(name = "tenant", columnDefinition = "uuid") + private UUID tenantId; + + public static final String _tenantId = "tenantId"; + + public UUID getTenantId() { + return tenantId; + } + + @Override + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/data/types/JsonSQLType.java b/annotation-service/annotation/src/main/java/gr/cite/notification/data/types/JsonSQLType.java new file mode 100644 index 000000000..434f8eda1 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/data/types/JsonSQLType.java @@ -0,0 +1,80 @@ +package gr.cite.notification.data.types; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.SqlTypes; +import org.hibernate.usertype.UserType; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +public class JsonSQLType implements UserType { + + @Override + public int getSqlType() { + return SqlTypes.JSON; + } + + @Override + public Class returnedClass() { + return String.class; + } + + @Override + public boolean equals(String x, String y) { + if (x == null) { + return y == null; + } else { + return x.equals(y); + } + } + + @Override + public int hashCode(String x) { + return x.hashCode(); + } + + @Override + public String nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException { + String json = rs.getString(position); + return rs.wasNull() ? null : json; + } + + @Override + public void nullSafeSet(PreparedStatement st, String value, int index, SharedSessionContractImplementor session) throws SQLException { + if (value == null) { + st.setNull(index, Types.OTHER); + } else { + st.setObject(index, value, Types.OTHER); + } + } + + @Override + public String deepCopy(String value) throws HibernateException { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(String value) throws HibernateException { + return value; + } + + @Override + public String assemble(Serializable cached, Object owner) throws HibernateException { + return (String) cached; + } + + @Override + public String replace(String original, String target, Object owner) throws HibernateException { + return original; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorDescription.java b/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorDescription.java new file mode 100644 index 000000000..eb1279ae6 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorDescription.java @@ -0,0 +1,22 @@ +package gr.cite.notification.errorcode; + +public class ErrorDescription { + private int code; + private String message; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusConfiguration.java b/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusConfiguration.java new file mode 100644 index 000000000..08adb6f11 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusConfiguration.java @@ -0,0 +1,20 @@ +package gr.cite.notification.errorcode; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(ErrorThesaurusProperties.class) +public class ErrorThesaurusConfiguration { + +// private final ErrorThesaurusProperties properties; +// +// @Autowired +// public ErrorThesaurusConfiguration(ErrorThesaurusProperties properties) { +// this.properties = properties; +// } +// +// public ErrorThesaurusProperties getProperties() { +// return properties; +// } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusProperties.java new file mode 100644 index 000000000..d19b62512 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusProperties.java @@ -0,0 +1,92 @@ +package gr.cite.notification.errorcode; + + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "error-thesaurus") +public class ErrorThesaurusProperties { + + private ErrorDescription hashConflict; + private ErrorDescription forbidden; + private ErrorDescription systemError; + private ErrorDescription missingTenant; + private ErrorDescription modelValidation; + private ErrorDescription nonPersonPrincipal; + private ErrorDescription singleTenantConfigurationPerTypeSupported; + private ErrorDescription incompatibleTenantConfigurationTypes; + private ErrorDescription overlappingTenantConfigurationNotifierList; + + public ErrorDescription getHashConflict() { + return hashConflict; + } + + public void setHashConflict(ErrorDescription hashConflict) { + this.hashConflict = hashConflict; + } + + public ErrorDescription getForbidden() { + return forbidden; + } + + public void setForbidden(ErrorDescription forbidden) { + this.forbidden = forbidden; + } + + public ErrorDescription getSystemError() { + return systemError; + } + + public void setSystemError(ErrorDescription systemError) { + this.systemError = systemError; + } + + public ErrorDescription getMissingTenant() { + return missingTenant; + } + + public void setMissingTenant(ErrorDescription missingTenant) { + this.missingTenant = missingTenant; + } + + public ErrorDescription getModelValidation() { + return modelValidation; + } + + public void setModelValidation(ErrorDescription modelValidation) { + this.modelValidation = modelValidation; + } + + public ErrorDescription getNonPersonPrincipal() { + return nonPersonPrincipal; + } + + public void setNonPersonPrincipal(ErrorDescription nonPersonPrincipal) { + this.nonPersonPrincipal = nonPersonPrincipal; + } + + + public ErrorDescription getSingleTenantConfigurationPerTypeSupported() { + return singleTenantConfigurationPerTypeSupported; + } + + public void setSingleTenantConfigurationPerTypeSupported(ErrorDescription singleTenantConfigurationPerTypeSupported) { + this.singleTenantConfigurationPerTypeSupported = singleTenantConfigurationPerTypeSupported; + } + + public ErrorDescription getIncompatibleTenantConfigurationTypes() { + return incompatibleTenantConfigurationTypes; + } + + public void setIncompatibleTenantConfigurationTypes(ErrorDescription incompatibleTenantConfigurationTypes) { + this.incompatibleTenantConfigurationTypes = incompatibleTenantConfigurationTypes; + } + + public ErrorDescription getOverlappingTenantConfigurationNotifierList() { + return overlappingTenantConfigurationNotifierList; + } + + public void setOverlappingTenantConfigurationNotifierList(ErrorDescription overlappingTenantConfigurationNotifierList) { + this.overlappingTenantConfigurationNotifierList = overlappingTenantConfigurationNotifierList; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/event/EventBroker.java b/annotation-service/annotation/src/main/java/gr/cite/notification/event/EventBroker.java new file mode 100644 index 000000000..3bc5a14b8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/event/EventBroker.java @@ -0,0 +1,37 @@ +package gr.cite.notification.event; + +import gr.cite.commons.web.oidc.apikey.events.ApiKeyStaleEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +@Service +public class EventBroker { + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + public void emitApiKeyStale(String apiKey) { + this.applicationEventPublisher.publishEvent(new ApiKeyStaleEvent(apiKey)); + } + + public void emit(ApiKeyStaleEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + + public void emit(TenantTouchedEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + + public void emit(UserTouchedEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + + public void emit(UserAddedToTenantEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + + public void emit(UserRemovedFromTenantEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/event/TenantTouchedEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/event/TenantTouchedEvent.java new file mode 100644 index 000000000..29be1cb7e --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/event/TenantTouchedEvent.java @@ -0,0 +1,42 @@ +package gr.cite.notification.event; + +import java.util.UUID; + +public class TenantTouchedEvent { + public TenantTouchedEvent() { + } + + public TenantTouchedEvent(UUID tenantId, String tenantCode, String previousTenantCode) { + this.tenantId = tenantId; + this.tenantCode = tenantCode; + this.previousTenantCode = previousTenantCode; + } + + private UUID tenantId; + private String tenantCode; + private String previousTenantCode; + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + public String getPreviousTenantCode() { + return previousTenantCode; + } + + public void setPreviousTenantCode(String previousTenantCode) { + this.previousTenantCode = previousTenantCode; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserAddedToTenantEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserAddedToTenantEvent.java new file mode 100644 index 000000000..c6c0c0f58 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserAddedToTenantEvent.java @@ -0,0 +1,32 @@ +package gr.cite.notification.event; + +import java.util.UUID; + +public class UserAddedToTenantEvent { + public UserAddedToTenantEvent() { + } + + public UserAddedToTenantEvent(UUID userId, UUID tenantId) { + this.userId = userId; + this.tenantId = tenantId; + } + + private UUID userId; + private UUID tenantId; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserRemovedFromTenantEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserRemovedFromTenantEvent.java new file mode 100644 index 000000000..6a175e34f --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserRemovedFromTenantEvent.java @@ -0,0 +1,32 @@ +package gr.cite.notification.event; + +import java.util.UUID; + +public class UserRemovedFromTenantEvent { + public UserRemovedFromTenantEvent() { + } + + public UserRemovedFromTenantEvent(UUID userId, UUID tenantId) { + this.userId = userId; + this.tenantId = tenantId; + } + + private UUID userId; + private UUID tenantId; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserTouchedEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserTouchedEvent.java new file mode 100644 index 000000000..44b83cd9a --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/event/UserTouchedEvent.java @@ -0,0 +1,42 @@ +package gr.cite.notification.event; + +import java.util.UUID; + +public class UserTouchedEvent { + public UserTouchedEvent() { + } + + public UserTouchedEvent(UUID userId, String subjectId, String previousSubjectId) { + this.userId = userId; + this.subjectId = subjectId; + this.previousSubjectId = previousSubjectId; + } + + private UUID userId; + private String subjectId; + private String previousSubjectId; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getSubjectId() { + return subjectId; + } + + public void setSubjectId(String subjectId) { + this.subjectId = subjectId; + } + + public String getPreviousSubjectId() { + return previousSubjectId; + } + + public void setPreviousSubjectId(String previousSubjectId) { + this.previousSubjectId = previousSubjectId; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/AppRabbitConfigurer.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/AppRabbitConfigurer.java new file mode 100644 index 000000000..a0e003cb9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/AppRabbitConfigurer.java @@ -0,0 +1,52 @@ +package gr.cite.notification.integrationevent; + +import gr.cite.notification.integrationevent.inbox.InboxProperties; +import gr.cite.notification.integrationevent.outbox.OutboxProperties; +import gr.cite.queueinbox.repository.InboxRepository; +import gr.cite.rabbitmq.RabbitConfigurer; +import gr.cite.rabbitmq.consumer.InboxBindings; +import gr.cite.rabbitmq.consumer.InboxCreator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@EnableConfigurationProperties({OutboxProperties.class, InboxProperties.class}) +@ConditionalOnProperty(prefix = "queue.rabbitmq", name = "listenerEnabled") +public class AppRabbitConfigurer extends RabbitConfigurer { + + private final ApplicationContext applicationContext; + + private final InboxProperties inboxProperties; + + public AppRabbitConfigurer(ApplicationContext applicationContext, InboxProperties inboxProperties) { + this.applicationContext = applicationContext; + this.inboxProperties = inboxProperties; + } + + @Bean + public InboxBindings inboxBindingsCreator() { + List bindingItems = new ArrayList<>(); + bindingItems.addAll(this.inboxProperties.getNotifyTopic()); + bindingItems.addAll(this.inboxProperties.getTenantRemovalTopic()); + bindingItems.addAll(this.inboxProperties.getTenantTouchedTopic()); + bindingItems.addAll(this.inboxProperties.getUserRemovalTopic()); + bindingItems.addAll(this.inboxProperties.getUserTouchedTopic()); + + return new InboxBindings(bindingItems); + } + + @Bean(name = "InboxCreator") + public InboxCreator inboxCreator() { + return (params) -> { + InboxRepository inboxRepository = this.applicationContext.getBean(InboxRepository.class); + return inboxRepository.create(params) != null; + }; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/InboxIntegrationEventConfigurer.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/InboxIntegrationEventConfigurer.java new file mode 100644 index 000000000..998ce70f3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/InboxIntegrationEventConfigurer.java @@ -0,0 +1,33 @@ +package gr.cite.notification.integrationevent; + +import gr.cite.notification.integrationevent.inbox.InboxProperties; +import gr.cite.notification.integrationevent.inbox.InboxRepositoryImpl; +import gr.cite.queueinbox.InboxConfigurer; +import gr.cite.queueinbox.repository.InboxRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({InboxProperties.class}) +@ConditionalOnProperty(prefix = "queue.task.listener", name = "enable", matchIfMissing = false) +public class InboxIntegrationEventConfigurer extends InboxConfigurer { + + private ApplicationContext applicationContext; + + private InboxProperties inboxProperties; + + public InboxIntegrationEventConfigurer(ApplicationContext applicationContext, InboxProperties inboxProperties) { + this.applicationContext = applicationContext; + this.inboxProperties = inboxProperties; + } + + @Bean + public InboxRepository inboxRepositoryCreator() { + return new InboxRepositoryImpl(this.applicationContext, this.inboxProperties); + } + +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/IntegrationEventContextImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/IntegrationEventContextImpl.java new file mode 100644 index 000000000..fd8c02dc3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/IntegrationEventContextImpl.java @@ -0,0 +1,24 @@ +package gr.cite.notification.integrationevent; + +import gr.cite.rabbitmq.IntegrationEventContext; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +public class IntegrationEventContextImpl implements IntegrationEventContext { + + private UUID tenant; + + public IntegrationEventContextImpl() { + } + + public UUID getTenant() { + return tenant; + } + + public void setTenant(UUID tenant) { + this.tenant = tenant; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/OutboxIntegrationEventConfigurer.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/OutboxIntegrationEventConfigurer.java new file mode 100644 index 000000000..70ebc5361 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/OutboxIntegrationEventConfigurer.java @@ -0,0 +1,68 @@ +package gr.cite.notification.integrationevent; + +import gr.cite.notification.integrationevent.outbox.OutboxProperties; +import gr.cite.notification.integrationevent.outbox.OutboxRepositoryImpl; +import gr.cite.queueoutbox.IntegrationEventContextCreator; +import gr.cite.queueoutbox.OutboxConfigurer; +import gr.cite.queueoutbox.repository.OutboxRepository; +import gr.cite.rabbitmq.IntegrationEventMessageConstants; +import gr.cite.rabbitmq.RabbitProperties; +import gr.cite.rabbitmq.broker.MessageHydrator; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Date; +import java.util.UUID; + +@Configuration +@EnableConfigurationProperties({OutboxProperties.class}) +@ConditionalOnProperty(prefix = "queue.task.publisher", name = "enable", matchIfMissing = false) +public class OutboxIntegrationEventConfigurer extends OutboxConfigurer { + private ApplicationContext applicationContext; + private OutboxProperties outboxProperties; + + public OutboxIntegrationEventConfigurer(ApplicationContext applicationContext, OutboxProperties outboxProperties) { + this.applicationContext = applicationContext; + this.outboxProperties = outboxProperties; + } + + @Bean + public MessageHydrator messageHydrator(RabbitProperties rabbitProperties) { + return (message, event, eventContext) -> { + MessageProperties messageProperties = message.getMessageProperties(); + messageProperties.setAppId(rabbitProperties.getAppId()); + messageProperties.setContentEncoding(StandardCharsets.UTF_8.displayName()); + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + //messageProperties.setUserId(userContext.getCurrentUser().toString()); + messageProperties.setTimestamp(Date.from(Instant.now())); + messageProperties.setMessageId(event.getMessageId().toString()); + + if (eventContext != null) { + UUID tenant = ((IntegrationEventContextImpl) eventContext).getTenant(); + if (tenant != null) { + messageProperties.setHeader(IntegrationEventMessageConstants.TENANT, tenant); + } + } + + return message; + }; + } + + @Bean + public IntegrationEventContextCreator integrationEventContextCreator() { + return (message) -> new IntegrationEventContextImpl(); + } + + @Bean + public OutboxRepository outboxRepositoryCreator() { + return new OutboxRepositoryImpl(this.applicationContext, this.outboxProperties); + } +} + + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/TrackedEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/TrackedEvent.java new file mode 100644 index 000000000..c5ecb2542 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/TrackedEvent.java @@ -0,0 +1,18 @@ +package gr.cite.notification.integrationevent; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TrackedEvent { + + public String trackingContextTag; + + public String getTrackingContextTag() { + return trackingContextTag; + } + + public void setTrackingContextTag(String trackingContextTag) { + this.trackingContextTag = trackingContextTag; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyHandler.java new file mode 100644 index 000000000..78f4f7f67 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyHandler.java @@ -0,0 +1,7 @@ +package gr.cite.notification.integrationevent.inbox; + +public interface ConsistencyHandler { + + Boolean isConsistent(T consistencyPredicates); + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyPredicates.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyPredicates.java new file mode 100644 index 000000000..e77c546a2 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/ConsistencyPredicates.java @@ -0,0 +1,5 @@ +package gr.cite.notification.integrationevent.inbox; + +public interface ConsistencyPredicates { + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/EventProcessingStatus.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/EventProcessingStatus.java new file mode 100644 index 000000000..274049473 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/EventProcessingStatus.java @@ -0,0 +1,8 @@ +package gr.cite.notification.integrationevent.inbox; + +public enum EventProcessingStatus { + Error, + Success, + Postponed, + Discard +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxPrincipal.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxPrincipal.java new file mode 100644 index 000000000..9fbc1521e --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxPrincipal.java @@ -0,0 +1,60 @@ +package gr.cite.notification.integrationevent.inbox; + +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import org.springframework.security.oauth2.core.ClaimAccessor; +import org.springframework.security.oauth2.jwt.JwtClaimNames; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InboxPrincipal implements MyPrincipal, ClaimAccessor { + + private final Map claims; + + private final boolean isAuthenticated; + + public InboxPrincipal(Boolean isAuthenticated, String name) { + this.claims = new HashMap<>(); + this.put(JwtClaimNames.SUB, name); + this.isAuthenticated = isAuthenticated; + } + + public static InboxPrincipal build(IntegrationEventProperties properties) { + InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId"); + inboxPrincipal.put("client_id", properties.getAppId()); + inboxPrincipal.put("active", "true"); + inboxPrincipal.put("nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString()); + inboxPrincipal.put("exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString()); + return inboxPrincipal; + } + + @Override + public Boolean isAuthenticated() { + return this.isAuthenticated; + } + + @Override + public Map getClaims() { + return this.claims; + } + + @Override + public List getClaimAsStringList(String claim) { + if (claims == null) + return null; + return this.getClaimAsStringList(claim); + } + + @Override + public String getName() { + return this.getClaimAsString(JwtClaimNames.SUB); + } + + public void put(String key, Object value) { + this.claims.put(key, value); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxProperties.java new file mode 100644 index 000000000..18e14699b --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxProperties.java @@ -0,0 +1,61 @@ +package gr.cite.notification.integrationevent.inbox; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +@ConfigurationProperties(prefix = "queue.task.listener.options") +public class InboxProperties { + + private final String exchange; + + private final List notifyTopic; + + private final List tenantRemovalTopic; + + private final List tenantTouchedTopic; + + private final List userRemovalTopic; + + private final List userTouchedTopic; + + public InboxProperties( + String exchange, + List notifyTopic, + List tenantRemovalTopic, + List tenantTouchedTopic, + List userRemovalTopic, + List userTouchedTopic) { + this.exchange = exchange; + this.notifyTopic = notifyTopic; + this.tenantRemovalTopic = tenantRemovalTopic; + this.tenantTouchedTopic = tenantTouchedTopic; + this.userRemovalTopic = userRemovalTopic; + this.userTouchedTopic = userTouchedTopic; + } + + public List getNotifyTopic() { + return notifyTopic; + } + + public List getTenantRemovalTopic() { + return tenantRemovalTopic; + } + + public List getTenantTouchedTopic() { + return tenantTouchedTopic; + } + + public List getUserRemovalTopic() { + return userRemovalTopic; + } + + public List getUserTouchedTopic() { + return userTouchedTopic; + } + + public String getExchange() { + return exchange; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxRepositoryImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxRepositoryImpl.java new file mode 100644 index 000000000..6205c99ca --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/InboxRepositoryImpl.java @@ -0,0 +1,363 @@ +package gr.cite.notification.integrationevent.inbox; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.data.QueueInboxEntity; +import gr.cite.notification.integrationevent.TrackedEvent; +import gr.cite.notification.integrationevent.inbox.tenantremoval.TenantRemovalIntegrationEventHandler; +import gr.cite.notification.integrationevent.inbox.tenanttouched.TenantTouchedIntegrationEventHandler; +import gr.cite.notification.integrationevent.inbox.userremoval.UserRemovalIntegrationEventHandler; +import gr.cite.notification.integrationevent.inbox.usertouched.UserTouchedIntegrationEventHandler; +import gr.cite.notification.query.QueueInboxQuery; +import gr.cite.queueinbox.entity.QueueInbox; +import gr.cite.queueinbox.entity.QueueInboxStatus; +import gr.cite.queueinbox.repository.CandidateInfo; +import gr.cite.queueinbox.repository.InboxRepository; +import gr.cite.queueinbox.task.MessageOptions; +import gr.cite.rabbitmq.consumer.InboxCreatorParams; +import gr.cite.tools.data.query.Ordering; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +public class InboxRepositoryImpl implements InboxRepository { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InboxRepositoryImpl.class)); + + protected final ApplicationContext applicationContext; + + private final JsonHandlingService jsonHandlingService; + + private final InboxProperties inboxProperties; + + public InboxRepositoryImpl( + ApplicationContext applicationContext, + InboxProperties inboxProperties + ) { + this.applicationContext = applicationContext; + this.jsonHandlingService = this.applicationContext.getBean(JsonHandlingService.class); + this.inboxProperties = inboxProperties; + } + + @Override + public CandidateInfo candidate(Instant lastCandidateCreationTimestamp, MessageOptions options) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + CandidateInfo candidate = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + QueueInboxEntity item = queryFactory.query(QueueInboxQuery.class) + .isActives(IsActive.Active) + .status(QueueInboxStatus.PENDING, QueueInboxStatus.ERROR) + .retryThreshold(options.getRetryThreashold()) + .createdAfter(lastCandidateCreationTimestamp) + .ordering(new Ordering().addAscending(QueueInboxEntity._createdAt)) + .first(); + + if (item != null) { + QueueInboxStatus prevState = item.getStatus(); + item.setStatus(QueueInboxStatus.PROCESSING); + + entityManager.merge(item); + entityManager.flush(); + + candidate = new CandidateInfo(); + candidate.setId(item.getId()); + candidate.setCreatedAt(item.getCreatedAt()); + candidate.setPreviousState(prevState); + } + + transaction.commit(); + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue inbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) + transaction.rollback(); + candidate = null; + } catch (Exception ex) { + logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) + transaction.rollback(); + candidate = null; + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex); + } + + return candidate; + } + + @Override + public Boolean shouldOmit(CandidateInfo candidate, Function shouldOmit) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + QueueInboxEntity item = queryFactory.query(QueueInboxQuery.class).ids(candidate.getId()).first(); + + if (item == null) { + logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidate.getId()); + } else { + if (shouldOmit.apply(item)) { + item.setStatus(QueueInboxStatus.OMITTED); + + entityManager.merge(item); + entityManager.flush(); + success = true; + } + } + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) + transaction.rollback(); + success = false; + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return success; + } + + @Override + public boolean shouldWait(CandidateInfo candidate, Function itIsTimeFunc) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + QueueInboxEntity item = queryFactory.query(QueueInboxQuery.class).ids(candidate.getId()).first(); + + if (item.getRetryCount() != null && item.getRetryCount() >= 1) { + Boolean itIsTime = itIsTimeFunc.apply(item); + + if (!itIsTime) { + item.setStatus(candidate.getPreviousState()); + + entityManager.merge(item); + entityManager.flush(); + success = true; + } + + success = !itIsTime; + } + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) + transaction.rollback(); + success = false; + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return success; + } + + @Override + public QueueInbox create(InboxCreatorParams inboxCreatorParams) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + QueueInboxEntity queueMessage = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + queueMessage = this.createQueueInboxEntity(inboxCreatorParams); + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + + transaction.begin(); + + entityManager.persist(queueMessage); + entityManager.flush(); + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) + transaction.rollback(); + success = false; + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return queueMessage; + } + + private QueueInboxEntity createQueueInboxEntity(InboxCreatorParams inboxCreatorParams) { + + QueueInboxEntity queueMessage = new QueueInboxEntity(); + queueMessage.setId(UUID.randomUUID()); + queueMessage.setTenantId(null); + queueMessage.setExchange(this.inboxProperties.getExchange()); + queueMessage.setRoute(inboxCreatorParams.getRoutingKey()); + queueMessage.setQueue(inboxCreatorParams.getQueueName()); + queueMessage.setApplicationId(inboxCreatorParams.getAppId()); + queueMessage.setMessageId(UUID.fromString(inboxCreatorParams.getMessageId())); + queueMessage.setMessage(inboxCreatorParams.getMessageBody()); + queueMessage.setIsActive(IsActive.Active); + queueMessage.setStatus(QueueInboxStatus.PENDING); + queueMessage.setRetryCount(0); + queueMessage.setCreatedAt(Instant.now()); + + return queueMessage; + } + + @Override + public Boolean emit(CandidateInfo candidateInfo) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + QueueInboxEntity queueInboxMessage = queryFactory.query(QueueInboxQuery.class).ids(candidateInfo.getId()).first(); + + if (queueInboxMessage == null) { + logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidateInfo.getId()); + } else { + + EventProcessingStatus status = this.processMessage(queueInboxMessage.getRoute(), queueInboxMessage.getMessageId().toString(), queueInboxMessage.getApplicationId(), queueInboxMessage.getMessage()); + switch (status) { + case Success: { + queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL); + break; + } + case Postponed: { + queueInboxMessage.setStatus(QueueInboxStatus.PARKED); + break; + } + case Error: { + queueInboxMessage.setStatus(QueueInboxStatus.ERROR); + queueInboxMessage.setRetryCount(queueInboxMessage.getRetryCount() != null ? queueInboxMessage.getRetryCount() + 1 : 0); + break; + } + case Discard: + default: { + queueInboxMessage.setStatus(QueueInboxStatus.DISCARD); + break; + } + } + success = status == EventProcessingStatus.Success; + + entityManager.merge(queueInboxMessage); + entityManager.flush(); + } + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) + transaction.rollback(); + success = false; + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return success; + } + + private EventProcessingStatus processMessage(String routingKey, String messageId, String appId, String message) { + IntegrationEventHandler handler; + logger.debug("Processing message with routing key '{}'", routingKey); + if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantRemovalTopic())) + handler = this.applicationContext.getBean(TenantRemovalIntegrationEventHandler.class); + else if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantTouchedTopic())) + handler = this.applicationContext.getBean(TenantTouchedIntegrationEventHandler.class); + else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserRemovalTopic())) + handler = this.applicationContext.getBean(UserRemovalIntegrationEventHandler.class); + else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserTouchedTopic())) + handler = this.applicationContext.getBean(UserTouchedIntegrationEventHandler.class); + else { + logger.error("No handler found for message routing key '{}'. Discarding.", routingKey); + handler = null; + } + + if (handler == null) + return EventProcessingStatus.Discard; + + IntegrationEventProperties properties = new IntegrationEventProperties(); + properties.setAppId(appId); + properties.setMessageId(messageId); + + TrackedEvent event = this.jsonHandlingService.fromJsonSafe(TrackedEvent.class, message); +// using (LogContext.PushProperty(this._logTrackingConfig.LogTrackingContextName, @event.TrackingContextTag)) +// { + try { + return handler.handle(properties, message); + } catch (Exception ex) { + logger.error("problem handling event from routing key " + routingKey + ". Setting nack and continuing...", ex); + return EventProcessingStatus.Error; + } +// } + } + + private Boolean routingKeyMatched(String routingKey, List topics) { + if (topics == null || topics.isEmpty()) + return false; + return topics.stream().anyMatch(x -> x.equals(routingKey)); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventHandler.java new file mode 100644 index 000000000..729cd7642 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventHandler.java @@ -0,0 +1,7 @@ +package gr.cite.notification.integrationevent.inbox; + +public interface IntegrationEventHandler { + + EventProcessingStatus handle(IntegrationEventProperties properties, String message); + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventProperties.java new file mode 100644 index 000000000..9d5fcea93 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/IntegrationEventProperties.java @@ -0,0 +1,25 @@ +package gr.cite.notification.integrationevent.inbox; + +public class IntegrationEventProperties { + + private String messageId; + + private String appId; + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyHandler.java new file mode 100644 index 000000000..685597a98 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyHandler.java @@ -0,0 +1,26 @@ +package gr.cite.notification.integrationevent.inbox.tenantremoval; + +import gr.cite.notification.integrationevent.inbox.ConsistencyHandler; +import gr.cite.notification.query.TenantQuery; +import gr.cite.tools.data.query.QueryFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantRemovalConsistencyHandler implements ConsistencyHandler { + + private final QueryFactory queryFactory; + + public TenantRemovalConsistencyHandler(QueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + @Override + public Boolean isConsistent(TenantRemovalConsistencyPredicates consistencyPredicates) { + long count = this.queryFactory.query(TenantQuery.class).ids(consistencyPredicates.getTenantId()).count(); + return count > 0; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyPredicates.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyPredicates.java new file mode 100644 index 000000000..f55869126 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalConsistencyPredicates.java @@ -0,0 +1,23 @@ +package gr.cite.notification.integrationevent.inbox.tenantremoval; + +import gr.cite.notification.integrationevent.inbox.ConsistencyPredicates; + +import java.util.UUID; + +public class TenantRemovalConsistencyPredicates implements ConsistencyPredicates { + + private UUID tenantId; + + public TenantRemovalConsistencyPredicates(UUID tenantId) { + this.tenantId = tenantId; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEvent.java new file mode 100644 index 000000000..cf1201688 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEvent.java @@ -0,0 +1,19 @@ +package gr.cite.notification.integrationevent.inbox.tenantremoval; + +import gr.cite.notification.integrationevent.TrackedEvent; + +import java.util.UUID; + +public class TenantRemovalIntegrationEvent extends TrackedEvent { + + private UUID id; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandler.java new file mode 100644 index 000000000..a6e5d27ed --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandler.java @@ -0,0 +1,7 @@ +package gr.cite.notification.integrationevent.inbox.tenantremoval; + +import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; + +public interface TenantRemovalIntegrationEventHandler extends IntegrationEventHandler { + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandlerImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..c27824f3f --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenantremoval/TenantRemovalIntegrationEventHandlerImpl.java @@ -0,0 +1,111 @@ +package gr.cite.notification.integrationevent.inbox.tenantremoval; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; +import gr.cite.notification.integrationevent.inbox.InboxPrincipal; +import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; +import gr.cite.notification.service.tenant.TenantService; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.AbstractMap; +import java.util.Map; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantRemovalIntegrationEventHandlerImpl.class)); + + private final JsonHandlingService jsonHandlingService; + + private final ApplicationContext applicationContext; + + private final ErrorThesaurusProperties errors; + + private final MessageSource messageSource; + + public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext, ErrorThesaurusProperties errors, MessageSource messageSource) { + this.jsonHandlingService = jsonHandlingService; + this.applicationContext = applicationContext; + this.errors = errors; + this.messageSource = messageSource; + } + + @Override + public EventProcessingStatus handle(IntegrationEventProperties properties, String message) { + TenantRemovalIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(TenantRemovalIntegrationEvent.class, message); + if (event == null) + return EventProcessingStatus.Error; + + EntityManager entityManager = null; + EntityTransaction transaction = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); + currentPrincipalResolver.push(InboxPrincipal.build(properties)); + + TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler = this.applicationContext.getBean(TenantRemovalConsistencyHandler.class); + if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) + return EventProcessingStatus.Postponed; + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + try { + TenantService tenantService = this.applicationContext.getBean(TenantService.class); + tenantService.deleteAndSave(event.getId()); + + AuditService auditService = this.applicationContext.getBean(AuditService.class); + + auditService.track(AuditableAction.Tenant_Delete, Map.ofEntries( + new AbstractMap.SimpleEntry("id", event.getId()) + )); + //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + transaction.commit(); + } catch (Exception e) { + transaction.rollback(); + throw e; + } finally { + currentPrincipalResolver.pop(); + } + + transaction.commit(); + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) + transaction.rollback(); + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) + transaction.rollback(); + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } + return null; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEvent.java new file mode 100644 index 000000000..899d22db8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEvent.java @@ -0,0 +1,29 @@ +package gr.cite.notification.integrationevent.inbox.tenanttouched; + +import gr.cite.notification.integrationevent.TrackedEvent; + +import java.util.UUID; + +public class TenantTouchedIntegrationEvent extends TrackedEvent { + + private UUID id; + + private String code; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandler.java new file mode 100644 index 000000000..f476601f3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandler.java @@ -0,0 +1,7 @@ +package gr.cite.notification.integrationevent.inbox.tenanttouched; + +import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; + +public interface TenantTouchedIntegrationEventHandler extends IntegrationEventHandler { + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandlerImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..013a2698f --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/tenanttouched/TenantTouchedIntegrationEventHandlerImpl.java @@ -0,0 +1,107 @@ +package gr.cite.notification.integrationevent.inbox.tenanttouched; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; +import gr.cite.notification.integrationevent.inbox.InboxPrincipal; +import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; +import gr.cite.notification.model.persist.TenantTouchedIntegrationEventPersist; +import gr.cite.notification.service.tenant.TenantService; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.validation.ValidatorFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.AbstractMap; +import java.util.Map; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantTouchedIntegrationEventHandlerImpl.class)); + + protected final ApplicationContext applicationContext; + + private final JsonHandlingService jsonHandlingService; + private final ValidatorFactory validatorFactory; + + public TenantTouchedIntegrationEventHandlerImpl(ApplicationContext applicationContext, JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory) { + this.applicationContext = applicationContext; + this.jsonHandlingService = jsonHandlingService; + this.validatorFactory = validatorFactory; + } + + @Override + public EventProcessingStatus handle(IntegrationEventProperties properties, String message) { + TenantTouchedIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(TenantTouchedIntegrationEvent.class, message); + if (event == null) + return EventProcessingStatus.Error; + + TenantTouchedIntegrationEventPersist model = new TenantTouchedIntegrationEventPersist(); + model.setId(event.getId()); + model.setCode(event.getCode()); + this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model); + + EntityManager entityManager = null; + EntityTransaction transaction = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); + currentPrincipalResolver.push(InboxPrincipal.build(properties)); + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + try { + TenantService tenantService = this.applicationContext.getBean(TenantService.class); + tenantService.persist(model, null); + + AuditService auditService = this.applicationContext.getBean(AuditService.class); + + auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries( + new AbstractMap.SimpleEntry("model", model) + )); + + transaction.commit(); + } catch (Exception e) { + transaction.rollback(); + throw e; + } finally { + currentPrincipalResolver.pop(); + } + + transaction.commit(); + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) + transaction.rollback(); + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) + transaction.rollback(); + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } + return null; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyHandler.java new file mode 100644 index 000000000..958d00b84 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyHandler.java @@ -0,0 +1,25 @@ +package gr.cite.notification.integrationevent.inbox.userremoval; + +import gr.cite.notification.integrationevent.inbox.ConsistencyHandler; +import gr.cite.notification.query.UserQuery; +import gr.cite.tools.data.query.QueryFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserRemovalConsistencyHandler implements ConsistencyHandler { + + private final QueryFactory queryFactory; + + public UserRemovalConsistencyHandler(QueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + @Override + public Boolean isConsistent(UserRemovalConsistencyPredicates consistencyPredicates) { + long count = this.queryFactory.query(UserQuery.class).ids(consistencyPredicates.getUserId()).count(); + return count != 0; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyPredicates.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyPredicates.java new file mode 100644 index 000000000..2c9974a7d --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalConsistencyPredicates.java @@ -0,0 +1,23 @@ +package gr.cite.notification.integrationevent.inbox.userremoval; + +import gr.cite.notification.integrationevent.inbox.ConsistencyPredicates; + +import java.util.UUID; + +public class UserRemovalConsistencyPredicates implements ConsistencyPredicates { + + private UUID userId; + + public UserRemovalConsistencyPredicates(UUID userId) { + this.userId = userId; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEvent.java new file mode 100644 index 000000000..2ce9f613d --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEvent.java @@ -0,0 +1,28 @@ +package gr.cite.notification.integrationevent.inbox.userremoval; + +import gr.cite.notification.integrationevent.TrackedEvent; + +import java.util.UUID; + +public class UserRemovalIntegrationEvent extends TrackedEvent { + + private UUID userId; + + private UUID tenant; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getTenant() { + return tenant; + } + + public void setTenant(UUID tenant) { + this.tenant = tenant; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandler.java new file mode 100644 index 000000000..7bc8d1245 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandler.java @@ -0,0 +1,7 @@ +package gr.cite.notification.integrationevent.inbox.userremoval; + +import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; + +public interface UserRemovalIntegrationEventHandler extends IntegrationEventHandler { + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandlerImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..326325373 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/userremoval/UserRemovalIntegrationEventHandlerImpl.java @@ -0,0 +1,141 @@ +package gr.cite.notification.integrationevent.inbox.userremoval; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; +import gr.cite.notification.integrationevent.inbox.InboxPrincipal; +import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.service.user.UserService; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.AbstractMap; +import java.util.Map; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserRemovalIntegrationEventHandlerImpl.class)); + + private final JsonHandlingService jsonHandlingService; + + private final ApplicationContext applicationContext; + + private final ErrorThesaurusProperties errors; + + private final MessageSource messageSource; + + public UserRemovalIntegrationEventHandlerImpl( + JsonHandlingService jsonHandlingService, + ApplicationContext applicationContext, + ErrorThesaurusProperties errors, + MessageSource messageSource + ) { + this.jsonHandlingService = jsonHandlingService; + this.applicationContext = applicationContext; + this.errors = errors; + this.messageSource = messageSource; + } + + @Override + public EventProcessingStatus handle(IntegrationEventProperties properties, String message) { + UserRemovalIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(UserRemovalIntegrationEvent.class, message); + if (event == null) + return EventProcessingStatus.Error; + if (event.getUserId() == null) + throw new MyValidationException(this.errors.getModelValidation().getCode(), "userId", messageSource.getMessage("Validation_Required", new Object[]{"userId"}, LocaleContextHolder.getLocale())); + + logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName()); + + EntityManager entityManager = null; + EntityTransaction transaction = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + TenantScope scope = this.applicationContext.getBean(TenantScope.class); + if (scope.isMultitenant() && event.getTenant() != null) { + TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(event.getTenant()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); + if (tenant == null) { + logger.error("missing tenant from event message"); + return EventProcessingStatus.Error; + } + scope.setTenant(event.getTenant(), tenant.getCode()); + } else if (scope.isMultitenant()) { + logger.error("missing tenant from event message"); + return EventProcessingStatus.Error; + } + + CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); + currentPrincipalResolver.push(InboxPrincipal.build(properties)); + + UserRemovalConsistencyHandler userRemovalConsistencyHandler = this.applicationContext.getBean(UserRemovalConsistencyHandler.class); + if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId())))) + return EventProcessingStatus.Postponed; + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + try { + UserService userService = this.applicationContext.getBean(UserService.class); + userService.deleteAndSave(event.getUserId()); + + AuditService auditService = this.applicationContext.getBean(AuditService.class); + + auditService.track(AuditableAction.User_Delete, Map.ofEntries( + new AbstractMap.SimpleEntry("id", event.getUserId()) + )); + //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + transaction.commit(); + } catch (Exception e) { + transaction.rollback(); + throw e; + } finally { + currentPrincipalResolver.pop(); + } + + transaction.commit(); + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) + transaction.rollback(); + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) + transaction.rollback(); + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } + return EventProcessingStatus.Success; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEvent.java new file mode 100644 index 000000000..daa3d9a54 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEvent.java @@ -0,0 +1,194 @@ +package gr.cite.notification.integrationevent.inbox.usertouched; + +import gr.cite.notification.common.enums.ContactInfoType; +import gr.cite.notification.common.validation.BaseValidator; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.integrationevent.TrackedEvent; +import gr.cite.notification.model.persist.UserTouchedIntegrationEventPersist; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class UserTouchedIntegrationEvent extends TrackedEvent { + + private UUID id; + + public static final String _id = "id"; + + private UUID tenant; + + private String name; + + public static final String _name = "name"; + + public static final int _nameLength = 200; + + private String subjectId; + + public static final String _subjectId = "subjectId"; + + private UserProfile profile; + + private List userContactInfo; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getTenant() { + return tenant; + } + + public void setTenant(UUID tenant) { + this.tenant = tenant; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSubjectId() { + return subjectId; + } + + public void setSubjectId(String subjectId) { + this.subjectId = subjectId; + } + + public UserProfile getProfile() { + return profile; + } + + public void setProfile(UserProfile profile) { + this.profile = profile; + } + + public List getUserContactInfo() { + return userContactInfo; + } + + public void setUserContactInfo(List userContactInfo) { + this.userContactInfo = userContactInfo; + } + + public static class UserProfile { + + private String timezone; + + private String culture; + + private String language; + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + } + + public static class UserContactInfo { + + private ContactInfoType type; + + private String value; + + private int ordinal; + + public ContactInfoType getType() { + return type; + } + + public void setType(ContactInfoType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getOrdinal() { + return ordinal; + } + + public void setOrdinal(int ordinal) { + this.ordinal = ordinal; + } + } + + @Component(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class UserTouchedIntegrationEventValidator extends BaseValidator { + + public static final String ValidatorName = "UserTouchedIntegrationEventValidator"; + + private final MessageSource messageSource; + + protected UserTouchedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return UserTouchedIntegrationEvent.class; + } + + @Override + protected List specifications(UserTouchedIntegrationEvent item) { + return Arrays.asList( + this.spec() + .iff(() -> !this.isNull(item.getId())) + .must(() -> this.isValidGuid(item.getId())) + .failOn(UserTouchedIntegrationEvent._id).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._id}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getName())) + .failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getName())) + .must(() -> this.lessEqualLength(item.getName(), UserTouchedIntegrationEventPersist._nameLength)) + .failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_MaxLength", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getSubjectId())) + .failOn(UserTouchedIntegrationEvent._subjectId).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._subjectId}, LocaleContextHolder.getLocale())) + ); + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandler.java new file mode 100644 index 000000000..24b11d5f3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandler.java @@ -0,0 +1,7 @@ +package gr.cite.notification.integrationevent.inbox.usertouched; + +import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; + +public interface UserTouchedIntegrationEventHandler extends IntegrationEventHandler { + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandlerImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..4b0bafa27 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/inbox/usertouched/UserTouchedIntegrationEventHandlerImpl.java @@ -0,0 +1,129 @@ +package gr.cite.notification.integrationevent.inbox.usertouched; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; +import gr.cite.notification.integrationevent.inbox.InboxPrincipal; +import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.service.user.UserService; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.validation.ValidatorFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.AbstractMap; +import java.util.Map; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserTouchedIntegrationEventHandlerImpl.class)); + + protected final ApplicationContext applicationContext; + + private final JsonHandlingService jsonHandlingService; + + private final ValidatorFactory validatorFactory; + + public UserTouchedIntegrationEventHandlerImpl( + JsonHandlingService jsonHandlingService, + ApplicationContext applicationContext, + ValidatorFactory validatorFactory) { + this.jsonHandlingService = jsonHandlingService; + this.applicationContext = applicationContext; + this.validatorFactory = validatorFactory; + } + + @Override + public EventProcessingStatus handle(IntegrationEventProperties properties, String message) { + UserTouchedIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(UserTouchedIntegrationEvent.class, message); + if (event == null) + return EventProcessingStatus.Error; + + logger.debug("Handling {}", UserTouchedIntegrationEvent.class.getSimpleName()); + + this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event); + + EntityManager entityManager = null; + EntityTransaction transaction = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + TenantScope scope = this.applicationContext.getBean(TenantScope.class); + if (scope.isMultitenant() && event.getTenant() != null) { + TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(event.getTenant()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); + if (tenant == null) { + logger.error("missing tenant from event message"); + return EventProcessingStatus.Error; + } + scope.setTenant(event.getTenant(), tenant.getCode()); + } else if (scope.isMultitenant()) { + logger.error("missing tenant from event message"); + return EventProcessingStatus.Error; + } +// +// ValidationService validator = this.applicationContext.getBean(ValidationService.class); +// validator.validateForce(model); + + CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); + currentPrincipalResolver.push(InboxPrincipal.build(properties)); + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + try { + UserService userService = this.applicationContext.getBean(UserService.class); + userService.persist(event, null); + + AuditService auditService = this.applicationContext.getBean(AuditService.class); + + auditService.track(AuditableAction.User_Persist, Map.ofEntries( + new AbstractMap.SimpleEntry("model", event) + )); + + transaction.commit(); + } catch (Exception e) { + transaction.rollback(); + throw e; + } finally { + currentPrincipalResolver.pop(); + } + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) + transaction.rollback(); + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) + transaction.rollback(); + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } + return EventProcessingStatus.Success; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxIntegrationEvent.java new file mode 100644 index 000000000..5961fe016 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxIntegrationEvent.java @@ -0,0 +1,19 @@ +package gr.cite.notification.integrationevent.outbox; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import gr.cite.notification.integrationevent.TrackedEvent; +import gr.cite.rabbitmq.IntegrationEvent; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class OutboxIntegrationEvent extends IntegrationEvent { + private TrackedEvent event; + + public TrackedEvent getEvent() { + return event; + } + + public void setEvent(TrackedEvent event) { + this.event = event; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxProperties.java new file mode 100644 index 000000000..af5e77173 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxProperties.java @@ -0,0 +1,58 @@ +package gr.cite.notification.integrationevent.outbox; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "queue.task.publisher.options") +public class OutboxProperties { + + private final String exchange; + + private final String tenantTouchTopic; + + private final String tenantRemovalTopic; + + private final String userTouchTopic; + + private final String userRemovalTopic; + + private final String notifyTopic; + + public OutboxProperties(String exchange, + String tenantTouchTopic, + String tenantRemovalTopic, + String userTouchTopic, + String userRemovalTopic, + String notifyTopic + ) { + this.exchange = exchange; + this.tenantTouchTopic = tenantTouchTopic; + this.tenantRemovalTopic = tenantRemovalTopic; + this.userTouchTopic = userTouchTopic; + this.userRemovalTopic = userRemovalTopic; + this.notifyTopic = notifyTopic; + } + + public String getExchange() { + return exchange; + } + + public String getTenantTouchTopic() { + return tenantTouchTopic; + } + + public String getTenantRemovalTopic() { + return tenantRemovalTopic; + } + + public String getUserTouchTopic() { + return userTouchTopic; + } + + public String getUserRemovalTopic() { + return userRemovalTopic; + } + + public String getNotifyTopic() { + return notifyTopic; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxRepositoryImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxRepositoryImpl.java new file mode 100644 index 000000000..c8a841597 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/integrationevent/outbox/OutboxRepositoryImpl.java @@ -0,0 +1,396 @@ +package gr.cite.notification.integrationevent.outbox; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.data.QueueOutboxEntity; +import gr.cite.notification.query.QueueOutboxQuery; +import gr.cite.queueoutbox.entity.QueueOutbox; +import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; +import gr.cite.queueoutbox.repository.CandidateInfo; +import gr.cite.queueoutbox.repository.OutboxRepository; +import gr.cite.queueoutbox.task.MessageOptions; +import gr.cite.rabbitmq.IntegrationEvent; +import gr.cite.tools.data.query.Ordering; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; + +import java.time.Instant; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class OutboxRepositoryImpl implements OutboxRepository { + + protected final ApplicationContext applicationContext; + private final Random random = new Random(); + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OutboxRepositoryImpl.class)); + private final JsonHandlingService jsonHandlingService; + private final OutboxProperties outboxProperties; + + public OutboxRepositoryImpl( + ApplicationContext applicationContext, + OutboxProperties outboxProperties + ) { + this.applicationContext = applicationContext; + this.jsonHandlingService = this.applicationContext.getBean(JsonHandlingService.class); + this.outboxProperties = outboxProperties; + } + + @Override + public CandidateInfo candidate(Instant lastCandidateCreationTimestamp, MessageOptions messageOptions, Function onConfirmTimeout) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + CandidateInfo candidate = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + QueueOutboxEntity item = queryFactory.query(QueueOutboxQuery.class) + .isActives(IsActive.Active) + .notifyStatus(QueueOutboxNotifyStatus.PENDING, QueueOutboxNotifyStatus.WAITING_CONFIRMATION, QueueOutboxNotifyStatus.ERROR) + .retryThreshold(messageOptions.getRetryThreashold()) + .confirmTimeout(messageOptions.getConfirmTimeoutSeconds()) + .createdAfter(lastCandidateCreationTimestamp) + .ordering(new Ordering().addAscending(QueueOutboxEntity._createdAt)) + .first(); + + if (item != null) { + boolean confirmTimeout = onConfirmTimeout.apply(item); + + QueueOutboxNotifyStatus prevState = item.getNotifyStatus(); + item.setNotifyStatus(QueueOutboxNotifyStatus.PROCESSING); + + entityManager.merge(item); + entityManager.flush(); + + candidate = new CandidateInfo(); + candidate.setId(item.getId()); + candidate.setCreatedAt(item.getCreatedAt()); + candidate.setPreviousState(prevState); + } + + transaction.commit(); + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) transaction.rollback(); + candidate = null; + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) transaction.rollback(); + candidate = null; + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } + + return candidate; + } + + @Override + public Boolean shouldOmit(CandidateInfo candidate, Function shouldOmit) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + QueueOutboxEntity item = queryFactory.query(QueueOutboxQuery.class).ids(candidate.getId()).first(); + + if (item == null) { + logger.warn("Could not lookup queue outbox {} to process. Continuing...", candidate.getId()); + } else { + if (shouldOmit.apply(item)) { + item.setNotifyStatus(QueueOutboxNotifyStatus.OMITTED); + + entityManager.merge(item); + entityManager.flush(); + success = true; + } + } + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) transaction.rollback(); + success = false; + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return success; + } + + @Override + public Boolean shouldWait(CandidateInfo candidate, Function itIsTimeFunc) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + QueueOutboxEntity item = queryFactory.query(QueueOutboxQuery.class).ids(candidate.getId()).first(); + + if (item.getRetryCount() != null && item.getRetryCount() >= 1) { + Boolean itIsTime = itIsTimeFunc.apply(item); + + if (!itIsTime) { + item.setNotifyStatus(candidate.getPreviousState()); + + entityManager.merge(item); + entityManager.flush(); + success = true; + } + + success = !itIsTime; + } + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) transaction.rollback(); + success = false; + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return success; + } + + @Override + public Boolean process(CandidateInfo candidateInfo, Boolean isAutoconfirmOnPublish, Function publish) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + Boolean success = false; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + QueueOutboxEntity item = queryFactory.query(QueueOutboxQuery.class).ids(candidateInfo.getId()).first(); + + if (item == null) { + logger.warn("Could not lookup queue outbox {} to process. Continuing...", candidateInfo.getId()); + } else { + + success = publish.apply(item); + if (success) { + if (isAutoconfirmOnPublish) { + item.setNotifyStatus(QueueOutboxNotifyStatus.CONFIRMED); + item.setConfirmedAt(Instant.now()); + } else { + item.setNotifyStatus(QueueOutboxNotifyStatus.WAITING_CONFIRMATION); + } + item.setPublishedAt(Instant.now()); + } else { + item.setNotifyStatus(QueueOutboxNotifyStatus.ERROR); + item.setRetryCount(item.getRetryCount() != null ? item.getRetryCount() + 1 : 0); + item.setPublishedAt(null); + } + + entityManager.merge(item); + entityManager.flush(); + } + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) transaction.rollback(); + success = false; + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return success; + } + + @Override + public void handleConfirm(List confirmedMessages) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + List queueOutboxMessages = queryFactory.query(QueueOutboxQuery.class).ids(confirmedMessages).collect(); + + if (queueOutboxMessages == null) { + logger.warn("Could not lookup messages {} to process. Continuing...", String.join(",", confirmedMessages.stream().map(x -> x.toString()).collect(Collectors.toList()))); + } else { + + for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) { + queueOutboxMessage.setNotifyStatus(QueueOutboxNotifyStatus.CONFIRMED); + queueOutboxMessage.setConfirmedAt(Instant.now()); + entityManager.merge(queueOutboxMessage); + } + + entityManager.flush(); + } + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + } + + @Override + public void handleNack(List nackedMessages) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); + List queueOutboxMessages = queryFactory.query(QueueOutboxQuery.class).ids(nackedMessages).collect(); + + if (queueOutboxMessages == null) { + logger.warn("Could not lookup messages {} to process. Continuing...", String.join(",", nackedMessages.stream().map(x -> x.toString()).collect(Collectors.toList()))); + } else { + + for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) { + queueOutboxMessage.setNotifyStatus(QueueOutboxNotifyStatus.ERROR); + queueOutboxMessage.setRetryCount(queueOutboxMessage.getRetryCount() != null ? queueOutboxMessage.getRetryCount() + 1 : 0); + entityManager.merge(queueOutboxMessage); + } + + entityManager.flush(); + } + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + } + + @Override + public QueueOutbox create(IntegrationEvent item) { + EntityTransaction transaction = null; + EntityManager entityManager = null; + boolean success = false; + QueueOutboxEntity queueMessage = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + queueMessage = this.mapEvent((OutboxIntegrationEvent) item); + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + + transaction.begin(); + + entityManager.persist(queueMessage); + entityManager.flush(); + + transaction.commit(); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + if (transaction != null) transaction.rollback(); + success = false; + } finally { + if (entityManager != null) entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + return queueMessage; + } + + private QueueOutboxEntity mapEvent(OutboxIntegrationEvent event) { + String routingKey; + switch (event.getType()) { + default: { + logger.error("unrecognized outgoing integration event {}. Skipping...", event.getType()); + return null; + } + } + +// UUID correlationId = UUID.randomUUID(); +// if (event.getEvent() != null) event.getEvent().setTrackingContextTag(correlationId.toString()); +// event.setMessage(this.jsonHandlingService.toJsonSafe(event.getEvent())); +// //this._logTrackingService.Trace(correlationId.ToString(), $"Correlating current tracking context with new correlationId {correlationId}"); +// +// QueueOutboxEntity queueMessage = new QueueOutboxEntity(); +// queueMessage.setId(UUID.randomUUID()); +// queueMessage.setTenantId(null); +// queueMessage.setExchange(this.outboxProperties.getExchange()); +// queueMessage.setRoute(routingKey); +// queueMessage.setMessageId(event.getMessageId()); +// queueMessage.setMessage(this.jsonHandlingService.toJsonSafe(event)); +// queueMessage.setIsActive(IsActive.Active); +// queueMessage.setNotifyStatus(QueueOutboxNotifyStatus.PENDING); +// queueMessage.setRetryCount(0); +// queueMessage.setCreatedAt(Instant.now()); +// queueMessage.setUpdatedAt(Instant.now()); +// +// return queueMessage; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleConfiguration.java b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleConfiguration.java new file mode 100644 index 000000000..b6699c02f --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleConfiguration.java @@ -0,0 +1,9 @@ +package gr.cite.notification.locale; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(LocaleProperties.class) +public class LocaleConfiguration { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleProperties.java b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleProperties.java new file mode 100644 index 000000000..296b55a92 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleProperties.java @@ -0,0 +1,32 @@ +package gr.cite.notification.locale; + + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "locale") +public class LocaleProperties { + + private String timezone; + public String getTimezone() { + return timezone; + } + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + private String language; + public String getLanguage() { + return language; + } + public void setLanguage(String language) { + this.language = language; + } + + private String culture; + public String getCulture() { + return culture; + } + public void setCulture(String culture) { + this.culture = culture; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleService.java new file mode 100644 index 000000000..f2f54e6e7 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleService.java @@ -0,0 +1,18 @@ +package gr.cite.notification.locale; + +import java.util.Locale; +import java.util.TimeZone; + +public interface LocaleService { + String timezoneName(); + TimeZone timezone(); + TimeZone timezone(String code); + TimeZone timezoneSafe(String code); + + String cultureName(); + Locale culture(); + Locale culture(String code); + Locale cultureSafe(String code); + + String language(); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleServiceImpl.java new file mode 100644 index 000000000..71bdb83e7 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/locale/LocaleServiceImpl.java @@ -0,0 +1,85 @@ +package gr.cite.notification.locale; + +import gr.cite.notification.convention.ConventionService; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.ZoneId; +import java.util.Locale; +import java.util.TimeZone; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) +public class LocaleServiceImpl implements LocaleService { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(LocaleServiceImpl.class)); + private final LocaleProperties localeProperties; + private final ConventionService conventionService; + + @Autowired + public LocaleServiceImpl(LocaleProperties localeProperties, ConventionService conventionService) { + this.localeProperties = localeProperties; + this.conventionService = conventionService; + } + + + @Override + public String timezoneName() { + return this.localeProperties.getTimezone(); + } + + @Override + public TimeZone timezone() { + return TimeZone.getTimeZone(ZoneId.of(this.localeProperties.getTimezone())); + } + + @Override + public TimeZone timezone(String code) { + if (this.conventionService.isNullOrEmpty(code)) return this.timezone(); + return TimeZone.getTimeZone(ZoneId.of(code)); + } + + @Override + public TimeZone timezoneSafe(String code) { + try { + return this.timezone(code); + } catch (Exception ex){ + logger.warn("tried to retrieve timezone for '"+ code +"' but failed. falling back to default", ex); + return this.timezone(); + } + } + + @Override + public String cultureName() { + return this.localeProperties.getCulture(); + } + + @Override + public Locale culture() { + return Locale.forLanguageTag(this.localeProperties.getCulture()); + } + + @Override + public Locale culture(String code) { + if (this.conventionService.isNullOrEmpty(code)) return this.culture(); + return Locale.forLanguageTag(code); + } + + @Override + public Locale cultureSafe(String code) { + try { + return this.culture(code); + } catch (Exception ex){ + logger.warn("tried to retrieve timezone for '"+ code +"' but failed. falling back to default", ex); + return this.culture(); + } + } + + @Override + public String language() { + return this.localeProperties.getLanguage(); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/Tenant.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/Tenant.java new file mode 100644 index 000000000..ab585cf72 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/Tenant.java @@ -0,0 +1,82 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; + +import java.time.Instant; +import java.util.UUID; + +public class Tenant { + + private UUID id; + + public static final String _id = "id"; + + private String code; + + public static final String _code = "code"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + private IsActive isActive; + + public static final String _isActive = "isActive"; + + private String hash; + + public static final String _hash = "hash"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/TenantConfiguration.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/TenantConfiguration.java new file mode 100644 index 000000000..f14234bc9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/TenantConfiguration.java @@ -0,0 +1,132 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleConfigurationDataContainer; +import gr.cite.notification.common.types.tenantconfiguration.EmailClientConfigurationDataContainer; + +import java.time.Instant; +import java.util.UUID; + +public class TenantConfiguration { + + private UUID id; + + public static final String _id = "id"; + + private UUID tenantId; + + public static final String _tenantId = "tenantId"; + + private TenantConfigurationType type; + + public static final String _type = "type"; + + private String value; + + public static final String _value = "value"; + + private EmailClientConfigurationDataContainer emailClientData; + + public static final String _emailClientData = "emailClientData"; + + private DefaultUserLocaleConfigurationDataContainer defaultUserLocaleData; + + public static final String _defaultUserLocaleData = "defaultUserLocaleData"; + + private IsActive isActive; + + public static final String _isActive = "isActive"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + private String hash; + + public static final String _hash = "hash"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + public TenantConfigurationType getType() { + return type; + } + + public void setType(TenantConfigurationType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public EmailClientConfigurationDataContainer getEmailClientData() { + return emailClientData; + } + + public void setEmailClientData(EmailClientConfigurationDataContainer emailClientData) { + this.emailClientData = emailClientData; + } + + public DefaultUserLocaleConfigurationDataContainer getDefaultUserLocaleData() { + return defaultUserLocaleData; + } + + public void setDefaultUserLocaleData(DefaultUserLocaleConfigurationDataContainer defaultUserLocaleData) { + this.defaultUserLocaleData = defaultUserLocaleData; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/User.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/User.java new file mode 100644 index 000000000..7231b2245 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/User.java @@ -0,0 +1,80 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; + +import java.time.Instant; +import java.util.UUID; + +public class User { + + private UUID id; + public static final String _id = "id"; + + private String name; + public static final String _name = "name"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public static final String _hash = "hash"; + private String hash; + + public static final String _tenantUsers = "tenantUsers"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/UserCredential.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/UserCredential.java new file mode 100644 index 000000000..826a39d20 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/UserCredential.java @@ -0,0 +1,81 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; + +import java.time.Instant; +import java.util.UUID; + +public class UserCredential { + + private UUID id; + + public static final String _id = "id"; + + private String externalId; + + public static final String _externalId = "externalId"; + + private User user; + + public static final String _user = "user"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + private IsActive isActive; + + public static final String _isActive = "isActive"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/BaseBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/BaseBuilder.java new file mode 100644 index 000000000..087df8b45 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/BaseBuilder.java @@ -0,0 +1,95 @@ +package gr.cite.notification.model.builder; + +import gr.cite.notification.convention.ConventionService; +import gr.cite.tools.data.builder.Builder; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; + +import java.time.Instant; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public abstract class BaseBuilder implements Builder { + protected final LoggerService logger; + protected final ConventionService conventionService; + + public BaseBuilder( + ConventionService conventionService, + LoggerService logger + ) { + this.conventionService = conventionService; + this.logger = logger; + } + + public M build(FieldSet directives, D data) throws MyApplicationException { + if (data == null) { + //this.logger.Debug(new MapLogEntry("requested build for null item requesting fields").And("fields", directives)); +// return default(M); + M model = null; + return null; //TODO + } + List models = this.build(directives, Arrays.asList(data)); + return models.stream().findFirst().orElse(null); //TODO + } + + public abstract List build(FieldSet directives, List datas) throws MyApplicationException; + + public Map asForeignKey(QueryBase query, FieldSet directives, Function keySelector) throws MyApplicationException { + this.logger.trace("Building references from query"); + List datas = query.collectAs(directives); + this.logger.debug("collected {} items to build", Optional.ofNullable(datas).map(e -> e.size()).orElse(0)); + return this.asForeignKey(datas, directives, keySelector); + } + + public Map asForeignKey(List datas, FieldSet directives, Function keySelector) throws MyApplicationException { + this.logger.trace("building references"); + List models = this.build(directives, datas); + this.logger.debug("mapping {} build items from {} requested", Optional.ofNullable(models).map(e -> e.size()).orElse(0), Optional.ofNullable(datas).map(e -> e.size()).orElse(0)); + Map map = models.stream().collect(Collectors.toMap(o -> keySelector.apply(o), o -> o)); + return map; + } + + public Map> asMasterKey(QueryBase query, FieldSet directives, Function keySelector) throws MyApplicationException { + this.logger.trace("Building details from query"); + List datas = query.collectAs(directives); + this.logger.debug("collected {} items to build", Optional.ofNullable(datas).map(e -> e.size()).orElse(0)); + return this.asMasterKey(datas, directives, keySelector); + } + + public Map> asMasterKey(List datas, FieldSet directives, Function keySelector) throws MyApplicationException { + this.logger.trace("building details"); + List models = this.build(directives, datas); + this.logger.debug("mapping {} build items from {} requested", Optional.ofNullable(models).map(e -> e.size()).orElse(0), Optional.ofNullable(datas).map(e -> e.size()).orElse(0)); + Map> map = new HashMap<>(); + for (M model : models) { + K key = keySelector.apply(model); + if (!map.containsKey(key)) map.put(key, new ArrayList()); + map.get(key).add(model); + } + return map; + } + + public Map asEmpty(List keys, Function mapper, Function keySelector) { + this.logger.trace("building static references"); + List models = keys.stream().map(x -> mapper.apply(x)).collect(Collectors.toList()); + this.logger.debug("mapping {} build items from {} requested", Optional.ofNullable(models).map(x -> x.size()).orElse(0), Optional.ofNullable(keys).map(x -> x.size())); + Map map = models.stream().collect(Collectors.toMap(o -> keySelector.apply(o), o -> o)); + return map; + } + + protected String hashValue(Instant value) throws MyApplicationException { + return this.conventionService.hashValue(value); + } + + protected String asPrefix(String name) { + return this.conventionService.asPrefix(name); + } + + protected String asIndexer(String... names) { + return this.conventionService.asIndexer(names); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantBuilder.java new file mode 100644 index 000000000..c1fd44c14 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantBuilder.java @@ -0,0 +1,68 @@ +package gr.cite.notification.model.builder; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.model.Tenant; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public TenantBuilder( + ConventionService conventionService, + BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantBuilder.class))); + this.builderFactory = builderFactory; + } + + public TenantBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + + List models = new ArrayList<>(); + for (TenantEntity d : data) { + Tenant m = new Tenant(); + if (fields.hasField(this.asIndexer(Tenant._id))) + m.setId(d.getId()); + if (fields.hasField(this.asIndexer(Tenant._code))) + m.setCode(d.getCode()); + if (fields.hasField(this.asIndexer(Tenant._isActive))) + m.setIsActive(d.getIsActive()); + if (fields.hasField(this.asIndexer(Tenant._createdAt))) + m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(Tenant._updatedAt))) + m.setUpdatedAt(d.getUpdatedAt()); + if (fields.hasField(this.asIndexer(Tenant._hash))) + m.setHash(this.hashValue(d.getUpdatedAt())); + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java new file mode 100644 index 000000000..c8668d105 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java @@ -0,0 +1,117 @@ +package gr.cite.notification.model.builder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleConfigurationDataContainer; +import gr.cite.notification.common.types.tenantconfiguration.EmailClientConfigurationDataContainer; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.*; + +@Component +@RequestScope +public class TenantConfigurationBuilder extends BaseBuilder { + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + private final ObjectMapper mapper; + @Autowired + public TenantConfigurationBuilder(ConventionService conventionService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantConfigurationBuilder.class))); + this.mapper = new ObjectMapper(); + } + + public TenantConfigurationBuilder authorize(EnumSet values){ + this.authorize = values; + return this; + } + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0),Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size) .orElse(0)); + this.logger.trace(new DataLogEntry("requested fields",fields)); + if(fields == null || data == null || fields.isEmpty()) return new ArrayList<>(); + + List models = new ArrayList<>(); + for(TenantConfigurationEntity d : data){ + TenantConfiguration m = new TenantConfiguration(); + if(fields.hasField(this.asIndexer(TenantConfiguration._id))) m.setId(d.getId()); + if(fields.hasField(this.asIndexer(TenantConfiguration._tenantId))) m.setTenantId(d.getTenantId()); + if(fields.hasField(this.asIndexer(TenantConfiguration._type))) m.setType(d.getType()); + if(fields.hasField(this.asIndexer(TenantConfiguration._value))) m.setValue(d.getValue()); + if(fields.hasField(this.asIndexer(TenantConfiguration._defaultUserLocaleData))) { + try { + m.setDefaultUserLocaleData(mapper.readValue(d.getValue(), DefaultUserLocaleConfigurationDataContainer.class)); + + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if (!fields.extractPrefixed(this.asIndexer(TenantConfiguration._defaultUserLocaleData)).isEmpty()) { + try { + DefaultUserLocaleConfigurationDataContainer container = mapper.readValue(d.getValue(), DefaultUserLocaleConfigurationDataContainer.class); + if (container != null) { + m.setDefaultUserLocaleData(new DefaultUserLocaleConfigurationDataContainer()); + if (fields.hasField(this.asIndexer(TenantConfiguration._defaultUserLocaleData, DefaultUserLocaleConfigurationDataContainer.Field.LANGUAGE))) + m.getDefaultUserLocaleData().setLanguage(container.getLanguage()); + if (fields.hasField(this.asIndexer(TenantConfiguration._defaultUserLocaleData, DefaultUserLocaleConfigurationDataContainer.Field.TIME_ZONE))) + m.getDefaultUserLocaleData().setTimeZone(container.getTimeZone()); + if (fields.hasField(this.asIndexer(TenantConfiguration._defaultUserLocaleData, DefaultUserLocaleConfigurationDataContainer.Field.CULTURE))) + m.getDefaultUserLocaleData().setCulture(container.getCulture()); + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if(fields.hasField(this.asIndexer(TenantConfiguration._emailClientData))) { + try { + m.setEmailClientData(mapper.readValue(d.getValue(), EmailClientConfigurationDataContainer.class)); + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if (!fields.extractPrefixed(this.asIndexer(TenantConfiguration._emailClientData)).isEmpty()) { + try { + EmailClientConfigurationDataContainer container = mapper.readValue(d.getValue(), EmailClientConfigurationDataContainer.class); + if (container != null) { + m.setEmailClientData(new EmailClientConfigurationDataContainer()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.ENABLE_SSL))) + m.getEmailClientData().setEnableSSL(container.getEnableSSL()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.REQUIRE_CREDENTIALS))) + m.getEmailClientData().setRequireCredentials(container.getRequireCredentials()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.HOST_SERVER))) + m.getEmailClientData().setHostServer(container.getHostServer()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.HOST_PORT_NO))) + m.getEmailClientData().setHostPortNo(container.getHostPortNo()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.CERTIFICATE_PATH))) + m.getEmailClientData().setCertificatePath(container.getCertificatePath()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.EMAIL_ADDRESS))) + m.getEmailClientData().setEmailAddress(container.getEmailAddress()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.EMAIL_USER_NAME))) + m.getEmailClientData().setEmailUserName(container.getEmailUserName()); + if (fields.hasField(this.asIndexer(TenantConfiguration._emailClientData, EmailClientConfigurationDataContainer.Field.EMAIL_PASSWORD))) + m.getEmailClientData().setEmailPassword(container.getEmailPassword()); + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if(fields.hasField(this.asIndexer(TenantConfiguration._isActive))) m.setIsActive(d.getIsActive()); + if(fields.hasField(this.asIndexer(TenantConfiguration._createdAt))) m.setCreatedAt(d.getCreatedAt()); + if(fields.hasField(this.asIndexer(TenantConfiguration._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); + if(fields.hasField(this.asIndexer(TenantConfiguration._hash))) m.setHash(this.hashValue(d.getUpdatedAt())); + models.add(m); + } + this.logger.debug("build {} items",Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/UserBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/UserBuilder.java new file mode 100644 index 000000000..e65104883 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/UserBuilder.java @@ -0,0 +1,69 @@ +package gr.cite.notification.model.builder; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.model.User; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + private final QueryFactory queryFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public UserBuilder( + ConventionService conventionService, + BuilderFactory builderFactory, + QueryFactory queryFactory + ) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(UserBuilder.class))); + this.builderFactory = builderFactory; + this.queryFactory = queryFactory; + } + + public UserBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List datas) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(datas).map(e -> e.size()).orElse(0), Optional.ofNullable(fields).map(e -> e.getFields()).map(e -> e.size()).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || fields.isEmpty()) return new ArrayList<>(); + + List models = new ArrayList<>(); + + for (UserEntity d : datas) { + User m = new User(); + if (fields.hasField(this.asIndexer(User._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(User._hash))) m.setHash(this.hashValue(d.getUpdatedAt())); + if (fields.hasField(this.asIndexer(User._name))) m.setName(d.getName()); + if (fields.hasField(this.asIndexer(User._createdAt))) m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(User._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); + if (fields.hasField(this.asIndexer(User._isActive))) m.setIsActive(d.getIsActive()); + models.add(m); + } + this.logger.debug("build {} items", Optional.ofNullable(models).map(e -> e.size()).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantConfigBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantConfigBuilder.java new file mode 100644 index 000000000..57ad4bea8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantConfigBuilder.java @@ -0,0 +1,60 @@ +package gr.cite.notification.model.builder.tenantconfig; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.types.tenant.TenantConfigEntity; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.model.builder.BaseBuilder; +import gr.cite.notification.model.tenantconfig.TenantConfig; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantConfigBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public TenantConfigBuilder( + ConventionService conventionService, BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantConfigBuilder.class))); + this.builderFactory = builderFactory; + } + + public TenantConfigBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + + FieldSet depositFields = fields.extractPrefixed(this.asPrefix(TenantConfig._deposit)); + FieldSet fileFields = fields.extractPrefixed(this.asPrefix(TenantConfig._fileTransformers)); + + List models = new ArrayList<>(); + for (TenantConfigEntity d : data) { + TenantConfig m = new TenantConfig(); + if (!depositFields.isEmpty() && d.getDeposit() != null) m.setDeposit(this.builderFactory.builder(TenantDepositConfigBuilder.class).authorize(this.authorize).build(depositFields, d.getDeposit())); + if (!fileFields.isEmpty() && d.getFileTransformers() != null) m.setFileTransformers(this.builderFactory.builder(TenantFileTransformersBuilder.class).authorize(this.authorize).build(fileFields, d.getFileTransformers())); + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantDepositConfigBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantDepositConfigBuilder.java new file mode 100644 index 000000000..ec7c4b901 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantDepositConfigBuilder.java @@ -0,0 +1,61 @@ +package gr.cite.notification.model.builder.tenantconfig; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.types.tenant.TenantDepositConfigEntity; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.model.builder.BaseBuilder; +import gr.cite.notification.model.tenantconfig.TenantDepositConfig; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantDepositConfigBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public TenantDepositConfigBuilder( + ConventionService conventionService, BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantDepositConfigBuilder.class))); + this.builderFactory = builderFactory; + } + + public TenantDepositConfigBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + + FieldSet sourcesFields = fields.extractPrefixed(this.asPrefix(TenantDepositConfig._sources)); + + List models = new ArrayList<>(); + for (TenantDepositConfigEntity d : data) { + TenantDepositConfig m = new TenantDepositConfig(); + if (!sourcesFields.isEmpty() && d.getSources() != null) { + m.setSources(this.builderFactory.builder(TenantSourceBuilder.class).authorize(this.authorize).build(sourcesFields, d.getSources())); + } + + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantFileTransformersBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantFileTransformersBuilder.java new file mode 100644 index 000000000..5ea102750 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantFileTransformersBuilder.java @@ -0,0 +1,61 @@ +package gr.cite.notification.model.builder.tenantconfig; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.types.tenant.TenantFileTransformersConfigEntity; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.model.builder.BaseBuilder; +import gr.cite.notification.model.tenantconfig.TenantFileTransformersConfig; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantFileTransformersBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public TenantFileTransformersBuilder( + ConventionService conventionService, BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantFileTransformersBuilder.class))); + this.builderFactory = builderFactory; + } + + public TenantFileTransformersBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + + FieldSet sourcesFields = fields.extractPrefixed(this.asPrefix(TenantFileTransformersConfig._sources)); + + List models = new ArrayList<>(); + for (TenantFileTransformersConfigEntity d : data) { + TenantFileTransformersConfig m = new TenantFileTransformersConfig(); + if (!sourcesFields.isEmpty() && d.getSources() != null) { + m.setSources(this.builderFactory.builder(TenantSourceBuilder.class).authorize(this.authorize).build(sourcesFields, d.getSources())); + } + + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantSourceBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantSourceBuilder.java new file mode 100644 index 000000000..0260ab410 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantSourceBuilder.java @@ -0,0 +1,62 @@ +package gr.cite.notification.model.builder.tenantconfig; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.types.tenant.TenantSourceEntity; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.model.builder.BaseBuilder; +import gr.cite.notification.model.tenantconfig.TenantSource; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantSourceBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public TenantSourceBuilder( + ConventionService conventionService, BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantSourceBuilder.class))); + this.builderFactory = builderFactory; + } + + public TenantSourceBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + + List models = new ArrayList<>(); + for (TenantSourceEntity d : data) { + TenantSource m = new TenantSource(); + if (fields.hasField(this.asIndexer(TenantSource._url))) m.setUrl(d.getUrl()); + if (fields.hasField(this.asIndexer(TenantSource._codes))) m.setCodes(d.getCodes()); + if (fields.hasField(this.asIndexer(TenantSource._issuerUrl))) m.setIssuerUrl(d.getIssuerUrl()); + if (fields.hasField(this.asIndexer(TenantSource._clientId))) m.setClientId(d.getClientId()); + if (fields.hasField(this.asIndexer(TenantSource._clientSecret))) m.setClientSecret(d.getClientSecret()); + if (fields.hasField(this.asIndexer(TenantSource._scope))) m.setScope(d.getScope()); + + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/BaseCensor.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/BaseCensor.java new file mode 100644 index 000000000..12d1b9360 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/BaseCensor.java @@ -0,0 +1,22 @@ +package gr.cite.notification.model.censorship; + +import gr.cite.notification.convention.ConventionService; +import gr.cite.tools.data.censor.Censor; +import gr.cite.tools.fieldset.FieldSet; + +public class BaseCensor implements Censor { + + protected final ConventionService conventionService; + + public BaseCensor(ConventionService conventionService){ + this.conventionService = conventionService; + } + + protected Boolean isEmpty(FieldSet fields) { + return fields == null || fields.isEmpty(); + } + + protected String asIndexerPrefix(String part){ + return this.conventionService.asPrefix(part); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantCensor.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantCensor.java new file mode 100644 index 000000000..4440362e2 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantCensor.java @@ -0,0 +1,32 @@ +package gr.cite.notification.model.censorship; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.convention.ConventionService; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantCensor extends BaseCensor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantCensor.class)); + private final AuthorizationService authService; + + @Autowired + public TenantCensor(ConventionService conventionService, AuthorizationService authService) { + super(conventionService); + this.authService = authService; + } + + public void censor(FieldSet fields) { + logger.debug(new DataLogEntry("censoring fields", fields)); + if (this.isEmpty(fields)) return; + this.authService.authorizeForce(Permission.BrowseTenant); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantConfigurationCensor.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantConfigurationCensor.java new file mode 100644 index 000000000..d3b099193 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/TenantConfigurationCensor.java @@ -0,0 +1,32 @@ +package gr.cite.notification.model.censorship; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.convention.ConventionService; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantConfigurationCensor extends BaseCensor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationCensor.class)); + private final AuthorizationService authService; + + @Autowired + public TenantConfigurationCensor(ConventionService conventionService, AuthorizationService authService) { + super(conventionService); + this.authService = authService; + } + + public void censor(FieldSet fields) { + logger.debug(new DataLogEntry("censoring fields", fields)); + if (this.isEmpty(fields)) return; + this.authService.authorizeForce(Permission.BrowseTenantConfiguration); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/UserCensor.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/UserCensor.java new file mode 100644 index 000000000..ffe0677b3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/censorship/UserCensor.java @@ -0,0 +1,50 @@ +package gr.cite.notification.model.censorship; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.OwnedResource; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.model.User; +import gr.cite.tools.data.censor.CensorFactory; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.UUID; + + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserCensor extends BaseCensor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserCensor.class)); + + protected final AuthorizationService authService; + protected final CensorFactory censorFactory; + + @Autowired + public UserCensor( + ConventionService conventionService, + AuthorizationService authService, + CensorFactory censorFactory + ) { + super(conventionService); + this.authService = authService; + this.censorFactory = censorFactory; + } + + public void censor(FieldSet fields, UUID userId) throws MyForbiddenException { + logger.debug(new DataLogEntry("censoring fields", fields)); + if (this.isEmpty(fields)) return; + this.authService.authorizeAtLeastOneForce(userId != null ? List.of(new OwnedResource(userId)) : null, Permission.BrowseUser); + FieldSet tenantUsersFields = fields.extractPrefixed(this.asIndexerPrefix(User._tenantUsers)); + //FieldSet indicatorAccessesFields = fields.extractPrefixed(this.asIndexerPrefix(User._indicatorAccesses)); + //this.censorFactory.censor(IndicatorAccessCensor.class).censor(indicatorAccessesFields, userId); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantConfigurationDeleter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantConfigurationDeleter.java new file mode 100644 index 000000000..ffbb084fc --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantConfigurationDeleter.java @@ -0,0 +1,78 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.query.TenantConfigurationQuery; +import gr.cite.tools.data.deleter.Deleter; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantConfigurationDeleter implements Deleter { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationDeleter.class)); + + private final TenantScopedEntityManager entityManager; + protected final QueryFactory queryFactory; + private final DeleterFactory deleterFactory; + + @Autowired + public TenantConfigurationDeleter( + TenantScopedEntityManager entityManager, + QueryFactory queryFactory, + DeleterFactory deleterFactory + ) { + this.entityManager = entityManager; + this.queryFactory = queryFactory; + this.deleterFactory = deleterFactory; + } + + public void deleteAndSaveByIds(List ids) throws InvalidApplicationException { + logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids)); + List data = this.queryFactory.query(TenantConfigurationQuery.class).ids(ids).collect(); + logger.trace("received {} items", Optional.of(data).map(List::size).orElse(0)); + this.deleteAndSave(data); + } + + public void deleteAndSave(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.delete(data); + logger.trace("saving changes"); + this.entityManager.flush(); + logger.trace("changes saved"); + } + + public void delete(List datas) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(datas).map(List::size).orElse(0)); + if (datas == null || datas.isEmpty()) return; + + List ids = datas.stream().map(TenantConfigurationEntity::getId).distinct().collect(Collectors.toList()); + + Instant now = Instant.now(); + + for (TenantConfigurationEntity item : datas) { + logger.trace("deleting item {}", item.getId()); + item.setIsActive(IsActive.Inactive); + item.setUpdatedAt(now); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantDeleter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantDeleter.java new file mode 100644 index 000000000..db4900766 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/TenantDeleter.java @@ -0,0 +1,75 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.query.TenantQuery; +import gr.cite.tools.data.deleter.Deleter; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantDeleter implements Deleter { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantDeleter.class)); + + private final TenantScopedEntityManager entityManager; + protected final QueryFactory queryFactory; + private final DeleterFactory deleterFactory; + + @Autowired + public TenantDeleter( + TenantScopedEntityManager entityManager, + QueryFactory queryFactory, + DeleterFactory deleterFactory + ) { + this.entityManager = entityManager; + this.queryFactory = queryFactory; + this.deleterFactory = deleterFactory; + } + + public void deleteAndSaveByIds(List ids) throws InvalidApplicationException { + logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids)); + List data = this.queryFactory.query(TenantQuery.class).ids(ids).collect(); + logger.trace("received {} items", Optional.of(data).map(List::size).orElse(0)); + this.deleteAndSave(data); + } + + public void deleteAndSave(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.delete(data); + logger.trace("saving changes"); + this.entityManager.flush(); + logger.trace("changes saved"); + } + + public void delete(List datas) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(datas).map(List::size).orElse(0)); + if (datas == null || datas.isEmpty()) return; + + Instant now = Instant.now(); + + for (TenantEntity item : datas) { + logger.trace("deleting item {}", item.getId()); + item.setIsActive(IsActive.Inactive); + item.setUpdatedAt(now); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserCredentialDeleter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserCredentialDeleter.java new file mode 100644 index 000000000..b29145af9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserCredentialDeleter.java @@ -0,0 +1,74 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.query.UserCredentialQuery; +import gr.cite.tools.data.deleter.Deleter; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserCredentialDeleter implements Deleter { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserCredentialDeleter.class)); + + private final TenantScopedEntityManager entityManager; + + private final QueryFactory queryFactory; + + @Autowired + public UserCredentialDeleter( + TenantScopedEntityManager entityManager, + QueryFactory queryFactory + ) { + this.entityManager = entityManager; + this.queryFactory = queryFactory; + } + + public void deleteAndSaveByIds(List ids) throws InvalidApplicationException { + logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids)); + List data = this.queryFactory.query(UserCredentialQuery.class).ids(ids).collect(); + logger.trace("retrieved {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.deleteAndSave(data); + } + + public void deleteAndSave(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.delete(data); + logger.trace("saving changes"); + this.entityManager.flush(); + logger.trace("changes saved"); + } + + public void delete(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + if (data == null || data.isEmpty()) + return; + + Instant now = Instant.now(); + + for (UserCredentialEntity item : data) { + logger.trace("deleting item {}", item); + item.setIsActive(IsActive.Inactive); + item.setUpdatedAt(now); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java new file mode 100644 index 000000000..09ad26512 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java @@ -0,0 +1,90 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.query.UserCredentialQuery; +import gr.cite.notification.query.UserQuery; +import gr.cite.tools.data.deleter.Deleter; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import jakarta.persistence.EntityManager; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserDeleter implements Deleter { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserDeleter.class)); + + private final EntityManager entityManager; + + protected final QueryFactory queryFactory; + + protected final DeleterFactory deleterFactory; + + @Autowired + public UserDeleter( + EntityManager entityManager, + QueryFactory queryFactory, + DeleterFactory deleterFactory + ) { + this.entityManager = entityManager; + this.queryFactory = queryFactory; + this.deleterFactory = deleterFactory; + } + + public void deleteAndSaveByIds(List ids) throws InvalidApplicationException { + logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids)); + List data = this.queryFactory.query(UserQuery.class).ids(ids).collect(); + logger.trace("retrieved {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.deleteAndSave(data); + } + + public void deleteAndSave(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + this.delete(data); + logger.trace("saving changes"); + this.entityManager.flush(); + logger.trace("changes saved"); + } + + public void delete(List data) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0)); + if (data == null || data.isEmpty()) + return; + + List ids = data.stream().map(UserEntity::getId).distinct().collect(Collectors.toList()); + + { + logger.debug("checking related - {}", UserCredentialEntity.class.getSimpleName()); + List items = this.queryFactory.query(UserCredentialQuery.class).userIds(ids).collect(); + UserCredentialDeleter deleter = this.deleterFactory.deleter(UserCredentialDeleter.class); + deleter.delete(items); + } + + Instant now = Instant.now(); + + for (UserEntity item : data) { + logger.trace("deleting item {}", item.getId()); + item.setIsActive(IsActive.Inactive); + item.setUpdatedAt(now); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/TenantTouchedIntegrationEventPersist.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/TenantTouchedIntegrationEventPersist.java new file mode 100644 index 000000000..e8932c3de --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/TenantTouchedIntegrationEventPersist.java @@ -0,0 +1,78 @@ +package gr.cite.notification.model.persist; + +import gr.cite.notification.common.validation.BaseValidator; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class TenantTouchedIntegrationEventPersist { + + private UUID id; + public static final String _id = "id"; + + private String code; + public static final String _code = "code"; + public static final int _codeLength = 50; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Component(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class TenantTouchedIntegrationEventPersistValidator extends BaseValidator { + + public static final String ValidatorName = "TenantTouchedIntegrationEventPersistValidator"; + + private final MessageSource messageSource; + + protected TenantTouchedIntegrationEventPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return TenantTouchedIntegrationEventPersist.class; + } + + @Override + protected List specifications(TenantTouchedIntegrationEventPersist item) { + return Arrays.asList( + this.spec() + .iff(() -> !this.isNull(item.getId())) + .must(() -> this.isValidGuid(item.getId())) + .failOn(TenantTouchedIntegrationEventPersist._id).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantTouchedIntegrationEventPersist._id}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getCode())) + .failOn(TenantTouchedIntegrationEventPersist._code).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantTouchedIntegrationEventPersist._code}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getCode())) + .must(() -> this.lessEqualLength(item.getCode(), TenantTouchedIntegrationEventPersist._codeLength)) + .failOn(TenantTouchedIntegrationEventPersist._code).failWith(messageSource.getMessage("Validation_MaxLength", new Object[]{TenantTouchedIntegrationEventPersist._code}, LocaleContextHolder.getLocale())) + ); + } + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java new file mode 100644 index 000000000..0d658d200 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java @@ -0,0 +1,80 @@ +package gr.cite.notification.model.persist; + +import gr.cite.notification.common.validation.BaseValidator; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.integrationevent.inbox.usertouched.UserTouchedIntegrationEvent; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class UserTouchedIntegrationEventPersist { + + private UUID id; + public static final String _id = "id"; + + private String name; + + public static final String _name = "name"; + + public static final int _nameLength = 200; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Component(UserTouchedIntegrationEventPersist.UserTouchedIntegrationEventPersistValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class UserTouchedIntegrationEventPersistValidator extends BaseValidator { + + public static final String ValidatorName = "UserTouchedIntegrationEventPersistValidator"; + + private final MessageSource messageSource; + + protected UserTouchedIntegrationEventPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return UserTouchedIntegrationEventPersist.class; + } + + @Override + protected List specifications(UserTouchedIntegrationEventPersist item) { + return Arrays.asList( + this.spec() + .iff(() -> !this.isNull(item.getId())) + .must(() -> this.isValidGuid(item.getId())) + .failOn(UserTouchedIntegrationEvent._id).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEventPersist._id}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getName())) + .failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEventPersist._name}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getName())) + .must(() -> this.lessEqualLength(item.getName(), UserTouchedIntegrationEvent._nameLength)) + .failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_MaxLength", new Object[]{UserTouchedIntegrationEventPersist._name}, LocaleContextHolder.getLocale())) + ); + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java new file mode 100644 index 000000000..7f3063e6d --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java @@ -0,0 +1,177 @@ +package gr.cite.notification.model.persist.tenantconfiguration; + +import gr.cite.notification.common.validation.BaseValidator; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class TenantConfigurationEmailClientPersist { + private UUID id; + public static final String _id = "id"; + private Boolean requireCredentials; + public static final String _requireCredentials = "requireCredentials"; + private Boolean enableSSL; + public static final String _enableSSL = "enableSSL"; + private String certificatePath; + public static final String _certificatePath = "certificatePath"; + private String hostServer; + public static final String _hostServer = "hostServer"; + private Integer hostPortNo; + public static final String _hostPortNo = "hostPortNo"; + private String emailAddress; + public static final String _emailAddress = "emailAddress"; + private String emailUserName; + public static final String _emailUserName = "emailUserName"; + private String emailPassword; + public static final String _emailPassword = "emailPassword"; + private String hash; + public static final String _hash = "hash"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Boolean getRequireCredentials() { + return requireCredentials; + } + + public void setRequireCredentials(Boolean requireCredentials) { + this.requireCredentials = requireCredentials; + } + + public Boolean getEnableSSL() { + return enableSSL; + } + + public void setEnableSSL(Boolean enableSSL) { + this.enableSSL = enableSSL; + } + + public String getCertificatePath() { + return certificatePath; + } + + public void setCertificatePath(String certificatePath) { + this.certificatePath = certificatePath; + } + + public String getHostServer() { + return hostServer; + } + + public void setHostServer(String hostServer) { + this.hostServer = hostServer; + } + + public Integer getHostPortNo() { + return hostPortNo; + } + + public void setHostPortNo(Integer hostPortNo) { + this.hostPortNo = hostPortNo; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getEmailUserName() { + return emailUserName; + } + + public void setEmailUserName(String emailUserName) { + this.emailUserName = emailUserName; + } + + public String getEmailPassword() { + return emailPassword; + } + + public void setEmailPassword(String emailPassword) { + this.emailPassword = emailPassword; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + @Component(TenantConfigurationEmailClientPersist.TenantConfigurationEmailClientPersistValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class TenantConfigurationEmailClientPersistValidator extends BaseValidator { + + public static final String ValidatorName = "TenantConfigurationEmailClientPersistValidator"; + + private final MessageSource messageSource; + + protected TenantConfigurationEmailClientPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return TenantConfigurationEmailClientPersist.class; + } + + @Override + protected List specifications(TenantConfigurationEmailClientPersist item) { + return Arrays.asList( + this.spec() + .iff(() -> this.isValidGuid(item.getId())) + .must(() -> this.isValidHash(item.getHash())) + .failOn(TenantConfigurationEmailClientPersist._hash).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._hash}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isValidGuid(item.getId())) + .must(() -> !this.isValidHash(item.getHash())) + .failOn(TenantConfigurationEmailClientPersist._hash).failWith(messageSource.getMessage("Validation_OverPosting", new Object[]{}, LocaleContextHolder.getLocale())), + + this.spec() + .must(() -> !this.isNull(item.getRequireCredentials())) + .failOn(TenantConfigurationEmailClientPersist._requireCredentials).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._requireCredentials}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getEnableSSL())) + .failOn(TenantConfigurationEmailClientPersist._enableSSL).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._enableSSL}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getCertificatePath())) + .failOn(TenantConfigurationEmailClientPersist._certificatePath).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._certificatePath}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getHostServer())) + .failOn(TenantConfigurationEmailClientPersist._hostServer).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._hostServer}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getHostPortNo())) + .failOn(TenantConfigurationEmailClientPersist._hostPortNo).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._hostPortNo}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getEmailAddress())) + .failOn(TenantConfigurationEmailClientPersist._emailAddress).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._emailAddress}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getEmailUserName())) + .failOn(TenantConfigurationEmailClientPersist._emailUserName).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._emailUserName}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getEmailPassword())) + .failOn(TenantConfigurationEmailClientPersist._emailPassword).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationEmailClientPersist._emailPassword}, LocaleContextHolder.getLocale())) + + ); + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java new file mode 100644 index 000000000..a6e15c081 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java @@ -0,0 +1,92 @@ +package gr.cite.notification.model.persist.tenantconfiguration; + +import gr.cite.notification.common.validation.BaseValidator; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +public class TenantConfigurationUserLocaleIntegrationPersist { + private String language; + public static final String _language = "language"; + private String timeZone; + public static final String _timeZone = "timeZone"; + private String culture; + public static final String _culture = "culture"; + + private String hash; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + @Component(TenantConfigurationUserLocaleIntegrationPersist.TenantConfigurationUserLocaleIntegrationPersistValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class TenantConfigurationUserLocaleIntegrationPersistValidator extends BaseValidator { + + public static final String ValidatorName = "TenantConfigurationUserLocaleIntegrationPersistValidator"; + + private final MessageSource messageSource; + + protected TenantConfigurationUserLocaleIntegrationPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return TenantConfigurationUserLocaleIntegrationPersist.class; + } + + @Override + protected List specifications(TenantConfigurationUserLocaleIntegrationPersist item) { + return Arrays.asList( + this.spec() + .must(() -> !this.isNull(item.getLanguage())) + .failOn(TenantConfigurationUserLocaleIntegrationPersist._language).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationUserLocaleIntegrationPersist._language}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getTimeZone())) + .failOn(TenantConfigurationUserLocaleIntegrationPersist._timeZone).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationUserLocaleIntegrationPersist._timeZone}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getCulture())) + .failOn(TenantConfigurationUserLocaleIntegrationPersist._culture).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationUserLocaleIntegrationPersist._culture}, LocaleContextHolder.getLocale())) + + ); + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java new file mode 100644 index 000000000..cd55d9c64 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java @@ -0,0 +1,28 @@ +package gr.cite.notification.model.tenantconfig; + + +public class TenantConfig { + + public static final String _deposit = "deposit"; + private TenantDepositConfig deposit; + + public static final String _fileTransformers = "fileTransformers"; + private TenantFileTransformersConfig fileTransformers; + + public TenantDepositConfig getDeposit() { + return deposit; + } + + public void setDeposit(TenantDepositConfig deposit) { + this.deposit = deposit; + } + + public TenantFileTransformersConfig getFileTransformers() { + return fileTransformers; + } + + public void setFileTransformers(TenantFileTransformersConfig fileTransformers) { + this.fileTransformers = fileTransformers; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantDepositConfig.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantDepositConfig.java new file mode 100644 index 000000000..2a0517b64 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantDepositConfig.java @@ -0,0 +1,17 @@ +package gr.cite.notification.model.tenantconfig; + +import java.util.List; + +public class TenantDepositConfig { + + public static final String _sources = "sources"; + private List sources; + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantFileTransformersConfig.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantFileTransformersConfig.java new file mode 100644 index 000000000..c7b6c0cc2 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantFileTransformersConfig.java @@ -0,0 +1,17 @@ +package gr.cite.notification.model.tenantconfig; + +import java.util.List; + +public class TenantFileTransformersConfig { + + public static final String _sources = "sources"; + private List sources; + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantSource.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantSource.java new file mode 100644 index 000000000..5003b7781 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/tenantconfig/TenantSource.java @@ -0,0 +1,73 @@ +package gr.cite.notification.model.tenantconfig; + + +import java.util.List; + +public class TenantSource { + + public static final String _url = "url"; + private String url; + + public static final String _codes = "codes"; + private List codes; + + public static final String _issuerUrl = "issuerUrl"; + private String issuerUrl; + + public static final String _clientId = "clientId"; + private String clientId; + + public static final String _clientSecret = "clientSecret"; + private String clientSecret; + + public static final String _scope = "scope"; + private String scope; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public List getCodes() { + return codes; + } + + public void setCodes(List codes) { + this.codes = codes; + } + + public String getIssuerUrl() { + return issuerUrl; + } + + public void setIssuerUrl(String issuerUrl) { + this.issuerUrl = issuerUrl; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/model/user/PublicUser.java b/annotation-service/annotation/src/main/java/gr/cite/notification/model/user/PublicUser.java new file mode 100644 index 000000000..0e683ce0d --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/model/user/PublicUser.java @@ -0,0 +1,29 @@ +package gr.cite.notification.model.user; + + +import java.util.UUID; + +public class PublicUser { + + public static final String _id = "id"; + private UUID id; + + public static final String _name = "name"; + private String name = null; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueInboxQuery.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueInboxQuery.java new file mode 100644 index 000000000..f33bbe037 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueInboxQuery.java @@ -0,0 +1,219 @@ +package gr.cite.notification.query; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.QueueInboxEntity; +import gr.cite.queueinbox.entity.QueueInboxStatus; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.Ordering; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class QueueInboxQuery extends QueryBase { + + private Collection ids; + private Instant createdAfter; + private Collection isActives; + private Collection exchanges; + private Collection routes; + private Collection status; + private Integer retryThreshold; + + public QueueInboxQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public QueueInboxQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public QueueInboxQuery ids(List value) { + this.ids = value; + return this; + } + + public QueueInboxQuery isActives(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public QueueInboxQuery isActives(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public QueueInboxQuery isActives(List value) { + this.isActives = value; + return this; + } + + public QueueInboxQuery exchanges(String value) { + this.exchanges = List.of(value); + return this; + } + + public QueueInboxQuery exchanges(String... value) { + this.exchanges = Arrays.asList(value); + return this; + } + + public QueueInboxQuery exchanges(List value) { + this.exchanges = value; + return this; + } + + public QueueInboxQuery routes(String value) { + this.routes = List.of(value); + return this; + } + + public QueueInboxQuery routes(String... value) { + this.routes = Arrays.asList(value); + return this; + } + + public QueueInboxQuery routes(List value) { + this.routes = value; + return this; + } + + public QueueInboxQuery status(QueueInboxStatus value) { + this.status = List.of(value); + return this; + } + + public QueueInboxQuery status(QueueInboxStatus... value) { + this.status = Arrays.asList(value); + return this; + } + + public QueueInboxQuery status(List value) { + this.status = value; + return this; + } + + public QueueInboxQuery createdAfter(Instant value) { + this.createdAfter = value; + return this; + } + + public QueueInboxQuery retryThreshold(Integer value) { + this.retryThreshold = value; + return this; + } + + public QueueInboxQuery ordering(Ordering ordering) { + this.setOrder(ordering); + return this; + } + + @Override + protected Class entityClass() { + return QueueInboxEntity.class; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.ids) || this.isEmpty(this.isActives) || this.isEmpty(this.exchanges) + || this.isEmpty(this.routes) || this.isEmpty(this.status); + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueInboxEntity._id)); + for (UUID item : this.ids) inClause.value(item); + predicates.add(inClause); + } + + if (this.createdAfter != null) { + predicates.add(queryContext.CriteriaBuilder.greaterThan(queryContext.Root.get(QueueInboxEntity._createdAt), this.createdAfter)); + } + if (this.isActives != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueInboxEntity._isActive)); + for (IsActive item : this.isActives) inClause.value(item); + predicates.add(inClause); + } + if (this.exchanges != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueInboxEntity._exchange)); + for (String item : this.exchanges) inClause.value(item); + predicates.add(inClause); + } + if (this.routes != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueInboxEntity._route)); + for (String item : this.routes) inClause.value(item); + predicates.add(inClause); + } + + if (this.status != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueInboxEntity._status)); + for (QueueInboxStatus item : this.status) inClause.value(item); + predicates.add(inClause); + } + + if (this.retryThreshold != null) { + predicates.add(queryContext.CriteriaBuilder.or(queryContext.CriteriaBuilder.isNull(queryContext.Root.get(QueueInboxEntity._retryCount)), + queryContext.CriteriaBuilder.lessThanOrEqualTo(queryContext.Root.get(QueueInboxEntity._retryCount), this.retryThreshold))); + } + + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected QueueInboxEntity convert(Tuple tuple, Set columns) { + QueueInboxEntity item = new QueueInboxEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._id, UUID.class)); + item.setExchange(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._exchange, String.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._tenantId, UUID.class)); + item.setRoute(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._route, String.class)); + item.setMessage(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._message, String.class)); + item.setMessageId(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._messageId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._createdAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._isActive, IsActive.class)); + item.setStatus(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._status, QueueInboxStatus.class)); + item.setRetryCount(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._retryCount, Integer.class)); + item.setQueue(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._queue, String.class)); + item.setApplicationId(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._applicationId, String.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, QueueInboxEntity._updatedAt, Instant.class)); + return item; + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(QueueInboxEntity._id)) return QueueInboxEntity._id; + else if (item.match(QueueInboxEntity._exchange)) return QueueInboxEntity._exchange; + else if (item.match(QueueInboxEntity._tenantId)) return QueueInboxEntity._tenantId; + else if (item.match(QueueInboxEntity._route)) return QueueInboxEntity._route; + else if (item.match(QueueInboxEntity._message)) return QueueInboxEntity._message; + else if (item.match(QueueInboxEntity._messageId)) return QueueInboxEntity._messageId; + else if (item.match(QueueInboxEntity._createdAt)) return QueueInboxEntity._createdAt; + else if (item.match(QueueInboxEntity._isActive)) return QueueInboxEntity._isActive; + else if (item.match(QueueInboxEntity._status)) return QueueInboxEntity._status; + else if (item.match(QueueInboxEntity._retryCount)) return QueueInboxEntity._retryCount; + else if (item.match(QueueInboxEntity._queue)) return QueueInboxEntity._queue; + else if (item.match(QueueInboxEntity._applicationId)) return QueueInboxEntity._applicationId; + else if (item.match(QueueInboxEntity._createdAt)) return QueueInboxEntity._createdAt; + else if (item.match(QueueInboxEntity._updatedAt)) return QueueInboxEntity._updatedAt; + else return null; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java new file mode 100644 index 000000000..36f0584cf --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java @@ -0,0 +1,235 @@ +package gr.cite.notification.query; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.QueueOutboxEntity; +import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.Ordering; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class QueueOutboxQuery extends QueryBase { + + private Collection ids; + private Instant createdAfter; + private Collection isActives; + private Collection exchanges; + private Collection routes; + private Collection notifyStatus; + private Integer retryThreshold; + private Integer confirmTimeout; + + public QueueOutboxQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public QueueOutboxQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public QueueOutboxQuery ids(List value) { + this.ids = value; + return this; + } + + public QueueOutboxQuery isActives(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public QueueOutboxQuery isActives(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public QueueOutboxQuery isActives(List value) { + this.isActives = value; + return this; + } + + public QueueOutboxQuery exchanges(String value) { + this.exchanges = List.of(value); + return this; + } + + public QueueOutboxQuery exchanges(String... value) { + this.exchanges = Arrays.asList(value); + return this; + } + + public QueueOutboxQuery exchanges(List value) { + this.exchanges = value; + return this; + } + + public QueueOutboxQuery routes(String value) { + this.routes = List.of(value); + return this; + } + + public QueueOutboxQuery routes(String... value) { + this.routes = Arrays.asList(value); + return this; + } + + public QueueOutboxQuery routes(List value) { + this.routes = value; + return this; + } + + public QueueOutboxQuery notifyStatus(QueueOutboxNotifyStatus value) { + this.notifyStatus = List.of(value); + return this; + } + + public QueueOutboxQuery notifyStatus(QueueOutboxNotifyStatus... value) { + this.notifyStatus = Arrays.asList(value); + return this; + } + + public QueueOutboxQuery notifyStatus(List value) { + this.notifyStatus = value; + return this; + } + + public QueueOutboxQuery createdAfter(Instant value) { + this.createdAfter = value; + return this; + } + + public QueueOutboxQuery retryThreshold(Integer value) { + this.retryThreshold = value; + return this; + } + + public QueueOutboxQuery confirmTimeout(Integer value) { + this.confirmTimeout = value; + return this; + } + + public QueueOutboxQuery ordering(Ordering ordering) { + this.setOrder(ordering); + return this; + } + + @Override + protected Class entityClass() { + return QueueOutboxEntity.class; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.ids) || this.isEmpty(this.isActives) || this.isEmpty(this.exchanges) + || this.isEmpty(this.routes) || this.isEmpty(this.notifyStatus); + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueOutboxEntity._id)); + for (UUID item : this.ids) inClause.value(item); + predicates.add(inClause); + } + + if (this.createdAfter != null) { + predicates.add(queryContext.CriteriaBuilder.greaterThan(queryContext.Root.get(QueueOutboxEntity._createdAt), this.createdAfter)); + } + if (this.isActives != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueOutboxEntity._isActive)); + for (IsActive item : this.isActives) inClause.value(item); + predicates.add(inClause); + } + if (this.exchanges != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueOutboxEntity._exchange)); + for (String item : this.exchanges) inClause.value(item); + predicates.add(inClause); + } + if (this.routes != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueOutboxEntity._route)); + for (String item : this.routes) inClause.value(item); + predicates.add(inClause); + } + + if (this.notifyStatus != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(QueueOutboxEntity._notifyStatus)); + for (QueueOutboxNotifyStatus item : this.notifyStatus) inClause.value(item); + predicates.add(inClause); + } + + if (this.retryThreshold != null) { + predicates.add(queryContext.CriteriaBuilder.or(queryContext.CriteriaBuilder.isNull(queryContext.Root.get(QueueOutboxEntity._retryCount)), + queryContext.CriteriaBuilder.lessThanOrEqualTo(queryContext.Root.get(QueueOutboxEntity._retryCount), this.retryThreshold))); + } + + if (this.confirmTimeout != null) { + predicates.add(queryContext.CriteriaBuilder.or(queryContext.CriteriaBuilder.isNull(queryContext.Root.get(QueueOutboxEntity._publishedAt)), + queryContext.CriteriaBuilder.and( + queryContext.CriteriaBuilder.isNotNull(queryContext.Root.get(QueueOutboxEntity._publishedAt)), + queryContext.CriteriaBuilder.isNull(queryContext.Root.get(QueueOutboxEntity._confirmedAt)), + queryContext.CriteriaBuilder.lessThan(queryContext.Root.get(QueueOutboxEntity._publishedAt), Instant.now().minusSeconds(this.confirmTimeout)) + ) + )); + } + + + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected QueueOutboxEntity convert(Tuple tuple, Set columns) { + QueueOutboxEntity item = new QueueOutboxEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._id, UUID.class)); + item.setExchange(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._exchange, String.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._tenantId, UUID.class)); + item.setRoute(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._route, String.class)); + item.setMessage(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._message, String.class)); + item.setMessageId(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._messageId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._createdAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._isActive, IsActive.class)); + item.setNotifyStatus(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._notifyStatus, QueueOutboxNotifyStatus.class)); + item.setRetryCount(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._retryCount, Integer.class)); + item.setPublishedAt(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._publishedAt, Instant.class)); + item.setConfirmedAt(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._confirmedAt, Instant.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, QueueOutboxEntity._updatedAt, Instant.class)); + return item; + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(QueueOutboxEntity._id)) return QueueOutboxEntity._id; + else if (item.match(QueueOutboxEntity._exchange)) return QueueOutboxEntity._exchange; + else if (item.match(QueueOutboxEntity._tenantId)) return QueueOutboxEntity._tenantId; + else if (item.match(QueueOutboxEntity._route)) return QueueOutboxEntity._route; + else if (item.match(QueueOutboxEntity._message)) return QueueOutboxEntity._message; + else if (item.match(QueueOutboxEntity._messageId)) return QueueOutboxEntity._messageId; + else if (item.match(QueueOutboxEntity._createdAt)) return QueueOutboxEntity._createdAt; + else if (item.match(QueueOutboxEntity._isActive)) return QueueOutboxEntity._isActive; + else if (item.match(QueueOutboxEntity._notifyStatus)) return QueueOutboxEntity._notifyStatus; + else if (item.match(QueueOutboxEntity._retryCount)) return QueueOutboxEntity._retryCount; + else if (item.match(QueueOutboxEntity._publishedAt)) return QueueOutboxEntity._publishedAt; + else if (item.match(QueueOutboxEntity._confirmedAt)) return QueueOutboxEntity._confirmedAt; + else if (item.match(QueueOutboxEntity._createdAt)) return QueueOutboxEntity._createdAt; + else if (item.match(QueueOutboxEntity._updatedAt)) return QueueOutboxEntity._updatedAt; + else return null; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java new file mode 100644 index 000000000..5a5a7ead9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java @@ -0,0 +1,129 @@ +package gr.cite.notification.query; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.Predicate; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class TenantConfigurationQuery extends QueryBase { + + private List ids; + + private List isActives; + + private List type; + + public TenantConfigurationQuery ids(UUID... ids) { + this.ids = Arrays.asList(ids); + return this; + } + + public TenantConfigurationQuery ids(List ids) { + this.ids = ids; + return this; + } + + public TenantConfigurationQuery isActive(IsActive... isActives) { + this.isActives = Arrays.asList(isActives); + return this; + } + + public TenantConfigurationQuery isActive(List isActive) { + this.isActives = isActive; + return this; + } + + public TenantConfigurationQuery type(TenantConfigurationType... type) { + this.type = Arrays.asList(type); + return this; + } + + public TenantConfigurationQuery type(List type) { + this.type = type; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.ids) || this.isEmpty(this.isActives); + } + + @Override + protected Class entityClass() { + return TenantConfigurationEntity.class; + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + predicates.add(queryContext.Root.get(TenantConfigurationEntity._id).in(ids)); + } + + if (this.isActives != null) { + predicates.add(queryContext.Root.get(TenantConfigurationEntity._isActive).in(isActives)); + } + + if (type != null) { + predicates.add(queryContext.Root.get(TenantConfigurationEntity._type).in(type)); + } + + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(TenantConfiguration._id)) + return TenantConfigurationEntity._id; + else if (item.match(TenantConfiguration._tenantId)) + return TenantConfigurationEntity._tenantId; + else if (item.match(TenantConfiguration._defaultUserLocaleData)) + return TenantConfiguration._defaultUserLocaleData; + else if (item.match(TenantConfiguration._emailClientData)) + return TenantConfiguration._emailClientData; + else if (item.match(TenantConfiguration._type)) + return TenantConfigurationEntity._type; + else if (item.match(TenantConfiguration._value)) + return TenantConfigurationEntity._value; + else if (item.match(TenantConfiguration._createdAt)) + return TenantConfigurationEntity._createdAt; + else if (item.match(TenantConfiguration._updatedAt)) + return TenantConfigurationEntity._updatedAt; + else if (item.match(TenantConfiguration._isActive)) + return TenantConfigurationEntity._isActive; + else + return null; + } + + @Override + protected TenantConfigurationEntity convert(Tuple tuple, Set columns) { + TenantConfigurationEntity item = new TenantConfigurationEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._id, UUID.class)); + item.setValue(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._value, String.class)); + item.setType(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._type, TenantConfigurationType.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._tenantId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity._isActive, IsActive.class)); + return item; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantQuery.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantQuery.java new file mode 100644 index 000000000..4ffd3bf0a --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/TenantQuery.java @@ -0,0 +1,131 @@ +package gr.cite.notification.query; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.model.Tenant; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.Predicate; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class TenantQuery extends QueryBase { + + private String like; + + private Collection ids; + + private Collection isActives; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + public TenantQuery like(String value) { + this.like = value; + return this; + } + + public TenantQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public TenantQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public TenantQuery ids(Collection values) { + this.ids = values; + return this; + } + + public TenantQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public TenantQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public TenantQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public TenantQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.ids) || this.isEmpty(this.isActives); + } + + @Override + protected Class entityClass() { + return TenantEntity.class; + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + predicates.add(queryContext.Root.get(TenantEntity._id).in(ids)); + } + + if (this.like != null && !this.like.isEmpty()) { + predicates.add(queryContext.CriteriaBuilder.like(queryContext.Root.get(TenantEntity._isActive), this.like)); + } + + if (this.isActives != null) { + predicates.add(queryContext.Root.get(TenantEntity._isActive).in(isActives)); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(Tenant._id)) + return TenantEntity._id; + else if (item.match(Tenant._code)) + return TenantEntity._code; + else if (item.match(Tenant._createdAt)) + return TenantEntity._createdAt; + else if (item.match(Tenant._updatedAt)) + return TenantEntity._updatedAt; + else if (item.match(Tenant._isActive)) + return TenantEntity._isActive; + else + return null; + } + + @Override + protected TenantEntity convert(Tuple tuple, Set columns) { + TenantEntity item = new TenantEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, TenantEntity._id, UUID.class)); + item.setCode(QueryBase.convertSafe(tuple, columns, TenantEntity._code, String.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, TenantEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, TenantEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, TenantEntity._isActive, IsActive.class)); + return item; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/UserCredentialQuery.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/UserCredentialQuery.java new file mode 100644 index 000000000..2ac910df3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/UserCredentialQuery.java @@ -0,0 +1,236 @@ +package gr.cite.notification.query; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.model.UserCredential; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserCredentialQuery extends QueryBase { + + private Collection ids; + + private Collection excludedIds; + + private Collection isActives; + + private Collection userIds; + + private Collection externalIds; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + private final UserScope userScope; + + private final AuthorizationService authService; + + public UserCredentialQuery(UserScope userScope, AuthorizationService authService) { + this.userScope = userScope; + this.authService = authService; + } + + public UserCredentialQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public UserCredentialQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public UserCredentialQuery ids(Collection values) { + this.ids = values; + return this; + } + + public UserCredentialQuery excludedIds(Collection values) { + this.excludedIds = values; + return this; + } + + public UserCredentialQuery excludedIds(UUID value) { + this.excludedIds = List.of(value); + return this; + } + + public UserCredentialQuery excludedIds(UUID... value) { + this.excludedIds = Arrays.asList(value); + return this; + } + + public UserCredentialQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public UserCredentialQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public UserCredentialQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public UserCredentialQuery userIds(UUID value) { + this.userIds = List.of(value); + return this; + } + + public UserCredentialQuery userIds(UUID... value) { + this.userIds = Arrays.asList(value); + return this; + } + + public UserCredentialQuery userIds(Collection values) { + this.userIds = values; + return this; + } + + public UserCredentialQuery externalIds(String value) { + this.externalIds = List.of(value); + return this; + } + + public UserCredentialQuery externalIds(String... value) { + this.externalIds = Arrays.asList(value); + return this; + } + + public UserCredentialQuery externalIds(Collection values) { + this.externalIds = values; + return this; + } + + public UserCredentialQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return + this.isEmpty(this.ids) || + this.isEmpty(this.userIds) || + this.isEmpty(this.externalIds) || + this.isEmpty(this.excludedIds); + } + + @Override + protected Class entityClass() { + return UserCredentialEntity.class; + } + + @Override + protected Predicate applyAuthZ(QueryContext queryContext) { + if (this.authorize.contains(AuthorizationFlags.None)) + return null; + if (this.authorize.contains(AuthorizationFlags.Permission) && this.authService.authorize(Permission.BrowseUser)) + return null; + UUID userId; + if (this.authorize.contains(AuthorizationFlags.Owner)) + userId = this.userScope.getUserIdSafe(); + else + userId = null; + + List predicates = new ArrayList<>(); + if (userId != null) { + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._userId)).value(userId)); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._id)); + for (UUID item : this.ids) + inClause.value(item); + predicates.add(inClause); + } + if (this.isActives != null) { + predicates.add(queryContext.Root.get(UserCredentialEntity._isActive).in(isActives)); + } + if (this.userIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._userId)); + for (UUID item : this.userIds) + inClause.value(item); + predicates.add(inClause); + } + if (this.excludedIds != null) { + CriteriaBuilder.In notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._id)); + for (UUID item : this.excludedIds) + notInClause.value(item); + predicates.add(notInClause.not()); + } + if (this.externalIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._externalId)); + for (String item : this.externalIds) + inClause.value(item); + predicates.add(inClause); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(UserCredential._id)) + return UserCredentialEntity._id; + else if (item.match(UserCredential._externalId)) + return UserCredentialEntity._externalId; + else if (item.prefix(UserCredential._user)) + return UserCredentialEntity._userId; + else if (item.match(UserCredential._user)) + return UserCredentialEntity._userId; + else if (item.match(UserCredential._createdAt)) + return UserCredentialEntity._createdAt; + else if (item.match(UserCredential._updatedAt)) + return UserCredentialEntity._updatedAt; + else if (item.match(UserCredential._isActive)) + return UserCredentialEntity._isActive; + else + return null; + } + + @Override + protected UserCredentialEntity convert(Tuple tuple, Set columns) { + UserCredentialEntity item = new UserCredentialEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._id, UUID.class)); + item.setExternalId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._externalId, String.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._userId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._createdAt, IsActive.class)); + return item; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/UserQuery.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/UserQuery.java new file mode 100644 index 000000000..26d778a1c --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/UserQuery.java @@ -0,0 +1,159 @@ +package gr.cite.notification.query; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.model.User; +import gr.cite.notification.model.user.PublicUser; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.time.Instant; +import java.util.*; + +@Component +@RequestScope +public class UserQuery extends QueryBase { + + private String like; + private Collection ids; + private Collection isActives; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + private final UserScope userScope; + private final AuthorizationService authService; + + public UserQuery( + UserScope userScope, + AuthorizationService authService + ) { + this.userScope = userScope; + this.authService = authService; + } + + public UserQuery like(String value) { + this.like = value; + return this; + } + + public UserQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public UserQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public UserQuery ids(Collection values) { + this.ids = values; + return this; + } + + public UserQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public UserQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public UserQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public UserQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Class entityClass() { + return UserEntity.class; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.ids) || this.isEmpty(this.isActives); + } + + @Override + protected Predicate applyAuthZ(QueryContext queryContext) { + if (this.authorize.contains(AuthorizationFlags.None)) return null; + if (this.authorize.contains(AuthorizationFlags.Permission) && this.authService.authorize(Permission.BrowseUser)) return null; + UUID ownerId = null; + if (this.authorize.contains(AuthorizationFlags.Owner)) ownerId = this.userScope.getUserIdSafe(); + + List predicates = new ArrayList<>(); + if (ownerId != null) { + predicates.add(queryContext.CriteriaBuilder.equal(queryContext.Root.get(User._id), ownerId)); + } + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserEntity._id)); + for (UUID item : this.ids) inClause.value(item); + predicates.add(inClause); + } + if (this.like != null && !this.like.isEmpty()) { + predicates.add(queryContext.CriteriaBuilder.like(queryContext.Root.get(UserEntity._name), this.like)); + } + if (this.isActives != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserEntity._isActive)); + for (IsActive item : this.isActives) inClause.value(item); + predicates.add(inClause); + } + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected UserEntity convert(Tuple tuple, Set columns) { + UserEntity item = new UserEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, UserEntity._id, UUID.class)); + item.setName(QueryBase.convertSafe(tuple, columns, UserEntity._name, String.class)); + item.setAdditionalInfo(QueryBase.convertSafe(tuple, columns, UserEntity._additionalInfo, String.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, UserEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, UserEntity._isActive, IsActive.class)); + return item; + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(User._id) || item.match(PublicUser._id)) return UserEntity._id; + else if (item.match(User._name) || item.match(PublicUser._name)) return UserEntity._name; + else if (item.match(User._createdAt) ) return UserEntity._createdAt; + else if (item.match(User._updatedAt)) return UserEntity._updatedAt; + else if (item.match(User._hash)) return UserEntity._updatedAt; + else if (item.match(User._isActive)) return UserEntity._isActive; + else return null; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java new file mode 100644 index 000000000..48e5963c3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java @@ -0,0 +1,52 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.query.TenantConfigurationQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.util.List; +import java.util.UUID; + +public class TenantConfigurationLookup extends Lookup { + private List ids; + private List isActives; + private List type; + + public List getIsActives() { + return isActives; + } + + public void setIsActives(List isActive) { + this.isActives = isActive; + } + + public List getIds() { + return ids; + } + + public void setIds(List ids) { + this.ids = ids; + } + + public List getType() { + return type; + } + + public void setType(List type) { + this.type = type; + } + + public TenantConfigurationQuery enrich(QueryFactory queryFactory) { + TenantConfigurationQuery query = queryFactory.query(TenantConfigurationQuery.class); + if (this.isActives != null) query.isActive(this.isActives); + if (this.ids != null) query.ids(this.ids); + if (this.type != null) query.type(this.type); + + this.enrichCommon(query); + + return query; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantLookup.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantLookup.java new file mode 100644 index 000000000..21886546e --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/TenantLookup.java @@ -0,0 +1,51 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.query.TenantQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.util.List; +import java.util.UUID; + +public class TenantLookup extends Lookup { + private String like; + private List isActive; + private List ids; + + public String getLike() { + return like; + } + + public void setLike(String like) { + this.like = like; + } + + public List getIsActive() { + return isActive; + } + + public void setIsActive(List isActive) { + this.isActive = isActive; + } + + public List getIds() { + return ids; + } + + public void setIds(List ids) { + this.ids = ids; + } + + public TenantQuery enrich(QueryFactory queryFactory) { + TenantQuery query = queryFactory.query(TenantQuery.class); + if (this.like != null) query.like(this.like); + if (this.isActive != null) query.isActive(this.isActive); + if (this.ids != null) query.ids(this.ids); + + this.enrichCommon(query); + + return query; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/UserLookup.java b/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/UserLookup.java new file mode 100644 index 000000000..f96b12909 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/query/lookup/UserLookup.java @@ -0,0 +1,52 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.query.UserQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.util.List; +import java.util.UUID; + +public class UserLookup extends Lookup { + private String like; + private List isActive; + private List ids; + + public String getLike() { + return like; + } + + public void setLike(String like) { + this.like = like; + } + + public List getIsActive() { + return isActive; + } + + public void setIsActive(List isActive) { + this.isActive = isActive; + } + + public List getIds() { + return ids; + } + + public void setIds(List ids) { + this.ids = ids; + } + + public UserQuery enrich(QueryFactory queryFactory) { + UserQuery query = queryFactory.query(UserQuery.class); + if (this.like != null) query.like(this.like); + if (this.isActive != null) query.isActive(this.isActive); + if (this.ids != null) query.ids(this.ids); + + this.enrichCommon(query); + + return query; + } + + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/AuthnSandbox.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/AuthnSandbox.java new file mode 100644 index 000000000..3e5a408b3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/AuthnSandbox.java @@ -0,0 +1,29 @@ +package gr.cite.notification.service; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +@Service +@RequestScope +public class AuthnSandbox { + + private final CurrentPrincipalResolver currentPrincipalResolver; + private final ClaimExtractor claimExtractor; + + @Autowired + public AuthnSandbox( + CurrentPrincipalResolver currentPrincipalResolver, + ClaimExtractor claimExtractor) { + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractor = claimExtractor; + } + + public String sayHi() { + MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); + return "Hi " + this.claimExtractor.name(principal); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingService.java new file mode 100644 index 000000000..eb4ddb426 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingService.java @@ -0,0 +1,26 @@ +package gr.cite.notification.service.formatting; + +import java.time.Instant; +import java.util.Locale; +import java.util.TimeZone; +import java.util.UUID; + +public interface FormattingService { + + String format(int value, UUID userId, String format, Locale locale); + + String format(int value, String format, Locale locale); + + String format(long value, UUID userId, String format, Locale locale); + + String format(long value, String format, Locale locale); + + String format(double value, UUID userId, Integer decimals, String format, Locale locale); + + String format(double value, Integer decimals, String format, Locale locale); + + String format(Instant value, UUID userId, TimeZone timezone, String format, Locale locale); + + String format(Instant value, TimeZone timeZone, String format, Locale locale); +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java new file mode 100644 index 000000000..a5eb69cfb --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java @@ -0,0 +1,171 @@ +package gr.cite.notification.service.formatting; + +import gr.cite.notification.cache.FormattingUserprofileCacheCacheService; +import gr.cite.notification.config.formatting.FormattingServiceProperties; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.locale.LocaleService; +import gr.cite.tools.data.query.QueryFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Locale; +import java.util.TimeZone; +import java.util.UUID; + +@Component +@RequestScope +public class FormattingServiceImpl implements FormattingService { + + private final QueryFactory queryFactory; + private final ConventionService conventionService; + private final LocaleService localeService; + private final FormattingServiceProperties properties; + private final FormattingUserprofileCacheCacheService formattingUserprofileCacheCacheService; + + @Autowired + public FormattingServiceImpl(QueryFactory queryFactory, + ConventionService conventionService, + LocaleService localeService, + FormattingServiceProperties properties, + FormattingUserprofileCacheCacheService formattingUserprofileCacheCacheService + ) { + this.queryFactory = queryFactory; + this.conventionService = conventionService; + this.localeService = localeService; + this.properties = properties; + this.formattingUserprofileCacheCacheService = formattingUserprofileCacheCacheService; + } + + public String format(int value, UUID userId, String format, Locale locale) { + return this.formatNonDecimal(value, userId, format, locale); + } + + private String formatNonDecimal(Number value,UUID userId, String format, Locale locale) { + Locale localeToUse = this.localeService.culture(); + if (locale != null) { + localeToUse = locale; + } else if (userId != null) { + FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue profile = this.getUserProfile(userId); + if (profile != null) { + localeToUse = this.localeService.cultureSafe(profile.getCulture()); + } + } + + String formatToUse = !this.conventionService.isNullOrEmpty(format) ? format : this.properties.getIntegerFormat(); + return this.formatNonDecimal(value, formatToUse, localeToUse); + } + + public String format(int value, String format, Locale locale) { + return this.formatNonDecimal(value, format, locale); + } + + private String formatNonDecimal(Number value, String format, Locale locale) { + if (this.conventionService.isNullOrEmpty(format) && locale != null) return NumberFormat.getInstance(locale).format(value); + else if (!this.conventionService.isNullOrEmpty(format) && locale == null) return String.format(format, value); + else if (!this.conventionService.isNullOrEmpty(format) && locale != null) return String.format(locale, format, value); + + return NumberFormat.getInstance(Locale.ROOT).format(value); + } + + public String format(long value, UUID userId, String format, Locale locale) { + return this.formatNonDecimal(value, userId, format, locale); + } + + public String format(long value, String format, Locale locale) { + return this.formatNonDecimal(value, format, locale); + } + + public String format(double value, UUID userId, Integer decimals, String format, Locale locale) { + Locale localeToUse = this.localeService.culture(); + if (locale != null) { + localeToUse = locale; + } else if (userId != null) { + FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue profile = this.getUserProfile(userId); + if (profile != null) { + localeToUse = this.localeService.cultureSafe(profile.getCulture()); + } + } + + String formatToUse = !this.conventionService.isNullOrEmpty(format) ? format : this.properties.getDecimalFormat(); + int decimalsToUse = decimals != null ? decimals : this.properties.getDecimalDigitsRound(); + return this.format(value, decimalsToUse, formatToUse, localeToUse); + } + + public String format(double value, Integer decimals, String format, Locale locale) { + double val = value; + if (decimals != null) { + BigDecimal bd = new BigDecimal(Double.toString(value)); + bd = bd.setScale(decimals, RoundingMode.HALF_UP); + val = bd.doubleValue(); + } + + if (this.conventionService.isNullOrEmpty(format) && locale != null) return NumberFormat.getInstance(locale).format(val); + else if (!this.conventionService.isNullOrEmpty(format) && locale == null) return new DecimalFormat(format).format(val); + else if (!this.conventionService.isNullOrEmpty(format) && locale != null) return new DecimalFormat(format, new DecimalFormatSymbols(locale)).format(val); + + return NumberFormat.getInstance(Locale.ROOT).format(val); + } + + public String format(Instant value, UUID userId, TimeZone timezone, String format, Locale locale) { + FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue profile = null; + if (userId != null && (locale == null || timezone == null)) { + profile = this.getUserProfile(userId); + } + + Locale localeToUse = this.localeService.culture(); + if (locale != null) { + localeToUse = locale; + } else if (userId != null) { + localeToUse = this.localeService.cultureSafe(profile.getCulture()); + } + + TimeZone timezoneToUse = this.localeService.timezone(); + if (timezone != null) { + timezoneToUse = timezone; + } else if (userId != null) { + timezoneToUse = this.localeService.timezoneSafe(profile.getZone()); + } + + String formatToUse = !this.conventionService.isNullOrEmpty(format) ? format : this.properties.getDateTimeFormat(); + return this.format(value, timezoneToUse, formatToUse, localeToUse); + } + + public String format(Instant value, TimeZone timeZone, String format, Locale locale) { + ZoneId zoneId = ZoneId.from(ZoneOffset.UTC); + + if (timeZone != null) { + zoneId = timeZone.toZoneId(); + } + if (this.conventionService.isNullOrEmpty(format) && locale != null) DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).withLocale(locale).withZone(zoneId).format(value); + else if (!this.conventionService.isNullOrEmpty(format) && locale == null) return DateTimeFormatter.ofPattern(format).withZone(zoneId).format(value); + else if (!this.conventionService.isNullOrEmpty(format) && locale != null) return DateTimeFormatter.ofPattern(format, locale).withZone(zoneId).format(value); + + return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).withZone(zoneId).format(value); + } + + private FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue getUserProfile(UUID userId) { + FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue cacheValue = this.formattingUserprofileCacheCacheService.lookup(this.formattingUserprofileCacheCacheService.buildKey(userId)); + if (cacheValue != null) { + return cacheValue; + } else { +// UserEntity user = this.queryFactory.query(UserQuery.class).ids(userId).firstAs(new BaseFieldSet().ensure(User._culture).ensure(User._language).ensure(User._timezone).ensure(User._id)); +// if (user == null) return null; +// cacheValue = new FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue(userId, user.getTimezone(), user.getCulture(), user.getLanguage()); +// this.formattingUserprofileCacheCacheService.put(cacheValue); + + return cacheValue; + } + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantService.java new file mode 100644 index 000000000..ef8af48f7 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantService.java @@ -0,0 +1,21 @@ +package gr.cite.notification.service.tenant; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.model.persist.TenantTouchedIntegrationEventPersist; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.FieldSet; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +public interface TenantService { + + Tenant persist(TenantTouchedIntegrationEventPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException; + + void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantServiceImpl.java new file mode 100644 index 000000000..8437565d8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenant/TenantServiceImpl.java @@ -0,0 +1,105 @@ +package gr.cite.notification.service.tenant; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.model.builder.TenantBuilder; +import gr.cite.notification.model.deleter.TenantDeleter; +import gr.cite.notification.model.persist.TenantTouchedIntegrationEventPersist; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import jakarta.persistence.EntityManager; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +@Service +public class TenantServiceImpl implements TenantService { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantServiceImpl.class)); + + private final AuthorizationService authorizationService; + + private final DeleterFactory deleterFactory; + + private final ConventionService conventionService; + + private final EntityManager entityManager; + + private final BuilderFactory builderFactory; + + public TenantServiceImpl(AuthorizationService authorizationService, + DeleterFactory deleterFactory, + ConventionService conventionService, + EntityManager entityManager, + BuilderFactory builderFactory) { + this.authorizationService = authorizationService; + this.deleterFactory = deleterFactory; + this.conventionService = conventionService; + this.entityManager = entityManager; + this.builderFactory = builderFactory; + } + + @Override + public Tenant persist(TenantTouchedIntegrationEventPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { + logger.debug(new MapLogEntry("persisting tenant").And("model", model).And("fields", fields)); + + this.authorizationService.authorizeForce(Permission.EditTenant); + + Boolean isValid = this.conventionService.isValidGuid(model.getId()); + + TenantEntity data; + if (isValid) { + data = this.entityManager.find(TenantEntity.class, model.getId()); + if (data == null) { + data = new TenantEntity(); + data.setId(model.getId()); + data.setCode(model.getCode()); + data.setIsActive(IsActive.Active); + data.setCreatedAt(Instant.now()); + + this.entityManager.persist(data); + } else { + data.setCode(model.getCode()); + data.setUpdatedAt(Instant.now()); + data.setIsActive(IsActive.Active); + + this.entityManager.merge(data); + } + } else { + throw new MyApplicationException("Not valid tenant id"); + } + + this.entityManager.flush(); + + return this.builderFactory.builder(TenantBuilder.class).authorize(EnumSet.of(AuthorizationFlags.None)).build(BaseFieldSet.build(fields, Tenant._id), data); + } + + @Override + public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug("deleting Tenant: {}", id); + + this.authorizationService.authorizeForce(Permission.DeleteTenant); + + this.deleterFactory.deleter(TenantDeleter.class).deleteAndSaveByIds(List.of(id)); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java new file mode 100644 index 000000000..bd3b0b9d9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java @@ -0,0 +1,20 @@ +package gr.cite.notification.service.tenantconfiguration; + +import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleConfigurationDataContainer; +import gr.cite.notification.common.types.tenantconfiguration.EmailClientConfigurationDataContainer; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationEmailClientPersist; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationUserLocaleIntegrationPersist; +import gr.cite.tools.fieldset.FieldSet; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +public interface TenantConfigurationService { + EmailClientConfigurationDataContainer collectTenantEmailClient(); + DefaultUserLocaleConfigurationDataContainer collectTenantUserLocale(); + TenantConfiguration persist(TenantConfigurationEmailClientPersist emailClientPersist, FieldSet fieldSet); + TenantConfiguration persist(TenantConfigurationUserLocaleIntegrationPersist userLocaleIntegrationPersist, FieldSet fieldSet); + void deleteAndSave(UUID id) throws InvalidApplicationException; + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java new file mode 100644 index 000000000..4941ec4f3 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java @@ -0,0 +1,207 @@ +package gr.cite.notification.service.tenantconfiguration; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleConfigurationDataContainer; +import gr.cite.notification.common.types.tenantconfiguration.EmailClientConfigurationDataContainer; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.notification.model.builder.TenantConfigurationBuilder; +import gr.cite.notification.model.deleter.TenantConfigurationDeleter; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationEmailClientPersist; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationUserLocaleIntegrationPersist; +import gr.cite.notification.query.TenantConfigurationQuery; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +@Component +@RequestScope +public class TenantConfigurationServiceImpl implements TenantConfigurationService { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationServiceImpl.class)); + + private final ApplicationContext applicationContext; + private final JsonHandlingService jsonHandlingService; + private final AuthorizationService authorizationService; + private final ConventionService conventionService; + private final ErrorThesaurusProperties errors; + private final MessageSource messageSource; + private final BuilderFactory builderFactory; + + private final TenantScopedEntityManager dbContext; + private final DeleterFactory deleterFactory; + + @Autowired + public TenantConfigurationServiceImpl(ApplicationContext applicationContext, JsonHandlingService jsonHandlingService, AuthorizationService authorizationService, ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, BuilderFactory builderFactory, TenantScopedEntityManager dbContext, DeleterFactory deleterFactory) { + this.applicationContext = applicationContext; + this.jsonHandlingService = jsonHandlingService; + this.authorizationService = authorizationService; + this.conventionService = conventionService; + this.errors = errors; + this.messageSource = messageSource; + this.builderFactory = builderFactory; + this.dbContext = dbContext; + this.deleterFactory = deleterFactory; + } + + @Override + public EmailClientConfigurationDataContainer collectTenantEmailClient() { + TenantConfigurationQuery query = applicationContext.getBean(TenantConfigurationQuery.class); + String data = query.isActive(IsActive.Active).type(TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION).first().getValue(); + if (data == null) return null; + + try { + EmailClientConfigurationDataContainer emailClientData = this.jsonHandlingService.fromJson(EmailClientConfigurationDataContainer.class, data); + return emailClientData; + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + @Override + public DefaultUserLocaleConfigurationDataContainer collectTenantUserLocale() { + TenantConfigurationQuery query = applicationContext.getBean(TenantConfigurationQuery.class); + TenantConfigurationEntity entity = query.isActive(IsActive.Active).type(TenantConfigurationType.DEFAULT_USER_LOCALE).first(); + if(entity == null){ + return null; + } + String data = entity.getValue(); + if (data == null) return null; + + try { + DefaultUserLocaleConfigurationDataContainer userLocaleData = this.jsonHandlingService.fromJson(DefaultUserLocaleConfigurationDataContainer.class, data); + return userLocaleData; + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + @Override + public TenantConfiguration persist(TenantConfigurationEmailClientPersist emailClientPersist, FieldSet fieldSet) { + EmailClientConfigurationDataContainer container = new EmailClientConfigurationDataContainer(); + container.setEnableSSL(emailClientPersist.getEnableSSL()); + container.setRequireCredentials(emailClientPersist.getRequireCredentials()); + container.setHostServer(emailClientPersist.getHostServer()); + container.setHostPortNo(emailClientPersist.getHostPortNo()); + container.setCertificatePath(emailClientPersist.getCertificatePath()); + container.setEmailAddress(emailClientPersist.getEmailAddress()); + container.setEmailUserName(emailClientPersist.getEmailUserName()); + container.setEmailPassword(emailClientPersist.getEmailPassword()); + try { + String value = jsonHandlingService.toJson(container); + return this.persist(emailClientPersist.getId(), emailClientPersist.getHash(), TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION, value, fieldSet); + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + @Override + public TenantConfiguration persist(TenantConfigurationUserLocaleIntegrationPersist userLocaleIntegrationPersist, FieldSet fieldSet) { + this.authorizationService.authorizeForce(Permission.EditTenantConfiguration); + + TenantConfigurationQuery tenantConfigurationQuery = applicationContext.getBean(TenantConfigurationQuery.class); + TenantConfigurationEntity data = tenantConfigurationQuery.isActive(IsActive.Active).type(TenantConfigurationType.DEFAULT_USER_LOCALE).first(); + Boolean isUpdate = data != null; + if (!isUpdate) { + + data = new TenantConfigurationEntity(); + data.setCreatedAt(Instant.now()); + data.setIsActive(IsActive.Active); + data.setType(TenantConfigurationType.DEFAULT_USER_LOCALE); + } + try { + DefaultUserLocaleConfigurationDataContainer container = new DefaultUserLocaleConfigurationDataContainer(); + container.setCulture(userLocaleIntegrationPersist.getCulture()); + container.setTimeZone(userLocaleIntegrationPersist.getTimeZone()); + container.setLanguage(userLocaleIntegrationPersist.getLanguage()); + String value = jsonHandlingService.toJson(container); + + data.setValue(value); + data.setUpdatedAt(Instant.now()); + this.dbContext.merge(data); + } catch (InvalidApplicationException | JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + + //this._eventBroker.EmitTenantConfigurationTouched(this._scope.Tenant, type); + + TenantConfiguration persisted = this.builderFactory.builder(TenantConfigurationBuilder.class).build(fieldSet.merge(new BaseFieldSet(TenantConfiguration._id, TenantConfiguration._hash)), data); + return persisted; + } + + @Override + public void deleteAndSave(UUID id) throws InvalidApplicationException { + logger.debug("deleting tenant Configuration: {}", id); + this.authorizationService.authorizeForce(Permission.DeleteNotification); + this.deleterFactory.deleter(TenantConfigurationDeleter.class).deleteAndSaveByIds(List.of(id)); + } + + private TenantConfiguration persist(UUID modelId, String modelHash, TenantConfigurationType type, String value, FieldSet fieldSet) { + this.authorizationService.authorizeForce(Permission.EditTenantConfiguration); + + Boolean isUpdate = this.conventionService.isValidGuid(modelId); + + TenantConfigurationQuery tenantConfigurationQuery = applicationContext.getBean(TenantConfigurationQuery.class); + List existingConfigIds = tenantConfigurationQuery.isActive(IsActive.Active).type(type).collectAs(new BaseFieldSet(TenantConfigurationEntity._id)).stream().map(TenantConfigurationEntity::getId).toList(); + TenantConfigurationEntity data = null; + if (isUpdate) { + if (!existingConfigIds.contains(modelId)) throw new MyValidationException(this.errors.getSingleTenantConfigurationPerTypeSupported().getCode(), this.errors.getSingleTenantConfigurationPerTypeSupported().getMessage()); + if (existingConfigIds.size() > 1) throw new MyValidationException(this.errors.getSingleTenantConfigurationPerTypeSupported().getCode(), this.errors.getSingleTenantConfigurationPerTypeSupported().getMessage()); + + + data = tenantConfigurationQuery.ids(modelId).first(); + if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{modelId, TenantConfigurationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (!modelHash.equals(this.conventionService.hashValue(data.getUpdatedAt()))) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); + if (!data.getType().equals(type)) throw new MyValidationException(this.errors.getIncompatibleTenantConfigurationTypes().getCode(), this.errors.getIncompatibleTenantConfigurationTypes().getMessage()); + } else { + if (!existingConfigIds.isEmpty()) throw new MyValidationException(this.errors.getSingleTenantConfigurationPerTypeSupported().getCode(), this.errors.getSingleTenantConfigurationPerTypeSupported().getMessage()); + + data = new TenantConfigurationEntity(); + data.setCreatedAt(Instant.now()); + data.setIsActive(IsActive.Active); + data.setType(type); + } + + data.setValue(value); + data.setUpdatedAt(Instant.now()); + + try { + this.dbContext.merge(data); + } catch (InvalidApplicationException e) { + logger.error(e.getMessage(), e); + } + + //this._eventBroker.EmitTenantConfigurationTouched(this._scope.Tenant, type); + + TenantConfiguration persisted = this.builderFactory.builder(TenantConfigurationBuilder.class).build(fieldSet.merge(new BaseFieldSet(TenantConfiguration._id, TenantConfiguration._hash)), data); + return persisted; + //return null; + + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserService.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserService.java new file mode 100644 index 000000000..3442efb49 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserService.java @@ -0,0 +1,21 @@ +package gr.cite.notification.service.user; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.notification.integrationevent.inbox.usertouched.UserTouchedIntegrationEvent; +import gr.cite.notification.model.User; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.FieldSet; + +import javax.management.InvalidApplicationException; +import java.util.UUID; + +public interface UserService { + + User persist(UserTouchedIntegrationEvent model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException; + + void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserServiceImpl.java new file mode 100644 index 000000000..28ea3ddb0 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/notification/service/user/UserServiceImpl.java @@ -0,0 +1,155 @@ +package gr.cite.notification.service.user; + +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.UserContactInfoEntity; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.integrationevent.inbox.usertouched.UserTouchedIntegrationEvent; +import gr.cite.notification.model.User; +import gr.cite.notification.model.builder.UserBuilder; +import gr.cite.notification.model.deleter.UserDeleter; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyForbiddenException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +@Service +public class UserServiceImpl implements UserService { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserServiceImpl.class)); + + private final AuthorizationService authorizationService; + + private final DeleterFactory deleterFactory; + + private final ConventionService conventionService; + + private final EntityManager entityManager; + + private final BuilderFactory builderFactory; + + private final QueryFactory queryFactory; + + private final JsonHandlingService jsonHandlingService; + + public UserServiceImpl(AuthorizationService authorizationService, + DeleterFactory deleterFactory, + ConventionService conventionService, + EntityManager entityManager, + BuilderFactory builderFactory, + QueryFactory queryFactory, + JsonHandlingService jsonHandlingService) { + this.authorizationService = authorizationService; + this.deleterFactory = deleterFactory; + this.conventionService = conventionService; + this.entityManager = entityManager; + this.builderFactory = builderFactory; + this.queryFactory = queryFactory; + this.jsonHandlingService = jsonHandlingService; + } + + @Override + @Transactional + public User persist(UserTouchedIntegrationEvent model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { + logger.debug(new MapLogEntry("persisting user").And("model", model).And("fields", fields)); + +// this.authorizationService.authorizeAtLeastOneForce(model.getId() != null ? List.of(new OwnedResource(model.getId())) : null, Permission.EditUser); + + Boolean isValid = this.conventionService.isValidGuid(model.getId()); + + UserEntity data; + if (isValid) { + data = this.entityManager.find(UserEntity.class, model.getId()); + if (data == null) { + data = new UserEntity(); + data.setId(model.getId()); + data.setName(model.getName()); + data.setAdditionalInfo(this.jsonHandlingService.toJson(model.getProfile())); + data.setCreatedAt(Instant.now()); + data.setUpdatedAt(Instant.now()); + data.setIsActive(IsActive.Active); + + this.entityManager.persist(data); + + for (UserTouchedIntegrationEvent.UserContactInfo eventC : model.getUserContactInfo()) { + this.entityManager.persist(this.buildContactInfoEntityFromEventData(eventC, model.getId())); + } + + this.entityManager.persist(this.buildUserCredentialEntityFromEventData(model)); + } else { + data.setName(model.getName()); + data.setAdditionalInfo(this.jsonHandlingService.toJson(model.getProfile())); + data.setUpdatedAt(Instant.now()); + data.setIsActive(IsActive.Active); + + this.entityManager.merge(data); + + } + } else { + throw new MyApplicationException("Not valid user id"); + } + + this.entityManager.flush(); + + return this.builderFactory.builder(UserBuilder.class).authorize(EnumSet.of(AuthorizationFlags.None)).build(BaseFieldSet.build(fields, User._id), data); + } + + @Override + public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug("deleting User: {}", id); + + this.authorizationService.authorizeForce(Permission.DeleteUser); + + this.deleterFactory.deleter(UserDeleter.class).deleteAndSaveByIds(List.of(id)); + } + + private UserContactInfoEntity buildContactInfoEntityFromEventData(UserTouchedIntegrationEvent.UserContactInfo eventC, UUID userId) { + UserContactInfoEntity contactInfo = new UserContactInfoEntity(); + contactInfo.setId(UUID.randomUUID()); + contactInfo.setUserId(userId); + contactInfo.setType(eventC.getType()); + contactInfo.setValue(eventC.getValue()); + contactInfo.setOrdinal(eventC.getOrdinal()); + contactInfo.setCreatedAt(Instant.now()); + contactInfo.setUpdatedAt(Instant.now()); + contactInfo.setIsActive(IsActive.Active); + + return contactInfo; + } + + private UserCredentialEntity buildUserCredentialEntityFromEventData(UserTouchedIntegrationEvent event) { + UserCredentialEntity credentialEntity = new UserCredentialEntity(); + credentialEntity.setId(UUID.randomUUID()); + credentialEntity.setUserId(event.getId()); + credentialEntity.setExternalId(event.getSubjectId()); + credentialEntity.setCreatedAt(Instant.now()); + credentialEntity.setUpdatedAt(Instant.now()); + credentialEntity.setIsActive(IsActive.Active); + + return credentialEntity; + } + +} diff --git a/annotation-service/pom.xml b/annotation-service/pom.xml new file mode 100644 index 000000000..186d500ba --- /dev/null +++ b/annotation-service/pom.xml @@ -0,0 +1,184 @@ + + + + 4.0.0 + gr.cite + annotation-service-parent + Argos Annotation Parent + 1.0.0 + pom + + + org.springframework.boot + spring-boot-starter-parent + 3.1.2 + + + + annotation + annotation-web + + + + 21 + 21 + 21 + 21 + + + + + org.springframework + spring-context-support + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.hibernate.orm + hibernate-core + compile + 6.3.1.Final + + + org.hibernate.orm + hibernate-c3p0 + compile + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + + + + com.fasterxml.jackson.core + jackson-core + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.13.3 + + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + + jakarta.annotation + jakarta.annotation-api + + + + + jakarta.validation + jakarta.validation-api + + + + jakarta.persistence + jakarta.persistence-api + 3.1.0 + + + + gr.cite + data-tools + 2.1.2 + + + + gr.cite + logging + 2.1.0 + + + + gr.cite + validation + 3.0.2 + + + + gr.cite + oidc-authn + 2.2.1 + + + gr.cite + oidc-authz + 2.1.0 + + + + gr.cite + rabbitmq-core + 2.1.1 + + + + gr.cite + queue-inbox + 1.0.0 + + + gr.cite + queue-outbox + 1.0.0 + + + + + + devel + + true + + + jar + + + + production + + jar + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + + profile + production + + + + + + \ No newline at end of file diff --git a/annotation-service/settings.xml b/annotation-service/settings.xml new file mode 100644 index 000000000..044f36492 --- /dev/null +++ b/annotation-service/settings.xml @@ -0,0 +1,12 @@ + + + org.sonarsource.scanner.maven + + + + cite-maven + ${server.username} + ${server.password} + + +