diff --git a/dmp-backend/notification-service/Dockerfile b/dmp-backend/notification-service/Dockerfile new file mode 100644 index 000000000..7530da159 --- /dev/null +++ b/dmp-backend/notification-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 notification /build/notification/ +COPY notification-web /build/notification-web/ +COPY settings.xml /root/.m2/settings.xml +RUN rm -f /build/notification-web/src/main/resources/config/app.env +RUN rm -f /build/notification-web/src/main/resources/config/*-dev.yml +RUN rm -f /build/notification-web/src/main/resources/logging/*.xml +RUN rm -f /build/notification-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/notification-web/target/sti-viewer-notification-web.jar /app/sti-viewer-notification-web.jar + +ENTRYPOINT ["java","-Dspring.config.additional-location=file:/config/","-Dspring.profiles.active=${PROFILE}","-Djava.security.egd=file:/dev/./urandom","-jar","/app/sti-viewer-notification-web.jar"] \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/pom.xml b/dmp-backend/notification-service/notification-web/pom.xml new file mode 100644 index 000000000..e90de6030 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + gr.cite + notification-service-parent + 1.0.0 + ../pom.xml + + + notification-web + jar + + + 11 + 11 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + 2.6.4 + + + + org.postgresql + postgresql + + + org.hibernate + hibernate-core + + + + gr.cite + notification + 1.0.0 + + + gr.cite + oidc-authz + 1.0.0 + + + gr.cite + cache + 1.0.0 + + + gr.cite + exceptions-web + 1.0.0 + + + gr.cite + cors-web + 1.0.0 + + + org.springframework.boot + spring-boot + 2.6.4 + + + org.springframework + spring-web + + + + + argos-notification-web + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/NotificationApplication.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/NotificationApplication.java new file mode 100644 index 000000000..164fdca37 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/NotificationApplication.java @@ -0,0 +1,21 @@ +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.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({ + "gr.cite.notification", + "gr.cite.tools", + "gr.cite.commons"}) +@EntityScan({ + "gr.cite.notification.data"}) +public class NotificationApplication { + + public static void main(String[] args) { + SpringApplication.run(NotificationApplication.class, args); + } + +} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java new file mode 100644 index 000000000..8f0f8367e --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java @@ -0,0 +1,40 @@ +package gr.cite.notification.web; + +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 gr.cite.notification.web.scope.user.UserInterceptor; +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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/authorization/OwnedAuthorizationHandler.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/authorization/OwnedAuthorizationHandler.java new file mode 100644 index 000000000..e95eb1556 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/config/AppMessageSourceConfiguration.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/config/AppMessageSourceConfiguration.java new file mode 100644 index 000000000..66adc10da --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java new file mode 100644 index 000000000..18d3c5172 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/config/SecurityConfiguration.java @@ -0,0 +1,141 @@ +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 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.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Set; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + 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; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf().disable() + .cors() + .and() + .addFilterBefore(apiKeyFilter, AbstractPreAuthenticatedProcessingFilter.class) + .authorizeRequests() + .antMatchers(buildAntPatterns(webSecurityProperties.getAllowedEndpoints())).anonymous() + .antMatchers(buildAntPatterns(webSecurityProperties.getAuthorizedEndpoints())).authenticated() + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) + .and() + .oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); + } + + @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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java new file mode 100644 index 000000000..95aab412c --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java @@ -0,0 +1,169 @@ +package gr.cite.notification.web.controllers; + +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationInAppTracking; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.model.InAppNotification; +import gr.cite.notification.model.Notification; +import gr.cite.notification.model.builder.InAppNotificationBuilder; +import gr.cite.notification.model.censorship.InAppNotificationCensor; +import gr.cite.notification.query.InAppNotificationQuery; +import gr.cite.notification.query.lookup.InAppNotificationLookup; +import gr.cite.notification.service.inappnotification.InAppNotificationService; +import gr.cite.notification.service.notification.NotificationService; +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.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +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.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.management.InvalidApplicationException; +import javax.transaction.Transactional; +import java.util.*; + +@RestController +@RequestMapping(path = "api/notification/inapp-notification") +public class InAppNotificationController { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationController.class)); + + private final BuilderFactory builderFactory; + private final AuditService auditService; + private final NotificationService notificationService; + private final CensorFactory censorFactory; + private final QueryFactory queryFactory; + private final MessageSource messageSource; + private final InAppNotificationService inAppNotificationService; + private final UserScope userScope; + private final ErrorThesaurusProperties errors; + + @Autowired + public InAppNotificationController(BuilderFactory builderFactory, + AuditService auditService, + NotificationService notificationService, CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource, + InAppNotificationService inAppNotificationService, UserScope userScope, ErrorThesaurusProperties errors) { + this.builderFactory = builderFactory; + this.auditService = auditService; + this.notificationService = notificationService; + this.censorFactory = censorFactory; + this.queryFactory = queryFactory; + this.messageSource = messageSource; + this.inAppNotificationService = inAppNotificationService; + this.userScope = userScope; + this.errors = errors; + } + + @PostMapping("query") + public QueryResult Query(@RequestBody InAppNotificationLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidApplicationException { + logger.debug("querying {}", InAppNotification.class.getSimpleName()); + + this.censorFactory.censor(InAppNotificationCensor.class).censor(lookup.getProject()); + + UUID userId = this.userScope.getUserId(); + if (userId == null) throw new MyForbiddenException(this.errors.getNonPersonPrincipal().getCode(), this.errors.getNonPersonPrincipal().getMessage()); + InAppNotificationQuery query = lookup.enrich(this.queryFactory).userId(userId); + List data = query.collectAs(lookup.getProject()); + List models = this.builderFactory.builder(InAppNotificationBuilder.class).build(lookup.getProject(), data); + long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); + + this.auditService.track(AuditableAction.InApp_Notification_Query, "lookup", lookup); + + return new QueryResult<>(models, count); + } + + @GetMapping("{id}") + @Transactional + public InAppNotification Get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + logger.debug(new MapLogEntry("retrieving" + InAppNotification.class.getSimpleName()).And("id", id).And("fields", fieldSet)); + + this.censorFactory.censor(InAppNotificationCensor.class).censor(fieldSet); + + InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).authorize(AuthorizationFlags.OwnerOrPermission).ids(id); + InAppNotification model = this.builderFactory.builder(InAppNotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet)); + if (model == null) + throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Notification.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.InApp_Notification_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("id", id), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return model; + } + + @PostMapping("{id}/read") + @Transactional + public ResponseEntity persist(@PathVariable UUID id) + { + logger.debug(new MapLogEntry("marking as read").And("id", id)); + + this.inAppNotificationService.markAsRead(id); + + this.auditService.track(AuditableAction.InApp_Notification_Read, Map.of("id", id)); + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + return ResponseEntity.ok("ok"); + } + + @PostMapping("read-all") + @Transactional + public ResponseEntity MarkAsReadAllUserInAppNotification() throws InvalidApplicationException { + logger.debug(new MapLogEntry("marking as read all")); + + UUID userId = this.userScope.getUserId(); + if (userId == null) throw new MyForbiddenException(this.errors.getNonPersonPrincipal().getCode(), this.errors.getNonPersonPrincipal().getMessage()); + + this.inAppNotificationService.markAsReadAllUserNotification(userId); + + this.auditService.track(AuditableAction.InApp_Notification_Read_All, Map.of("userId", userId)); + return ResponseEntity.ok("ok"); + } + + @GetMapping("count-unread") + @Transactional + public ResponseEntity CountUnread() throws InvalidApplicationException { + logger.debug("count-unread"); + + UUID userId = this.userScope.getUserId(); + if (userId == null) throw new MyForbiddenException(this.errors.getNonPersonPrincipal().getCode(), this.errors.getNonPersonPrincipal().getMessage()); + + this.censorFactory.censor(InAppNotificationCensor.class).censor(new BaseFieldSet(InAppNotification.Field.ID)); + + InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).isActive(IsActive.Active).trackingState(NotificationInAppTracking.STORED).userId(userId); + int count = Math.toIntExact(query.count()); + + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return ResponseEntity.ok(count); + } + + @DeleteMapping("{id}") + @Transactional + public void Delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug(new MapLogEntry("deleting" + Notification.class.getSimpleName()).And("id", id)); + + this.notificationService.deleteAndSave(id); + + this.auditService.track(AuditableAction.InApp_Notification_Delete, "id", id); + + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + } +} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationController.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationController.java new file mode 100644 index 000000000..236769802 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationController.java @@ -0,0 +1,127 @@ +package gr.cite.notification.web.controllers; + +import gr.cite.notification.audit.AuditableAction; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.model.Notification; +import gr.cite.notification.model.SendNotificationResult; +import gr.cite.notification.model.builder.NotificationBuilder; +import gr.cite.notification.model.censorship.NotificationCensor; +import gr.cite.notification.model.persist.NotificationPersist; +import gr.cite.notification.query.NotificationQuery; +import gr.cite.notification.query.lookup.NotificationLookup; +import gr.cite.notification.service.notification.NotificationService; +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.MyValidate; +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 javax.transaction.Transactional; +import java.util.*; + +@RestController +@RequestMapping(path = "api/notification/notification") +public class NotificationController { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationController.class)); + + private final BuilderFactory builderFactory; + private final AuditService auditService; + private final NotificationService notificationService; + private final CensorFactory censorFactory; + private final QueryFactory queryFactory; + private final MessageSource messageSource; + + @Autowired + public NotificationController(BuilderFactory builderFactory, + AuditService auditService, + NotificationService notificationService, CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource) { + this.builderFactory = builderFactory; + this.auditService = auditService; + this.notificationService = notificationService; + this.censorFactory = censorFactory; + this.queryFactory = queryFactory; + this.messageSource = messageSource; + } + + @PostMapping("query") + public QueryResult Query(@RequestBody NotificationLookup lookup) throws MyApplicationException, MyForbiddenException { + logger.debug("querying {}", Notification.class.getSimpleName()); + + this.censorFactory.censor(NotificationCensor.class).censor(lookup.getProject()); + + NotificationQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.OwnerOrPermission); + List data = query.collectAs(lookup.getProject()); + List models = this.builderFactory.builder(NotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data); + long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); + + this.auditService.track(AuditableAction.Notification_Query, "lookup", lookup); + + return new QueryResult<>(models, count); + } + + @GetMapping("{id}") + @Transactional + public Notification Get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + logger.debug(new MapLogEntry("retrieving" + Notification.class.getSimpleName()).And("id", id).And("fields", fieldSet)); + + this.censorFactory.censor(NotificationCensor.class).censor(fieldSet); + + NotificationQuery query = this.queryFactory.query(NotificationQuery.class).authorize(AuthorizationFlags.OwnerOrPermission).ids(id); + Notification model = this.builderFactory.builder(NotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet)); + if (model == null) + throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Notification.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.Notification_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("id", id), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return model; + } + + @PostMapping("persist") + @Transactional + public Notification Persist(@MyValidate @RequestBody NotificationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { + logger.debug(new MapLogEntry("persisting" + Notification.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); + + Notification persisted = this.notificationService.persist(model, fieldSet); + + this.auditService.track(AuditableAction.Notification_Persist, Map.ofEntries( + new AbstractMap.SimpleEntry("model", model), + new AbstractMap.SimpleEntry("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" + Notification.class.getSimpleName()).And("id", id)); + + this.notificationService.deleteAndSave(id); + + this.auditService.track(AuditableAction.InApp_Notification_Delete, "id", id); + + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + } + +} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/PrincipalController.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/PrincipalController.java new file mode 100644 index 000000000..c8ff2443d --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java new file mode 100644 index 000000000..0112e376c --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java @@ -0,0 +1,164 @@ +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.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +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.model.persist.tenantconfiguration.TenantConfigurationNotifierListPersist; +import gr.cite.notification.query.TenantConfigurationQuery; +import gr.cite.notification.query.lookup.NotifierListLookup; +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 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 javax.transaction.Transactional; +import javax.validation.Valid; +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 + public TenantConfiguration persist(@RequestBody @Valid 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; + } + + @PostMapping("notifier-list/available") + public NotifierListConfigurationDataContainer getAvailableNotifiers(@RequestBody NotifierListLookup tenantNotifierListLookup) + { + logger.debug("querying available notifiers"); + + NotifierListConfigurationDataContainer notifierListData = this.tenantConfigurationService.collectTenantAvailableNotifierList(tenantNotifierListLookup.getNotificationTypes()); + + this.auditService.track(AuditableAction.Tenant_Available_Notifiers_Query, Map.of( + "lookup", tenantNotifierListLookup + )); + //this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action); + + return notifierListData; + } + + @PostMapping("persist/notifier-list") + @Transactional + public TenantConfiguration persist(@RequestBody @Valid TenantConfigurationNotifierListPersist model, FieldSet fieldSet) + { + logger.debug(new MapLogEntry("persisting").And("type", TenantConfigurationType.NOTIFIER_LIST).And("model", model).And("fields", fieldSet)); + + TenantConfiguration persisted = this.tenantConfigurationService.persist(model, fieldSet); + + this.auditService.track(AuditableAction.Tenant_Configuration_Persist, Map.of( + "type", TenantConfigurationType.NOTIFIER_LIST, + "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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java new file mode 100644 index 000000000..352b0c3ba --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java @@ -0,0 +1,149 @@ +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.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.data.UserNotificationPreferenceEntity; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.notification.model.UserNotificationPreference; +import gr.cite.notification.model.builder.UserNotificationPreferenceBuilder; +import gr.cite.notification.model.censorship.UserNotificationPreferenceCensor; +import gr.cite.notification.model.persist.UserNotificationPreferencePersist; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationNotifierListPersist; +import gr.cite.notification.query.UserNotificationPreferenceQuery; +import gr.cite.notification.query.lookup.NotifierListLookup; +import gr.cite.notification.query.lookup.UserNotificationPreferenceLookup; +import gr.cite.notification.service.tenantconfiguration.TenantConfigurationService; +import gr.cite.notification.service.userNotificationPreference.UserNotificationPreferenceService; +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 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 javax.transaction.Transactional; +import javax.validation.Valid; +import java.util.*; + +@RestController +@RequestMapping(path = "api/notification/notification-preference") +public class UserNotificationPreferenceController { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserNotificationPreferenceController.class)); + + private final BuilderFactory builderFactory; + private final AuditService auditService; + private final UserNotificationPreferenceService userNotificationPreferenceService; + private final CensorFactory censorFactory; + private final QueryFactory queryFactory; + private final MessageSource messageSource; + + @Autowired + public UserNotificationPreferenceController(BuilderFactory builderFactory, + AuditService auditService, + UserNotificationPreferenceService userNotificationPreferenceService, + CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource) { + this.builderFactory = builderFactory; + this.auditService = auditService; + this.userNotificationPreferenceService = userNotificationPreferenceService; + this.censorFactory = censorFactory; + this.queryFactory = queryFactory; + this.messageSource = messageSource; + } + + @PostMapping("query") + public QueryResult query(@RequestBody UserNotificationPreferenceLookup lookup) throws MyApplicationException, MyForbiddenException { + logger.debug("querying {}", UserNotificationPreference.class.getSimpleName()); + + this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(lookup.getProject()); + + UserNotificationPreferenceQuery query = lookup.enrich(this.queryFactory); + List data = query.collectAs(lookup.getProject()); + List models = this.builderFactory.builder(UserNotificationPreferenceBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data); + long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); + + this.auditService.track(AuditableAction.User_Notification_Preference_Query, "lookup", lookup); + + return new QueryResult<>(models, count); + } + + @GetMapping("user/{userId}/current") + @Transactional + public UserNotificationPreference current(@PathVariable UUID userId, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + logger.debug(new MapLogEntry("retrieving" + UserNotificationPreference.class.getSimpleName()).And("userId", userId).And("fields", fieldSet)); + + this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(fieldSet); + + UserNotificationPreferenceQuery query = this.queryFactory.query(UserNotificationPreferenceQuery.class).userId(userId); + UserNotificationPreference model = this.builderFactory.builder(UserNotificationPreferenceBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet)); + if (model == null) + throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{userId, TenantConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.User_Notification_Preference_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("userId", userId), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + //this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return model; + } + + @PostMapping("notifier-list/available") + public NotifierListConfigurationDataContainer getAvailableNotifiers(@RequestBody NotifierListLookup tenantNotifierListLookup) + { + logger.debug("querying available notifiers"); + + NotifierListConfigurationDataContainer notifierListData = this.userNotificationPreferenceService.collectUserAvailableNotifierList(tenantNotifierListLookup.getNotificationTypes()); + + this.auditService.track(AuditableAction.User_Available_Notifiers_Query, Map.of( + "lookup", tenantNotifierListLookup + )); + //this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action); + + return notifierListData; + } + + @PostMapping("persist") + @Transactional + public List persist(@RequestBody @Valid UserNotificationPreferencePersist model, FieldSet fieldSet) + { + logger.debug(new MapLogEntry("persisting").And("type", TenantConfigurationType.NOTIFIER_LIST).And("model", model).And("fields", fieldSet)); + + List persisted = this.userNotificationPreferenceService.persist(model, fieldSet); + + this.auditService.track(AuditableAction.User_Notification_Preference_Persist, Map.of( + "type", TenantConfigurationType.NOTIFIER_LIST, + "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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/error/GenericErrorHandler.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/error/GenericErrorHandler.java new file mode 100644 index 000000000..1a7ebfc3a --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/model/Account.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/model/Account.java new file mode 100644 index 000000000..f8bbe0902 --- /dev/null +++ b/dmp-backend/notification-service/notification-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 final static String _userId = "userId"; + public UUID userId; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public final static String _subject = "subject"; + public UUID subject; + + public UUID getSubject() { + return subject; + } + + public void setSubject(UUID subject) { + this.subject = subject; + } + + public final static String _name = "name"; + @LogSensitive + public String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public final static String _scope = "scope"; + public List scope; + + public List getScope() { + return scope; + } + + public void setScope(List scope) { + this.scope = scope; + } + + public final static String _client = "client"; + public String client; + + public String getClient() { + return client; + } + + public void setClient(String client) { + this.client = client; + } + + public final static String _notBefore = "notBefore"; + public Instant notBefore; + + public Instant getNotBefore() { + return notBefore; + } + + public void setNotBefore(Instant notBefore) { + this.notBefore = notBefore; + } + + public final static String _issuedAt = "issuedAt"; + public Instant issuedAt; + + public Instant getIssuedAt() { + return issuedAt; + } + + public void setIssuedAt(Instant issuedAt) { + this.issuedAt = issuedAt; + } + + public final static String _authenticatedAt = "authenticatedAt"; + public Instant authenticatedAt; + + public Instant getAuthenticatedAt() { + return authenticatedAt; + } + + public void setAuthenticatedAt(Instant authenticatedAt) { + this.authenticatedAt = authenticatedAt; + } + + public final static String _expiresAt = "expiresAt"; + public Instant expiresAt; + + public Instant getExpiresAt() { + return expiresAt; + } + + public void setExpiresAt(Instant expiresAt) { + this.expiresAt = expiresAt; + } + + public final static String _more = "more"; + @LogSensitive + public Map> more; + + public Map> getMore() { + return more; + } + + public void setMore(Map> more) { + this.more = more; + } + } + + + public final static String _isAuthenticated = "isAuthenticated"; + private Boolean isAuthenticated; + + public Boolean getIsAuthenticated() { + return isAuthenticated; + } + + public void setIsAuthenticated(Boolean authenticated) { + isAuthenticated = authenticated; + } + + public final static String _principal = "principal"; + private PrincipalInfo principal; + + public PrincipalInfo getPrincipal() { + return principal; + } + + public void setPrincipal(PrincipalInfo principal) { + this.principal = principal; + } + + public final static String _permissions = "permissions"; + private List permissions; + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List permissions) { + this.permissions = permissions; + } +} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/model/AccountBuilder.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/model/AccountBuilder.java new file mode 100644 index 000000000..17deb15fb --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/model/QueryResult.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/model/QueryResult.java new file mode 100644 index 000000000..2e0899843 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheOptions.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheOptions.java new file mode 100644 index 000000000..752c8965f --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheService.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByCodeCacheService.java new file mode 100644 index 000000000..6c2d2107a --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheOptions.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheOptions.java new file mode 100644 index 000000000..1c5f69520 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheService.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantByIdCacheService.java new file mode 100644 index 000000000..89ab6c35d --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java new file mode 100644 index 000000000..ef36af989 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantInterceptor.java @@ -0,0 +1,167 @@ +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 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 javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; +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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java new file mode 100644 index 000000000..dbabb0857 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeClaimInterceptor.java @@ -0,0 +1,197 @@ +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 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 javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeConfiguration.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeConfiguration.java new file mode 100644 index 000000000..13d77c9ad --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java new file mode 100644 index 000000000..15bc2790f --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeHeaderInterceptor.java @@ -0,0 +1,162 @@ +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 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 javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeProperties.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/TenantScopeProperties.java new file mode 100644 index 000000000..6ed29645b --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheOptions.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheOptions.java new file mode 100644 index 000000000..30606754c --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheService.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/tenant/UserAllowedTenantCacheService.java new file mode 100644 index 000000000..0cebe00ea --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java new file mode 100644 index 000000000..21f1f0927 --- /dev/null +++ b/dmp-backend/notification-service/notification-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 javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java new file mode 100644 index 000000000..8a35750a6 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java new file mode 100644 index 000000000..abfd32f98 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/certificates/elasticsearch_dev04.crt b/dmp-backend/notification-service/notification-web/src/main/resources/certificates/elasticsearch_dev04.crt new file mode 100644 index 000000000..ae8d7a7c5 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/certificates/elasticsearch_dev04.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjqgAwIBAgIUKTnOSL0Rtnm8ZQkfSUvpQiBNGnMwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwHhcNMTkwOTAzMTUyMDM1WhcNMjIwOTAyMTUyMDM1WjAYMRYwFAYD +VQQDEw1lbGFzdGljc2VhcmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAkB1OMRBRUDUxQ6fIv5gv0myKDZgVJFnEEjIjU6YjMpf/htTsvu8zdpBoWhg6 +7IsflSkUPynDG4geFjQ/WtxVeqxjnmtIB2fMDAHppX882as3tYjBlHj1sU0/OwwI +Ga5/OtxOubGswrzCEEjIgZwTtSX2Yzx3tE2UzwjWsYwGCBM/ssN8Wc1vlqq20+Qr +Lsggk+dXapN2wL9FABrxrJfV2SxXb2qKLKVd3EIfs+HVqIt9dVrpcFRV3Lwexg+Z +wlJv58EPsynphczssBhMOhlmVqpRY8z88fqsbqDVdqHIF8hqn7czWFqeCRldnb7W +LWaYaOG0Jd6SM7OpHnfNgBST4wIDAQABo3gwdjAdBgNVHQ4EFgQUCkDAcWSJ6H2G +UFFh9dhk+mG0L08wHwYDVR0jBBgwFoAUDF2zHdVwSwxo9Z7sa9vIc4Sd+1MwKQYD +VR0RBCIwIIIJbG9jYWxob3N0hwR/AAABgg1lbGFzdGljc2VhcmNoMAkGA1UdEwQC +MAAwDQYJKoZIhvcNAQELBQADggEBAAQpkdkGl2H0ylgbmmNiIlrQX+U2i4ag4sJ6 +xsVR5OWxuyB/aMWhuOHkgueMh2wElufn60jK0Mh25b2U7oO/0Nq+28rhhP9HURLz +7/TwCbLcglTAgHQPWItwn5r5WKDFNCPNpZXFU/oG5H6hUJqTvuaTN6G/PQ6V9Yp3 +J00NbPuFq8tjNAc/kQnhC7zdC/7YQ/fanHBPkvQnkGbac5+VAF/se/JYbxRpSz23 +5a+v6BDb/kjs82QgV8dzsyFmntO+Neesu9tTJurBbQD5T3xMgoGSWLgnTCq3/drl +PMBLgUQHik629dU+7o8ePCdyULruGMR6CIBqO7ZKQASulhkxdUo= +-----END CERTIFICATE----- diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/application.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/application.yml new file mode 100644 index 000000000..4f9a66b6d --- /dev/null +++ b/dmp-backend/notification-service/notification-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/notification.yml[.yml], optional:classpath:config/notification-${spring.profiles.active}.yml[.yml], optional:file:../config/notification-${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/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/dmp-backend/notification-service/notification-web/src/main/resources/config/cache.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/cache.yml new file mode 100644 index 000000000..5b30dee77 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/cipher.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/cipher.yml new file mode 100644 index 000000000..caa559296 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/cors-devel.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/cors-devel.yml new file mode 100644 index 000000000..3e9227ef3 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/cors-devel.yml @@ -0,0 +1,3 @@ +web: + cors: + allowed-origins: [ http://localhost, http://localhost:4200 ] diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/cors.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/cors.yml new file mode 100644 index 000000000..3774f14d4 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/db-devel.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/db-devel.yml new file mode 100644 index 000000000..22a089fbd --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/db.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/db.yml new file mode 100644 index 000000000..ede758c3b --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/db.yml @@ -0,0 +1,21 @@ +spring: + jpa: + properties: + hibernate: + ddl-auto: validate + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate: + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + 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 diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/email.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/email.yml new file mode 100644 index 000000000..fb7c94ba0 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/errors.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/errors.yml new file mode 100644 index 000000000..79762a9e7 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/formatting.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/formatting.yml new file mode 100644 index 000000000..abdc31184 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml new file mode 100644 index 000000000..9848dc492 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml @@ -0,0 +1,50 @@ +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: intelcomp-sti-viewer-dev.roles + - type: authorities + filterBy: "(.*):::TenantCode::" + extractByExpression: "(.*):(.*)" + extractExpressionValue: "[[g1]]" + Scope: + - type: scope + AccessToken: + - type: x-access-token + visibility: SENSITIVE + Tenant: + - type: x-tenant + IssuedAt: + - type: iat + Issuer: + - type: iss + Audience: + - type: aud + TokenType: + - type: typ + AuthorizedParty: + - type: azp + Authorities: + - type: authorities + TenantCodes: + - type: authorities + filterBy: "(.*):(.*)" + extractByExpression: "(.*):(.*)" + extractExpressionValue: "[[g2]]" diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/locale.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/locale.yml new file mode 100644 index 000000000..b09c59e53 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/locale.yml @@ -0,0 +1,4 @@ +locale: + timezone: UTC + language: en + culture: en-US diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/logging-devel.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/logging-devel.yml new file mode 100644 index 000000000..2f565c0d7 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/logging-devel.yml @@ -0,0 +1,2 @@ +logging: + config: classpath:logging/logback-dev.xml diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/logging.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/logging.yml new file mode 100644 index 000000000..56e152d3f --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/notification-devel.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/notification-devel.yml new file mode 100644 index 000000000..fff9c2a54 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/notification-devel.yml @@ -0,0 +1,6 @@ +notification: + staticFields: + fields: + - key: "{installation-url}" + type: "String" + value: "http://localhost:42000" \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/notification.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/notification.yml new file mode 100644 index 000000000..69a28861d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/notification.yml @@ -0,0 +1,246 @@ +notification: + task: + processor: + enable: true + interval-seconds: 30 + options: + retry-threshold: 300 + max-retry-delay-seconds: 10800 + too-old-to-send-seconds: 36000 + too-old-to-track-seconds: 604800 + overrides: [] + resolver: + global-policies: + - #Confirmation + type: 4FDBFA80-7A71-4A69-B854-67CBB70648F1 + contacts: [ email ] + - #DataManagementPlan + type: 065DEECD-21BB-44AF-9983-E660FDF24BC4 + contacts: [ email ] + - #Finalised + type: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E + contacts: [ email ] + - #MergeConfirmation + type: BFE68845-CB05-4C5A-A03D-29161A7C9660 + contacts: [ email ] + - #Modified + type: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB + contacts: [ email ] + - #ModifiedFinalised + type: D3CD55FE-8DA2-42E7-A501-3795EE4F16D3 + contacts: [ email ] + - #Publish + type: 55736F7A-83AB-4190-AF43-9D031A6F9612 + contacts: [ email ] + - #Template + type: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B + contacts: [ email ] + - #UnlinkConfirmation + type: C9BC3F16-057E-4BBA-8A5F-36BD835E5604 + contacts: [ email ] + ad-hoc-config: + ad-hoc-notification-type: null + message: + email: + flows: + - #Confirmation + key: 4FDBFA80-7A71-4A69-B854-67CBB70648F1 + subject-path: classpath:notification_templates/Confirmation/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + + body-path: classpath:notification_templates/Confirmation/Email/body.{language}.html + body-field-options: + mandatory: [ ] + optional: + - key: "{expiration_time}" + value: -- + formatting: + '[{expiration_time}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #DataManagementPlan + key: 065DEECD-21BB-44AF-9983-E660FDF24BC4 + subject-path: classpath:notification_templates/DataManagementPlan/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/DataManagementPlan/Email/body.{language}.html + body-field-options: + mandatory: [ "{dmpname}", "{dmprole}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{dmpname}]': null + '[{dmprole}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Finalised + key: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E + subject-path: classpath:notification_templates/Finalised/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Finalised/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #MergeConfirmation + key: BFE68845-CB05-4C5A-A03D-29161A7C9660 + subject-path: classpath:notification_templates/MergeConfirmation/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/MergeConfirmation/Email/body.{language}.html + body-field-options: + mandatory: [ "{userName}", "{host}" ] + optional: + - key: "{expiration_time}" + value: --- + formatting: + '[{userName}]': null + '[{host}]': null + '[{expiration_time}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Modified + key: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB + subject-path: classpath:notification_templates/Modified/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Modified/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #ModifiedFinalised + key: D3CD55FE-8DA2-42E7-A501-3795EE4F16D3 + subject-path: classpath:notification_templates/ModifiedFinalised/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/ModifiedFinalised/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Publish + key: 55736F7A-83AB-4190-AF43-9D031A6F9612 + subject-path: classpath:notification_templates/Publish/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Publish/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Template + key: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B + subject-path: classpath:notification_templates/Template/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Template/Email/body.{language}.html + body-field-options: + mandatory: [ "{templateName}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{templateName}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #UnlinkConfirmation + key: C9BC3F16-057E-4BBA-8A5F-36BD835E5604 + subject-path: classpath:notification_templates/UnlinkConfirmation/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/UnlinkConfirmation/Email/body.{language}.html + body-field-options: + mandatory: [ ] + optional: + - key: "{email}" + value: + formatting: + '[{email}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + template-cache: + prefix: ${CACHE_DISAMBIGUATION:} + key-pattern: "{prefix}:Notification_Message_Email_Template:{key}:v0" + override-cache: + template-cache: + prefix: ${CACHE_DISAMBIGUATION:} + key-pattern: "{prefix}:Notification_Override_Template:{tenant}:{type}:{channel}:{{language}}:v0" \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/permissions.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/permissions.yml new file mode 100644 index 000000000..2f8484c6b --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/permissions.yml @@ -0,0 +1,143 @@ +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: + - ic-sti-superuser + - user + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditNotification: + roles: + - ic-sti-superuser + - user + clients: [ ] + allowAnonymous: true + 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 \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/queue-devel.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/queue-devel.yml new file mode 100644 index 000000000..f0e31766d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/queue-devel.yml @@ -0,0 +1,18 @@ +queue: + rabbitmq: + durable: true + queue: cite_dmp_devel_notification_inbox_queue + exchange: cite_dmp_devel_queue + listenerEnabled: true + publisherEnabled: true + task: + publisher: + options: + exchange: cite_dmp_devel_queue + rabbitmq: + enable: true + listener: + options: + exchange: cite_dmp_devel_queue + rabbitmq: + enable: false diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/queue.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/queue.yml new file mode 100644 index 000000000..2886f369a --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/queue.yml @@ -0,0 +1,58 @@ +spring: + rabbitmq: + host: ${RABBIT_HOST} + port: ${RABBIT_PORT} + username: ${RABBIT_USER} + password: ${RABBIT_PASS} + ssl: + enabled: false +queue: + rabbitmq: + enable: false + app-id: ${THE_API_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 + notify-topic: notification.notify + tenant-reactivation-topic: tenant.reactivated + tenant-removal-topic: tenant.remove + tenant-touch-topic: tenant.touch + tenant-user-invite-topic: tenant.invite + 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 + 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/dmp-backend/notification-service/notification-web/src/main/resources/config/security.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/security.yml new file mode 100644 index 000000000..3024921aa --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/server-devel.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/server-devel.yml new file mode 100644 index 000000000..2a628f82f --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/server.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/server.yml new file mode 100644 index 000000000..bc9a15d9f --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/config/tenant.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/tenant.yml new file mode 100644 index 000000000..7fbc2f91c --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/logging/logback-dev.xml b/dmp-backend/notification-service/notification-web/src/main/resources/logging/logback-dev.xml new file mode 100644 index 000000000..0aaad5f31 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/messages/messages.properties b/dmp-backend/notification-service/notification-web/src/main/resources/messages/messages.properties new file mode 100644 index 000000000..9f8ede6f6 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/messages/messages.properties @@ -0,0 +1,7 @@ +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 \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/messages/messages_el.properties b/dmp-backend/notification-service/notification-web/src/main/resources/messages/messages_el.properties new file mode 100644 index 000000000..4d060cc58 --- /dev/null +++ b/dmp-backend/notification-service/notification-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/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Confirmation/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Confirmation/Email/body.en.html new file mode 100644 index 000000000..3d46fc62d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Confirmation/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+ OpenDMP +

Thank you for joining OpenDMP!

+

Please confirm that your email address is correct to continue. +
The link will expire in {expiration_time}.

+ + + + + + +
+ + + + + + +
Confirm Email Address
+
+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Confirmation/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Confirmation/Email/subject.en.txt new file mode 100644 index 000000000..b6107c108 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Confirmation/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Email Confirmation \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/DataManagementPlan/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/DataManagementPlan/Email/body.en.html new file mode 100644 index 000000000..e85ecdd4c --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/DataManagementPlan/Email/body.en.html @@ -0,0 +1,305 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

You have been invited to collaborate to Data Management plan {dmpname} with role {dmprole}.

+

Click the button to redirect to {dmpname}.

+ + + + + + + +
+ + + + + + +
Join
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/DataManagementPlan/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/DataManagementPlan/Email/subject.en.txt new file mode 100644 index 000000000..2c36d1524 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/DataManagementPlan/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Data Management Plan Invite \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Finalised/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Finalised/Email/body.en.html new file mode 100644 index 000000000..7316beb27 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Finalised/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just finalised the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Finalised/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Finalised/Email/subject.en.txt new file mode 100644 index 000000000..a53f1a7bc --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Finalised/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Finalised \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/MergeConfirmation/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/MergeConfirmation/Email/body.en.html new file mode 100644 index 000000000..01f0536d6 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/MergeConfirmation/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+ OpenDMP +

User {userName} have sent you a merge Request.

+

Please confirm that you want to merge your {host} account with that account. +
The link will expire in {expiration_time}.

+ + + + + + +
+ + + + + + +
Confirm Merge Request
+
+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/MergeConfirmation/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/MergeConfirmation/Email/subject.en.txt new file mode 100644 index 000000000..5621c2292 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/MergeConfirmation/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Merge Confirmation \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Modified/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Modified/Email/body.en.html new file mode 100644 index 000000000..87460d2da --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Modified/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just made changes to the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Modified/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Modified/Email/subject.en.txt new file mode 100644 index 000000000..ff3f31c75 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Modified/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Modified \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/ModifiedFinalised/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/ModifiedFinalised/Email/body.en.html new file mode 100644 index 000000000..1cfb61e82 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/ModifiedFinalised/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just made changes and finalised the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/ModifiedFinalised/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/ModifiedFinalised/Email/subject.en.txt new file mode 100644 index 000000000..a7ea51cb5 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/ModifiedFinalised/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Modified Finalised \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Publish/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Publish/Email/body.en.html new file mode 100644 index 000000000..d23a1d6d8 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Publish/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just publish the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Publish/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Publish/Email/subject.en.txt new file mode 100644 index 000000000..8576f2649 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Publish/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Publish \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Template/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Template/Email/body.en.html new file mode 100644 index 000000000..7214de36b --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Template/Email/body.en.html @@ -0,0 +1,305 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

You have been invited to co-develop the Template {templateName}.

+

Click the button to redirect to {templateName}.

+ + + + + + + +
+ + + + + + +
{templateName}
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Template/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Template/Email/subject.en.txt new file mode 100644 index 000000000..ad592424e --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/Template/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Template Invite \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/UnlinkConfirmation/Email/body.en.html b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/UnlinkConfirmation/Email/body.en.html new file mode 100644 index 000000000..ee547f813 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/UnlinkConfirmation/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+ OpenDMP +

You have made a request to unlink your email account in ARGOS.

+

Please confirm that you want to unlink your {email} account. +
The link will expire in {expiration_time}.

+ + + + + + +
+ + + + + + +
Confirm Unlink Request
+
+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/UnlinkConfirmation/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/UnlinkConfirmation/Email/subject.en.txt new file mode 100644 index 000000000..e957d468f --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/resources/notification_templates/UnlinkConfirmation/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Unlink Email Confirmation \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/certificates/elasticsearch_dev04.crt b/dmp-backend/notification-service/notification-web/target/classes/certificates/elasticsearch_dev04.crt new file mode 100644 index 000000000..ae8d7a7c5 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/certificates/elasticsearch_dev04.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjqgAwIBAgIUKTnOSL0Rtnm8ZQkfSUvpQiBNGnMwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwHhcNMTkwOTAzMTUyMDM1WhcNMjIwOTAyMTUyMDM1WjAYMRYwFAYD +VQQDEw1lbGFzdGljc2VhcmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAkB1OMRBRUDUxQ6fIv5gv0myKDZgVJFnEEjIjU6YjMpf/htTsvu8zdpBoWhg6 +7IsflSkUPynDG4geFjQ/WtxVeqxjnmtIB2fMDAHppX882as3tYjBlHj1sU0/OwwI +Ga5/OtxOubGswrzCEEjIgZwTtSX2Yzx3tE2UzwjWsYwGCBM/ssN8Wc1vlqq20+Qr +Lsggk+dXapN2wL9FABrxrJfV2SxXb2qKLKVd3EIfs+HVqIt9dVrpcFRV3Lwexg+Z +wlJv58EPsynphczssBhMOhlmVqpRY8z88fqsbqDVdqHIF8hqn7czWFqeCRldnb7W +LWaYaOG0Jd6SM7OpHnfNgBST4wIDAQABo3gwdjAdBgNVHQ4EFgQUCkDAcWSJ6H2G +UFFh9dhk+mG0L08wHwYDVR0jBBgwFoAUDF2zHdVwSwxo9Z7sa9vIc4Sd+1MwKQYD +VR0RBCIwIIIJbG9jYWxob3N0hwR/AAABgg1lbGFzdGljc2VhcmNoMAkGA1UdEwQC +MAAwDQYJKoZIhvcNAQELBQADggEBAAQpkdkGl2H0ylgbmmNiIlrQX+U2i4ag4sJ6 +xsVR5OWxuyB/aMWhuOHkgueMh2wElufn60jK0Mh25b2U7oO/0Nq+28rhhP9HURLz +7/TwCbLcglTAgHQPWItwn5r5WKDFNCPNpZXFU/oG5H6hUJqTvuaTN6G/PQ6V9Yp3 +J00NbPuFq8tjNAc/kQnhC7zdC/7YQ/fanHBPkvQnkGbac5+VAF/se/JYbxRpSz23 +5a+v6BDb/kjs82QgV8dzsyFmntO+Neesu9tTJurBbQD5T3xMgoGSWLgnTCq3/drl +PMBLgUQHik629dU+7o8ePCdyULruGMR6CIBqO7ZKQASulhkxdUo= +-----END CERTIFICATE----- diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/application.yml b/dmp-backend/notification-service/notification-web/target/classes/config/application.yml new file mode 100644 index 000000000..4f9a66b6d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/notification.yml[.yml], optional:classpath:config/notification-${spring.profiles.active}.yml[.yml], optional:file:../config/notification-${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/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/dmp-backend/notification-service/notification-web/target/classes/config/cache.yml b/dmp-backend/notification-service/notification-web/target/classes/config/cache.yml new file mode 100644 index 000000000..5b30dee77 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/cipher.yml b/dmp-backend/notification-service/notification-web/target/classes/config/cipher.yml new file mode 100644 index 000000000..caa559296 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/cors-devel.yml b/dmp-backend/notification-service/notification-web/target/classes/config/cors-devel.yml new file mode 100644 index 000000000..3e9227ef3 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/cors-devel.yml @@ -0,0 +1,3 @@ +web: + cors: + allowed-origins: [ http://localhost, http://localhost:4200 ] diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/cors.yml b/dmp-backend/notification-service/notification-web/target/classes/config/cors.yml new file mode 100644 index 000000000..3774f14d4 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/db-devel.yml b/dmp-backend/notification-service/notification-web/target/classes/config/db-devel.yml new file mode 100644 index 000000000..22a089fbd --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/db-devel.yml @@ -0,0 +1,7 @@ +spring: + jpa: + show-sql: true + properties: + hibernate: + show_sql: true + format_sql: false diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/db.yml b/dmp-backend/notification-service/notification-web/target/classes/config/db.yml new file mode 100644 index 000000000..ede758c3b --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/db.yml @@ -0,0 +1,21 @@ +spring: + jpa: + properties: + hibernate: + ddl-auto: validate + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate: + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + 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 diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/email.yml b/dmp-backend/notification-service/notification-web/target/classes/config/email.yml new file mode 100644 index 000000000..fb7c94ba0 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/errors.yml b/dmp-backend/notification-service/notification-web/target/classes/config/errors.yml new file mode 100644 index 000000000..79762a9e7 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/formatting.yml b/dmp-backend/notification-service/notification-web/target/classes/config/formatting.yml new file mode 100644 index 000000000..abdc31184 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml b/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml new file mode 100644 index 000000000..9848dc492 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml @@ -0,0 +1,50 @@ +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: intelcomp-sti-viewer-dev.roles + - type: authorities + filterBy: "(.*):::TenantCode::" + extractByExpression: "(.*):(.*)" + extractExpressionValue: "[[g1]]" + Scope: + - type: scope + AccessToken: + - type: x-access-token + visibility: SENSITIVE + Tenant: + - type: x-tenant + IssuedAt: + - type: iat + Issuer: + - type: iss + Audience: + - type: aud + TokenType: + - type: typ + AuthorizedParty: + - type: azp + Authorities: + - type: authorities + TenantCodes: + - type: authorities + filterBy: "(.*):(.*)" + extractByExpression: "(.*):(.*)" + extractExpressionValue: "[[g2]]" diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/locale.yml b/dmp-backend/notification-service/notification-web/target/classes/config/locale.yml new file mode 100644 index 000000000..b09c59e53 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/locale.yml @@ -0,0 +1,4 @@ +locale: + timezone: UTC + language: en + culture: en-US diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/logging-devel.yml b/dmp-backend/notification-service/notification-web/target/classes/config/logging-devel.yml new file mode 100644 index 000000000..2f565c0d7 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/logging-devel.yml @@ -0,0 +1,2 @@ +logging: + config: classpath:logging/logback-dev.xml diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/logging.yml b/dmp-backend/notification-service/notification-web/target/classes/config/logging.yml new file mode 100644 index 000000000..56e152d3f --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/notification-devel.yml b/dmp-backend/notification-service/notification-web/target/classes/config/notification-devel.yml new file mode 100644 index 000000000..fff9c2a54 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/notification-devel.yml @@ -0,0 +1,6 @@ +notification: + staticFields: + fields: + - key: "{installation-url}" + type: "String" + value: "http://localhost:42000" \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/notification.yml b/dmp-backend/notification-service/notification-web/target/classes/config/notification.yml new file mode 100644 index 000000000..69a28861d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/notification.yml @@ -0,0 +1,246 @@ +notification: + task: + processor: + enable: true + interval-seconds: 30 + options: + retry-threshold: 300 + max-retry-delay-seconds: 10800 + too-old-to-send-seconds: 36000 + too-old-to-track-seconds: 604800 + overrides: [] + resolver: + global-policies: + - #Confirmation + type: 4FDBFA80-7A71-4A69-B854-67CBB70648F1 + contacts: [ email ] + - #DataManagementPlan + type: 065DEECD-21BB-44AF-9983-E660FDF24BC4 + contacts: [ email ] + - #Finalised + type: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E + contacts: [ email ] + - #MergeConfirmation + type: BFE68845-CB05-4C5A-A03D-29161A7C9660 + contacts: [ email ] + - #Modified + type: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB + contacts: [ email ] + - #ModifiedFinalised + type: D3CD55FE-8DA2-42E7-A501-3795EE4F16D3 + contacts: [ email ] + - #Publish + type: 55736F7A-83AB-4190-AF43-9D031A6F9612 + contacts: [ email ] + - #Template + type: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B + contacts: [ email ] + - #UnlinkConfirmation + type: C9BC3F16-057E-4BBA-8A5F-36BD835E5604 + contacts: [ email ] + ad-hoc-config: + ad-hoc-notification-type: null + message: + email: + flows: + - #Confirmation + key: 4FDBFA80-7A71-4A69-B854-67CBB70648F1 + subject-path: classpath:notification_templates/Confirmation/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + + body-path: classpath:notification_templates/Confirmation/Email/body.{language}.html + body-field-options: + mandatory: [ ] + optional: + - key: "{expiration_time}" + value: -- + formatting: + '[{expiration_time}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #DataManagementPlan + key: 065DEECD-21BB-44AF-9983-E660FDF24BC4 + subject-path: classpath:notification_templates/DataManagementPlan/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/DataManagementPlan/Email/body.{language}.html + body-field-options: + mandatory: [ "{dmpname}", "{dmprole}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{dmpname}]': null + '[{dmprole}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Finalised + key: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E + subject-path: classpath:notification_templates/Finalised/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Finalised/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #MergeConfirmation + key: BFE68845-CB05-4C5A-A03D-29161A7C9660 + subject-path: classpath:notification_templates/MergeConfirmation/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/MergeConfirmation/Email/body.{language}.html + body-field-options: + mandatory: [ "{userName}", "{host}" ] + optional: + - key: "{expiration_time}" + value: --- + formatting: + '[{userName}]': null + '[{host}]': null + '[{expiration_time}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Modified + key: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB + subject-path: classpath:notification_templates/Modified/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Modified/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #ModifiedFinalised + key: D3CD55FE-8DA2-42E7-A501-3795EE4F16D3 + subject-path: classpath:notification_templates/ModifiedFinalised/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/ModifiedFinalised/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Publish + key: 55736F7A-83AB-4190-AF43-9D031A6F9612 + subject-path: classpath:notification_templates/Publish/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Publish/Email/body.{language}.html + body-field-options: + mandatory: [ "{reasonName}", "{name}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{reasonName}]': null + '[{name}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #Template + key: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B + subject-path: classpath:notification_templates/Template/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/Template/Email/body.{language}.html + body-field-options: + mandatory: [ "{templateName}" ] + optional: + - key: "{recipient}" + value: + formatting: + '[{templateName}]': null + '[{recipient}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + - #UnlinkConfirmation + key: C9BC3F16-057E-4BBA-8A5F-36BD835E5604 + subject-path: classpath:notification_templates/UnlinkConfirmation/Email/subject.{language}.txt + subject-field-options: + mandatory: [ ] + optional: [ ] + body-path: classpath:notification_templates/UnlinkConfirmation/Email/body.{language}.html + body-field-options: + mandatory: [ ] + optional: + - key: "{email}" + value: + formatting: + '[{email}]': null + cc: [ ] + cc-mode: 0 + bcc: [ ] + bcc-mode: 0 + allow-attachments: false + cipher-fields: [ ] + template-cache: + prefix: ${CACHE_DISAMBIGUATION:} + key-pattern: "{prefix}:Notification_Message_Email_Template:{key}:v0" + override-cache: + template-cache: + prefix: ${CACHE_DISAMBIGUATION:} + key-pattern: "{prefix}:Notification_Override_Template:{tenant}:{type}:{channel}:{{language}}:v0" \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/permissions.yml b/dmp-backend/notification-service/notification-web/target/classes/config/permissions.yml new file mode 100644 index 000000000..2f8484c6b --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/permissions.yml @@ -0,0 +1,143 @@ +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: + - ic-sti-superuser + - user + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditNotification: + roles: + - ic-sti-superuser + - user + clients: [ ] + allowAnonymous: true + 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 \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/queue-devel.yml b/dmp-backend/notification-service/notification-web/target/classes/config/queue-devel.yml new file mode 100644 index 000000000..f0e31766d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/queue-devel.yml @@ -0,0 +1,18 @@ +queue: + rabbitmq: + durable: true + queue: cite_dmp_devel_notification_inbox_queue + exchange: cite_dmp_devel_queue + listenerEnabled: true + publisherEnabled: true + task: + publisher: + options: + exchange: cite_dmp_devel_queue + rabbitmq: + enable: true + listener: + options: + exchange: cite_dmp_devel_queue + rabbitmq: + enable: false diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/queue.yml b/dmp-backend/notification-service/notification-web/target/classes/config/queue.yml new file mode 100644 index 000000000..2886f369a --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/queue.yml @@ -0,0 +1,58 @@ +spring: + rabbitmq: + host: ${RABBIT_HOST} + port: ${RABBIT_PORT} + username: ${RABBIT_USER} + password: ${RABBIT_PASS} + ssl: + enabled: false +queue: + rabbitmq: + enable: false + app-id: ${THE_API_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 + notify-topic: notification.notify + tenant-reactivation-topic: tenant.reactivated + tenant-removal-topic: tenant.remove + tenant-touch-topic: tenant.touch + tenant-user-invite-topic: tenant.invite + 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 + 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/dmp-backend/notification-service/notification-web/target/classes/config/security.yml b/dmp-backend/notification-service/notification-web/target/classes/config/security.yml new file mode 100644 index 000000000..3024921aa --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/config/server-devel.yml b/dmp-backend/notification-service/notification-web/target/classes/config/server-devel.yml new file mode 100644 index 000000000..2a628f82f --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/server-devel.yml @@ -0,0 +1,2 @@ +server: + forward-headers-strategy: FRAMEWORK \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/server.yml b/dmp-backend/notification-service/notification-web/target/classes/config/server.yml new file mode 100644 index 000000000..bc9a15d9f --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/config/server.yml @@ -0,0 +1,3 @@ +server: + port: ${WEB_PORT} + forward-headers-strategy: NONE \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/tenant.yml b/dmp-backend/notification-service/notification-web/target/classes/config/tenant.yml new file mode 100644 index 000000000..7fbc2f91c --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/logging/logback-dev.xml b/dmp-backend/notification-service/notification-web/target/classes/logging/logback-dev.xml new file mode 100644 index 000000000..0aaad5f31 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/messages/messages.properties b/dmp-backend/notification-service/notification-web/target/classes/messages/messages.properties new file mode 100644 index 000000000..9f8ede6f6 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/messages/messages.properties @@ -0,0 +1,7 @@ +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 \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/messages/messages_el.properties b/dmp-backend/notification-service/notification-web/target/classes/messages/messages_el.properties new file mode 100644 index 000000000..4d060cc58 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/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/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Confirmation/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Confirmation/Email/body.en.html new file mode 100644 index 000000000..3d46fc62d --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Confirmation/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+ OpenDMP +

Thank you for joining OpenDMP!

+

Please confirm that your email address is correct to continue. +
The link will expire in {expiration_time}.

+ + + + + + +
+ + + + + + +
Confirm Email Address
+
+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Confirmation/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Confirmation/Email/subject.en.txt new file mode 100644 index 000000000..b6107c108 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Confirmation/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Email Confirmation \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/DataManagementPlan/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/DataManagementPlan/Email/body.en.html new file mode 100644 index 000000000..e85ecdd4c --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/DataManagementPlan/Email/body.en.html @@ -0,0 +1,305 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

You have been invited to collaborate to Data Management plan {dmpname} with role {dmprole}.

+

Click the button to redirect to {dmpname}.

+ + + + + + + +
+ + + + + + +
Join
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/DataManagementPlan/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/DataManagementPlan/Email/subject.en.txt new file mode 100644 index 000000000..2c36d1524 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/DataManagementPlan/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Data Management Plan Invite \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Finalised/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Finalised/Email/body.en.html new file mode 100644 index 000000000..7316beb27 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Finalised/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just finalised the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Finalised/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Finalised/Email/subject.en.txt new file mode 100644 index 000000000..a53f1a7bc --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Finalised/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Finalised \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/MergeConfirmation/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/MergeConfirmation/Email/body.en.html new file mode 100644 index 000000000..01f0536d6 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/MergeConfirmation/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+ OpenDMP +

User {userName} have sent you a merge Request.

+

Please confirm that you want to merge your {host} account with that account. +
The link will expire in {expiration_time}.

+ + + + + + +
+ + + + + + +
Confirm Merge Request
+
+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/MergeConfirmation/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/MergeConfirmation/Email/subject.en.txt new file mode 100644 index 000000000..5621c2292 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/MergeConfirmation/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Merge Confirmation \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Modified/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Modified/Email/body.en.html new file mode 100644 index 000000000..87460d2da --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Modified/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just made changes to the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Modified/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Modified/Email/subject.en.txt new file mode 100644 index 000000000..ff3f31c75 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Modified/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Modified \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/ModifiedFinalised/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/ModifiedFinalised/Email/body.en.html new file mode 100644 index 000000000..1cfb61e82 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/ModifiedFinalised/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just made changes and finalised the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/ModifiedFinalised/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/ModifiedFinalised/Email/subject.en.txt new file mode 100644 index 000000000..a7ea51cb5 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/ModifiedFinalised/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Modified Finalised \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Publish/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Publish/Email/body.en.html new file mode 100644 index 000000000..d23a1d6d8 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Publish/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

{reasonName} just publish the {name}.

+ + + + + + + +
+ + + + + + +
Click here to view it.
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Publish/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Publish/Email/subject.en.txt new file mode 100644 index 000000000..8576f2649 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Publish/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Publish \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Template/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Template/Email/body.en.html new file mode 100644 index 000000000..7214de36b --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Template/Email/body.en.html @@ -0,0 +1,305 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+

Dear {recipient},

+

You have been invited to co-develop the Template {templateName}.

+

Click the button to redirect to {templateName}.

+ + + + + + + +
+ + + + + + +
{templateName}
+
+ +
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Template/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Template/Email/subject.en.txt new file mode 100644 index 000000000..ad592424e --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/Template/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Template Invite \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/UnlinkConfirmation/Email/body.en.html b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/UnlinkConfirmation/Email/body.en.html new file mode 100644 index 000000000..ee547f813 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/UnlinkConfirmation/Email/body.en.html @@ -0,0 +1,304 @@ + + + + + + Simple Transactional Email + + + + + + + + + +
  +
+ + + This is preheader text. Some clients will show this text as a preview. + + + + + + + + +
+ + + + +
+ OpenDMP +

You have made a request to unlink your email account in ARGOS.

+

Please confirm that you want to unlink your {email} account. +
The link will expire in {expiration_time}.

+ + + + + + +
+ + + + + + +
Confirm Unlink Request
+
+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/dmp-backend/notification-service/notification-web/target/classes/notification_templates/UnlinkConfirmation/Email/subject.en.txt b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/UnlinkConfirmation/Email/subject.en.txt new file mode 100644 index 000000000..e957d468f --- /dev/null +++ b/dmp-backend/notification-service/notification-web/target/classes/notification_templates/UnlinkConfirmation/Email/subject.en.txt @@ -0,0 +1 @@ +ARGOS - Unlink Email Confirmation \ No newline at end of file diff --git a/dmp-backend/notification-service/notification/.gitignore b/dmp-backend/notification-service/notification/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/dmp-backend/notification-service/notification/.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/dmp-backend/notification-service/notification/pom.xml b/dmp-backend/notification-service/notification/pom.xml new file mode 100644 index 000000000..b5e71bb6a --- /dev/null +++ b/dmp-backend/notification-service/notification/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + + gr.cite + notification-service-parent + 1.0.0 + ../pom.xml + + + notification + 1.0.0 + + 11 + 11 + 11 + + + + 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 + + + + javax.persistence + javax.persistence-api + + + + gr.cite + data-tools + 1.0.0 + + + gr.cite + field-set + 1.0.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 + 1.0.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springframework + spring-web + + + gr.cite + cipher + 1.0.0 + compile + + + + diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java new file mode 100644 index 000000000..2fb58d514 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java @@ -0,0 +1,31 @@ +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 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"); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/AuthorizationFlags.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/AuthorizationFlags.java new file mode 100644 index 000000000..2cba3132b --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/OwnedAuthorizationRequirement.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/OwnedAuthorizationRequirement.java new file mode 100644 index 000000000..8171339da --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/OwnedResource.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/OwnedResource.java new file mode 100644 index 000000000..124f7ba2b --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/Permission.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/Permission.java new file mode 100644 index 000000000..346165927 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/authorization/Permission.java @@ -0,0 +1,39 @@ +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 + public static final String BrowseUserNotificationPreference = "BrowseUserNotificationPreference"; + public static final String EditUserNotificationPreference = "EditUserNotificationPreference"; + + // 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java new file mode 100644 index 000000000..b208526a8 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/cache/FormattingUserprofileCacheCacheService.java @@ -0,0 +1,93 @@ +package gr.cite.notification.cache; + +import gr.cite.notification.event.UserTouchedEvent; +import gr.cite.notification.config.formatting.FormattingUserprofileCacheOptions; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/cache/NotificationTemplateCache.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/cache/NotificationTemplateCache.java new file mode 100644 index 000000000..ebd581155 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/cache/NotificationTemplateCache.java @@ -0,0 +1,83 @@ +package gr.cite.notification.cache; + +import gr.cite.notification.config.notification.NotificationTemplateCacheOptions; +import gr.cite.tools.cache.CacheOptions; +import gr.cite.tools.cache.CacheService; +import org.springframework.cache.Cache; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +public class NotificationTemplateCache extends CacheService { + + public static class NotificationTemplateCacheValue { + private String prefix; + private String key; + private String pattern; + + public NotificationTemplateCacheValue() { + } + + public NotificationTemplateCacheValue(String prefix, String key, String pattern) { + this.prefix = prefix; + this.key = key; + this.pattern = pattern; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + } + + public NotificationTemplateCache(NotificationTemplateCacheOptions options) { + super(options); + } + + private Cache cache(){ + return this.cacheManager.getCache(this.options.getName()); + } + + public String lookup(NotificationTemplateCacheValue key) { + return this.cache().get(this.keyOf(key), String.class); + } + + public void put(NotificationTemplateCacheValue key, String value) { + this.cache().put(this.keyOf(key), value); + } + @Override + protected Class valueClass() { + return NotificationTemplateCacheValue.class; + } + + @Override + public String keyOf(NotificationTemplateCacheValue value) { + return this.buildKey(value); + } + + private String buildKey(NotificationTemplateCacheValue value) { + return value.getPattern() + .replace("{prefix}", value.getPrefix()) + .replace("{key}", value.getKey()); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/JsonHandlingService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/JsonHandlingService.java new file mode 100644 index 000000000..c76b3d7c7 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/JsonHandlingService.java @@ -0,0 +1,45 @@ +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.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/StringUtils.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/StringUtils.java new file mode 100644 index 000000000..0a73b94ef --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/XmlHandlingService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/XmlHandlingService.java new file mode 100644 index 000000000..e6b4b991e --- /dev/null +++ b/dmp-backend/notification-service/notification/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 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.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java new file mode 100644 index 000000000..e0d52875c --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/ContactInfoType.java @@ -0,0 +1,30 @@ +package gr.cite.notification.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import gr.cite.notification.data.conventers.DatabaseEnum; + +import java.util.HashMap; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/EnumUtils.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/EnumUtils.java new file mode 100644 index 000000000..fa581f847 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/InAppNotificationPriority.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/InAppNotificationPriority.java new file mode 100644 index 000000000..e85733fd2 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/InAppNotificationPriority.java @@ -0,0 +1,30 @@ +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 InAppNotificationPriority implements DatabaseEnum { + LOW((short)-1), + NORMAL((short)0), + HIGH((short)1), + EMERGENCY((short)2); + + private final Short value; + + InAppNotificationPriority(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(InAppNotificationPriority.class); + + public static InAppNotificationPriority of(Short i) { + return map.get(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/IsActive.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/IsActive.java new file mode 100644 index 000000000..10b9dc528 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationContactType.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationContactType.java new file mode 100644 index 000000000..778358983 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationContactType.java @@ -0,0 +1,31 @@ +package gr.cite.notification.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import gr.cite.notification.data.conventers.DatabaseEnum; + +import java.util.HashMap; +import java.util.Map; + +public enum NotificationContactType implements DatabaseEnum { + EMAIL((short)0), + SLACK_BROADCAST((short)1), + SMS((short)2), + IN_APP((short)3); + + private final Short value; + + NotificationContactType(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(NotificationContactType.class); + + public static NotificationContactType of(Short i) { + return map.get(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationInAppTracking.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationInAppTracking.java new file mode 100644 index 000000000..97957b507 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationInAppTracking.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 NotificationInAppTracking implements DatabaseEnum { + STORED((short)0), + DELIVERED((short)1); + + private final Short value; + + NotificationInAppTracking(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(NotificationInAppTracking.class); + + public static NotificationInAppTracking of(Short i) { + return map.get(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationNotifyState.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationNotifyState.java new file mode 100644 index 000000000..1dce8ba38 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationNotifyState.java @@ -0,0 +1,33 @@ +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 NotificationNotifyState implements DatabaseEnum { + + PENDING((short)0), + PROCESSING((short)1), + SUCCESSFUL((short)2), + ERROR((short)3), + OMITTED((short)4); + + private final Short value; + + NotificationNotifyState(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(NotificationNotifyState.class); + + public static NotificationNotifyState of(Short i) { + return map.get(i); + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationTrackingProcess.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationTrackingProcess.java new file mode 100644 index 000000000..61777b4cb --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationTrackingProcess.java @@ -0,0 +1,31 @@ +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 NotificationTrackingProcess implements DatabaseEnum { + PENDING((short)0), + PROCESSING((short)1), + COMPLETED((short)2), + ERROR((short)3), + OMITTED((short)4); + + private final Short value; + + NotificationTrackingProcess(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(NotificationTrackingProcess.class); + + public static NotificationTrackingProcess of(Short i) { + return map.get(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationTrackingState.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationTrackingState.java new file mode 100644 index 000000000..4a3998d88 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationTrackingState.java @@ -0,0 +1,40 @@ +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 NotificationTrackingState implements DatabaseEnum { + /* * + * Initial state + */ + UNDEFINED((short)0), + /* * + * Final for notifiers that do not provide any kind of tracking + */ + NA((short)1), + QUEUED((short)2), + SENT((short)3), + DELIVERED((short)4), + UNDELIVERED((short)5), + FAILED((short)6), + UNSENT((short)7); + + private final Short value; + + NotificationTrackingState(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(NotificationTrackingState.class); + + public static NotificationTrackingState of(Short i) { + return map.get(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/TenantConfigurationType.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/TenantConfigurationType.java new file mode 100644 index 000000000..799a59c7f --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestAttributes.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestAttributes.java new file mode 100644 index 000000000..f8f2df085 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestScope.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/fake/FakeRequestScope.java new file mode 100644 index 000000000..e8b6716e2 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyConfiguration.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyConfiguration.java new file mode 100644 index 000000000..52ed9fa9f --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyProperties.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/MultitenancyProperties.java new file mode 100644 index 000000000..56f8dd353 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java new file mode 100644 index 000000000..34e102280 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/TenantScope.java @@ -0,0 +1,90 @@ +package gr.cite.notification.common.scope.tenant; + +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import gr.cite.tools.logging.LoggerService; +import org.hibernate.Session; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import javax.persistence.EntityManager; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/TenantScoped.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/tenant/TenantScoped.java new file mode 100644 index 000000000..77ecacaea --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/user/UserScope.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/scope/user/UserScope.java new file mode 100644 index 000000000..0315f7dac --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/Attachment.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/Attachment.java new file mode 100644 index 000000000..da6249e93 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/ContactPair.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/ContactPair.java new file mode 100644 index 000000000..dcb9adac2 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/DataType.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/DataType.java new file mode 100644 index 000000000..d6dc095f5 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/DataType.java @@ -0,0 +1,42 @@ +package gr.cite.notification.common.types.notification; + +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum DataType { + Integer(0), + Decimal(1), + Double(2), + DateTime(3), + //TimeSpan(4), + String(5); + private static final Map values = new HashMap<>(); + + private final Integer mappedName; + + //For jackson parsing (used by MVC) + @JsonValue + public Integer getMappedName() { + return mappedName; + } + + static { + for (DataType e : values()) { + values.put(e.asInt(), e); + } + } + + private DataType(int mappedName) { + this.mappedName = mappedName; + } + + public Integer asInt() { + return this.mappedName; + } + + public static DataType fromString(Integer value) { + return values.getOrDefault(value, Integer); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/EmailOverrideMode.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/EmailOverrideMode.java new file mode 100644 index 000000000..1423c2a29 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/EmailOverrideMode.java @@ -0,0 +1,39 @@ +package gr.cite.notification.common.types.notification; + +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum EmailOverrideMode { + NotOverride(0), + Additive(1), + Replace(2); + private static final Map values = new HashMap<>(); + + private final Integer mappedName; + + //For jackson parsing (used by MVC) + @JsonValue + public Integer getMappedName() { + return mappedName; + } + + static { + for (EmailOverrideMode e : values()) { + values.put(e.asInt(), e); + } + } + + private EmailOverrideMode(int mappedName) { + this.mappedName = mappedName; + } + + public Integer asInt() { + return this.mappedName; + } + + public static EmailOverrideMode fromString(Integer value) { + return values.getOrDefault(value, NotOverride); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java new file mode 100644 index 000000000..a16ebc570 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java @@ -0,0 +1,40 @@ +package gr.cite.notification.common.types.notification; + +public class FieldInfo { + private String key; + private DataType type; + private String value; + + public FieldInfo(String key, DataType type, String value) { + this.key = key; + this.type = type; + this.value = value; + } + + public FieldInfo() { + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public DataType getType() { + return type; + } + + public void setType(DataType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/InAppTrackingData.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/InAppTrackingData.java new file mode 100644 index 000000000..aeb1eb989 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/InAppTrackingData.java @@ -0,0 +1,38 @@ +package gr.cite.notification.common.types.notification; + +import java.util.List; +import java.util.UUID; + +public class InAppTrackingData { + + private UUID inAppNotificationId; + private List traces; + + public InAppTrackingData() { + } + + public InAppTrackingData(UUID inAppNotificationId) { + this.inAppNotificationId = inAppNotificationId; + } + + public InAppTrackingData(UUID inAppNotificationId, List traces) { + this.inAppNotificationId = inAppNotificationId; + this.traces = traces; + } + + public UUID getInAppNotificationId() { + return inAppNotificationId; + } + + public void setInAppNotificationId(UUID inAppNotificationId) { + this.inAppNotificationId = inAppNotificationId; + } + + public List getTraces() { + return traces; + } + + public void setTraces(List traces) { + this.traces = traces; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/NotificationContactData.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/NotificationContactData.java new file mode 100644 index 000000000..b781a8c82 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/NotificationContactData.java @@ -0,0 +1,42 @@ +package gr.cite.notification.common.types.notification; + +import java.util.List; + +public class NotificationContactData { + private List contacts; + private List bcc; + private List cc; + + public NotificationContactData() { + } + + public NotificationContactData(List contacts, List bcc, List cc) { + this.contacts = contacts; + this.bcc = bcc; + this.cc = cc; + } + + public List getContacts() { + return contacts; + } + + public void setContacts(List contacts) { + this.contacts = contacts; + } + + public List getBcc() { + return bcc; + } + + public void setBcc(List bcc) { + this.bcc = bcc; + } + + public List getCc() { + return cc; + } + + public void setCc(List cc) { + this.cc = cc; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/NotificationFieldData.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/NotificationFieldData.java new file mode 100644 index 000000000..7e8dde5a6 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/NotificationFieldData.java @@ -0,0 +1,32 @@ +package gr.cite.notification.common.types.notification; + +import java.util.List; + +public class NotificationFieldData { + private List fields; + private List attachments; + + public NotificationFieldData(List fields, List attachments) { + this.fields = fields; + this.attachments = attachments; + } + + public NotificationFieldData() { + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public List getAttachments() { + return attachments; + } + + public void setAttachments(List attachments) { + this.attachments = attachments; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/RouteTrackingData.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/RouteTrackingData.java new file mode 100644 index 000000000..06c52701d --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/RouteTrackingData.java @@ -0,0 +1,30 @@ +package gr.cite.notification.common.types.notification; + +import java.util.List; + +public class RouteTrackingData { + + private String trackingId; + private List traces; + + public RouteTrackingData(String trackingId, List traces) { + this.trackingId = trackingId; + this.traces = traces; + } + + public String getTrackingId() { + return trackingId; + } + + public void setTrackingId(String trackingId) { + this.trackingId = trackingId; + } + + public List getTraces() { + return traces; + } + + public void setTraces(List traces) { + this.traces = traces; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/TrackingTrace.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/TrackingTrace.java new file mode 100644 index 000000000..b3bffba06 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/TrackingTrace.java @@ -0,0 +1,33 @@ +package gr.cite.notification.common.types.notification; + +import java.time.Instant; + +public class TrackingTrace { + + private Instant at; + private String data; + + public TrackingTrace() { + } + + public TrackingTrace(Instant at, String data) { + this.at = at; + this.data = data; + } + + public Instant getAt() { + return at; + } + + public void setAt(Instant at) { + this.at = at; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java new file mode 100644 index 000000000..6a13dbd50 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantConfigEntity.java @@ -0,0 +1,35 @@ +package gr.cite.notification.common.types.tenant; + + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java new file mode 100644 index 000000000..e4b02bd46 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantDepositConfigEntity.java @@ -0,0 +1,22 @@ +package gr.cite.notification.common.types.tenant; + +import javax.xml.bind.annotation.*; + +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java new file mode 100644 index 000000000..bd1e3409b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantFileTransformersConfigEntity.java @@ -0,0 +1,21 @@ +package gr.cite.notification.common.types.tenant; + +import javax.xml.bind.annotation.*; + +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java new file mode 100644 index 000000000..6c8ccf4b3 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenant/TenantSourceEntity.java @@ -0,0 +1,76 @@ +package gr.cite.notification.common.types.tenant; + +import javax.xml.bind.annotation.*; + +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/DefaultUserLocaleConfigurationDataContainer.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/DefaultUserLocaleConfigurationDataContainer.java new file mode 100644 index 000000000..2724f54c1 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/EmailClientConfigurationDataContainer.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/EmailClientConfigurationDataContainer.java new file mode 100644 index 000000000..71d70a997 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/NotifierListConfigurationDataContainer.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/NotifierListConfigurationDataContainer.java new file mode 100644 index 000000000..c91a91009 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/tenantconfiguration/NotifierListConfigurationDataContainer.java @@ -0,0 +1,30 @@ +package gr.cite.notification.common.types.tenantconfiguration; + +import gr.cite.notification.common.enums.NotificationContactType; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class NotifierListConfigurationDataContainer { + + public static class Field { + public static final String NOTIFIERS = "notifiers"; + } + private Map> notifiers; + + public NotifierListConfigurationDataContainer() { + } + + public NotifierListConfigurationDataContainer(Map> notifiers) { + this.notifiers = notifiers; + } + + public Map> getNotifiers() { + return notifiers; + } + + public void setNotifiers(Map> notifiers) { + this.notifiers = notifiers; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/xml/XmlBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/xml/XmlBuilder.java new file mode 100644 index 000000000..356bbd0a1 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/xml/XmlSerializable.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/xml/XmlSerializable.java new file mode 100644 index 000000000..8a9d2bc7c --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/EnumNotNull.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/EnumNotNull.java new file mode 100644 index 000000000..111523d6e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/EnumNotNull.java @@ -0,0 +1,11 @@ +package gr.cite.notification.common.validation; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class EnumNotNull implements ConstraintValidator { + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + return value != null; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldNotNullIfOtherSet.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldNotNullIfOtherSet.java new file mode 100644 index 000000000..ad2fd2fa9 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldNotNullIfOtherSet.java @@ -0,0 +1,22 @@ +package gr.cite.notification.common.validation; + + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Constraint( validatedBy = { FieldNotNullIfOtherSetValidator.class } ) +@Documented +@Target( { ElementType.TYPE } ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface FieldNotNullIfOtherSet { + Class[] groups () default {}; + + String notNullField() default "id"; + String otherSetField() default "hash"; + String failOn() default "hash"; + + String message () default "hash is required if id is set"; + + Class[] payload () default {}; +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldNotNullIfOtherSetValidator.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldNotNullIfOtherSetValidator.java new file mode 100644 index 000000000..b12b3eaf7 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldNotNullIfOtherSetValidator.java @@ -0,0 +1,35 @@ +package gr.cite.notification.common.validation; + +import org.springframework.beans.BeanWrapperImpl; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.Objects; + +public class FieldNotNullIfOtherSetValidator implements ConstraintValidator { + + private String notNullField; + private String otherSetField; + + @Override + public void initialize(FieldNotNullIfOtherSet constraintAnnotation) { + this.notNullField = constraintAnnotation.notNullField(); + this.otherSetField = constraintAnnotation.otherSetField(); + } + + @Override + public boolean isValid(Object entity, ConstraintValidatorContext context) { + Object notNullValue = new BeanWrapperImpl(entity) + .getPropertyValue(this.notNullField); + Object otherSetValue = new BeanWrapperImpl(entity) + .getPropertyValue(this.otherSetField); + + boolean hashIsString = Objects.equals(new BeanWrapperImpl(entity) + .getPropertyType(this.otherSetField), String.class); + + boolean hashValueEmpty = otherSetValue == null || (hashIsString && ((String)otherSetValue).isBlank()); + + if (notNullValue != null && hashValueEmpty) return false; + return true; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldsValueMatch.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldsValueMatch.java new file mode 100644 index 000000000..872fdb55a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldsValueMatch.java @@ -0,0 +1,29 @@ +package gr.cite.notification.common.validation; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Constraint(validatedBy = FieldsValueMatchValidator.class) +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface FieldsValueMatch { + Class[] groups () default {}; + + String field(); + String fieldMatch(); + String failOn(); + + String message() default "Fields values don't match!"; + + Class[] payload () default {}; + + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @interface List { + FieldsValueMatch[] value(); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldsValueMatchValidator.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldsValueMatchValidator.java new file mode 100644 index 000000000..6f1a4cde0 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/FieldsValueMatchValidator.java @@ -0,0 +1,31 @@ +package gr.cite.notification.common.validation; + +import org.springframework.beans.BeanWrapperImpl; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class FieldsValueMatchValidator implements ConstraintValidator { + + private String field; + private String fieldMatch; + + @Override + public void initialize(FieldsValueMatch constraintAnnotation) { + this.field = constraintAnnotation.field(); + this.fieldMatch = constraintAnnotation.fieldMatch(); + } + + @Override + public boolean isValid(Object entity, ConstraintValidatorContext context) { + + Object fieldValue = new BeanWrapperImpl(entity).getPropertyValue(field); + Object fieldMatchValue = new BeanWrapperImpl(entity).getPropertyValue(fieldMatch); + + if (fieldValue != null) { + return fieldValue.equals(fieldMatchValue); + } else { + return fieldMatchValue == null; + } + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidEnum.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidEnum.java new file mode 100644 index 000000000..b48e7c503 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidEnum.java @@ -0,0 +1,16 @@ +package gr.cite.notification.common.validation; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + + +@Constraint(validatedBy = EnumNotNull.class) +@Documented +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidEnum { + String message() default "enum is required"; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidId.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidId.java new file mode 100644 index 000000000..ed0240c5e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidId.java @@ -0,0 +1,18 @@ +package gr.cite.notification.common.validation; + + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Constraint( validatedBy = { ValidIdValidator.class } ) +@Documented +@Target( { ElementType.FIELD } ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface ValidId { + Class[] groups() default {}; + + String message() default "id set but not valid"; + + Class[] payload() default {}; +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidIdValidator.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidIdValidator.java new file mode 100644 index 000000000..729c032ff --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidIdValidator.java @@ -0,0 +1,40 @@ +package gr.cite.notification.common.validation; + +import gr.cite.notification.convention.ConventionService; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.UUID; + +public class ValidIdValidator implements ConstraintValidator { + + @Autowired + private ConventionService conventionService; + + @Override + public void initialize(ValidId constraintAnnotation) { } + + @Override + public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { + if(o == null) return true; + else if(o instanceof UUID){ + UUID uuidId = (UUID)o; + return this.conventionService.isValidGuid(uuidId); + } + else if(o instanceof Integer){ + Integer intId = (Integer)o; + return this.conventionService.isValidId(intId); + } + else{ + String stringId = o.toString(); + UUID uuidId = null; + try { + uuidId = UUID.fromString(stringId); + }catch (Exception ex){ + return false; + } + return this.conventionService.isValidGuid(uuidId); + } + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidationServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidationServiceImpl.java new file mode 100644 index 000000000..b6ebb716a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/validation/ValidationServiceImpl.java @@ -0,0 +1,36 @@ +package gr.cite.notification.common.validation; + +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.validation.BaseValidationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import javax.validation.Validator; +import java.util.List; +import java.util.Map; + +@Service +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class ValidationServiceImpl extends BaseValidationService { + + private ErrorThesaurusProperties errors; + + @Autowired + public ValidationServiceImpl(Validator validator, ErrorThesaurusProperties errors){ + super(validator); + this.errors = errors; + } + + @Override + public void validateForce(T item, Class... groups) { + List>> validationErrors = this.validate(item, groups); + if (validationErrors != null && !validationErrors.isEmpty()) { + throw new MyValidationException(this.errors.getModelValidation().getCode(), + this.errors.getModelValidation().getMessage(), + validationErrors); + } + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/email/EmailConfig.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/email/EmailConfig.java new file mode 100644 index 000000000..d3d210f40 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/email/EmailProperties.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/email/EmailProperties.java new file mode 100644 index 000000000..9f57df829 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/email/EmailProperties.java @@ -0,0 +1,18 @@ +package gr.cite.notification.config.email; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConstructorBinding +@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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/formatting/FormattingServiceConfiguration.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/formatting/FormattingServiceConfiguration.java new file mode 100644 index 000000000..2287c3638 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/formatting/FormattingServiceProperties.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/formatting/FormattingServiceProperties.java new file mode 100644 index 000000000..9366026c3 --- /dev/null +++ b/dmp-backend/notification-service/notification/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; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConstructorBinding +@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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/formatting/FormattingUserprofileCacheOptions.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/formatting/FormattingUserprofileCacheOptions.java new file mode 100644 index 000000000..9363efbd6 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationConfig.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationConfig.java new file mode 100644 index 000000000..d757deb02 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationConfig.java @@ -0,0 +1,81 @@ +package gr.cite.notification.config.notification; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.schedule.NotificationScheduleTask; +import gr.cite.notification.service.message.common.MessageBuilderBase; +import gr.cite.notification.service.notificationscheduling.NotificationSchedulingService; +import gr.cite.notification.service.notificationscheduling.NotificationSchedulingServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +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 org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@Configuration +@EnableConfigurationProperties(NotificationProperties.class) +public class NotificationConfig { + + public static class BeanQualifier { + public static final String GLOBAL_POLICIES_MAP = "globalPoliciesMap"; + public static final String FLOW_MAP = "flowMap"; + public static final String STATIC_FIELD_LIST = "staticFieldList"; + public static final String CIPHER_FIELDS = "cipherFields"; + + } + private final ApplicationContext applicationContext; + private final NotificationProperties properties; + + @Autowired + public NotificationConfig(ApplicationContext applicationContext, NotificationProperties properties) { + this.applicationContext = applicationContext; + this.properties = properties; + } + + @Bean(BeanQualifier.GLOBAL_POLICIES_MAP) + public Map> getGlobalPoliciesMap() { + return properties.getResolver().getGlobalPolicies().stream() + .collect(Collectors.toMap(NotificationProperties.Resolver.GlobalPolicy::getType, + NotificationProperties.Resolver.GlobalPolicy::getContacts)); + } + + @Bean(BeanQualifier.FLOW_MAP) + public Map> getFlowMap() { + return properties.getMessage().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, + stringTemplateEntry -> stringTemplateEntry.getValue().getFlows() + .stream().collect(Collectors.toMap(NotificationProperties.Flow::getKey, flow -> flow)))); + } + + @Bean(BeanQualifier.STATIC_FIELD_LIST) + public List getStaticFieldList() { + return properties.getStaticFields() != null && properties.getStaticFields() + .getFields() != null ? new ArrayList<>(properties.getStaticFields() + .getFields()) : new ArrayList<>(); + } + + @Bean(BeanQualifier.CIPHER_FIELDS) + public Map getFieldCiphers() { + return properties.getMessage().values().stream().flatMap(template -> template.getFlows().stream()) + .filter(flow -> flow.getCipherFields() != null && !flow.getCipherFields().isEmpty()) + .collect(Collectors.toMap(NotificationProperties.Flow::getKey, flow -> new MessageBuilderBase.FieldCiphering(flow.getCipherFields()))); + } + + @Bean + public NotificationSchedulingService notificationSchedulingService() { + return new NotificationSchedulingServiceImpl(this.applicationContext, this.properties); + } + + @Bean + @ConditionalOnProperty(name = "notification.task.processor.enable", havingValue = "true") + public NotificationScheduleTask notificationScheduleTask() { + return new NotificationScheduleTask(this.applicationContext, this.properties); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationProperties.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationProperties.java new file mode 100644 index 000000000..88f47cca8 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationProperties.java @@ -0,0 +1,348 @@ +package gr.cite.notification.config.notification; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.notification.EmailOverrideMode; +import gr.cite.notification.common.types.notification.FieldInfo; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@ConstructorBinding +@ConfigurationProperties(prefix = "notification") +public class NotificationProperties { + + private final Task task; + private final Resolver resolver; + private final Map message; + private final Template overrideCache; + private final StaticFieldItems staticFields; + + public NotificationProperties(Task task, Resolver resolver, Map message, Template overrideCache, StaticFieldItems staticFields) { + this.task = task; + this.resolver = resolver; + this.message = message; + this.overrideCache = overrideCache; + this.staticFields = staticFields; + } + + public Task getTask() { + return task; + } + + public Resolver getResolver() { + return resolver; + } + + public Map getMessage() { + return message; + } + + public Template getOverrideCache() { + return overrideCache; + } + + public StaticFieldItems getStaticFields() { + return staticFields; + } + + public static class Task { + private final Processor processor; + + public Task(Processor processor) { + this.processor = processor; + } + + public Processor getProcessor() { + return processor; + } + + public static class Processor { + private final Boolean enable; + private final Long intervalSeconds; + private final Options options; + private final List overrides; + + public Processor(Boolean enable, Long intervalSeconds, Options options, List overrides) { + this.enable = enable; + this.intervalSeconds = intervalSeconds; + this.options = options; + this.overrides = overrides; + } + + public Boolean getEnable() { + return enable; + } + + public Long getIntervalSeconds() { + return intervalSeconds; + } + + public Options getOptions() { + return options; + } + + public List getOverrides() { + return overrides; + } + + public static class Options { + private final Long retryThreshold; + private final Long maxRetryDelaySeconds; + private final Long tooOldToSendSeconds; + private final Long tooOldToTrackSeconds; + + public Options(Long retryThreshold, Long maxRetryDelaySeconds, Long tooOldToSendSeconds, Long tooOldToTrackSeconds) { + this.retryThreshold = retryThreshold; + this.maxRetryDelaySeconds = maxRetryDelaySeconds; + this.tooOldToSendSeconds = tooOldToSendSeconds; + this.tooOldToTrackSeconds = tooOldToTrackSeconds; + } + + public Long getRetryThreshold() { + return retryThreshold; + } + + public Long getMaxRetryDelaySeconds() { + return maxRetryDelaySeconds; + } + + public Long getTooOldToSendSeconds() { + return tooOldToSendSeconds; + } + + public Long getTooOldToTrackSeconds() { + return tooOldToTrackSeconds; + } + } + } + } + + public static class Resolver { + private final List globalPolicies; + + public Resolver(List globalPolicies) { + this.globalPolicies = globalPolicies; + } + + public List getGlobalPolicies() { + return globalPolicies; + } + + public static class GlobalPolicy { + private final UUID type; + private final List contacts; + + public GlobalPolicy(UUID type, List contacts) { + this.type = type; + this.contacts = contacts; + } + + public UUID getType() { + return type; + } + + public List getContacts() { + return contacts; + } + } + } + + public static class Template { + private final TemplateCache templateCache; + + private final List flows; + + public Template(TemplateCache templateCache, List flows) { + this.templateCache = templateCache; + this.flows = flows; + } + + public TemplateCache getTemplateCache() { + return templateCache; + } + + + public List getFlows() { + return flows; + } + + public static class TemplateCache { + private final String prefix; + private final String keyPattern; + + public TemplateCache(String prefix, String keyPattern) { + this.prefix = prefix; + this.keyPattern = keyPattern; + } + + public String getPrefix() { + return prefix; + } + + public String getKeyPattern() { + return keyPattern; + } + } + } + + public static class StaticFieldItems { + private final List Fields; + + public StaticFieldItems(List fields) { + Fields = fields; + } + + public List getFields() { + return Fields; + } + } + + public static class Field { + private final String key; + private final String type; + private final String value; + + public Field(String key, String type, String value) { + this.key = key; + this.type = type; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getType() { + return type; + } + + public String getValue() { + return value; + } + } + + public static class Flow { + private final UUID key; + private final String subjectPath; + private final String subjectKey; + private final FieldOption subjectFieldOptions; + private final String bodyPath; + private final String bodyKey; + private final FieldOption bodyFieldOptions; + private final List cc; + private final EmailOverrideMode ccMode; + private final List bcc; + private final EmailOverrideMode bccMode; + private final Boolean allowAttachments; + private final List cipherFields; + private final String priorityKey; + private final List extraDataKeys; + + public Flow(UUID key, String subjectPath, String subjectKey, FieldOption subjectFieldOptions, String bodyPath, String bodyKey, FieldOption bodyFieldOptions, List cc, EmailOverrideMode ccMode, List bcc, EmailOverrideMode bccMode, Boolean allowAttachments, List cipherFields, String priorityKey, List extraDataKeys) { + this.key = key; + this.subjectPath = subjectPath; + this.subjectKey = subjectKey; + this.subjectFieldOptions = subjectFieldOptions; + this.bodyPath = bodyPath; + this.bodyKey = bodyKey; + this.bodyFieldOptions = bodyFieldOptions; + this.cc = cc; + this.ccMode = ccMode; + this.bcc = bcc; + this.bccMode = bccMode; + this.allowAttachments = allowAttachments; + this.cipherFields = cipherFields; + this.priorityKey = priorityKey; + this.extraDataKeys = extraDataKeys; + } + + public UUID getKey() { + return key; + } + + public String getSubjectPath() { + return subjectPath; + } + + public String getSubjectKey() { + return subjectKey; + } + + public FieldOption getSubjectFieldOptions() { + return subjectFieldOptions; + } + + public String getBodyPath() { + return bodyPath; + } + + public String getBodyKey() { + return bodyKey; + } + + public FieldOption getBodyFieldOptions() { + return bodyFieldOptions; + } + + public List getCc() { + return cc; + } + + public EmailOverrideMode getCcMode() { + return ccMode; + } + + public List getBcc() { + return bcc; + } + + public EmailOverrideMode getBccMode() { + return bccMode; + } + + public Boolean getAllowAttachments() { + return allowAttachments; + } + + public List getCipherFields() { + return cipherFields; + } + + public String getPriorityKey() { + return priorityKey; + } + + public List getExtraDataKeys() { + return extraDataKeys; + } + + public static class FieldOption { + private final List mandatory; + private final List optional; + + private final Map formatting; + + public FieldOption(List mandatory, List optional, Map formatting) { + this.mandatory = mandatory; + this.optional = optional; + this.formatting = formatting; + } + + public List getMandatory() { + return mandatory; + } + + public List getOptional() { + return optional; + } + + public Map getFormatting() { + return formatting; + } + + } + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationTemplateCacheOptions.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationTemplateCacheOptions.java new file mode 100644 index 000000000..279bfe169 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/config/notification/NotificationTemplateCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.config.notification; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.template") +public class NotificationTemplateCacheOptions extends CacheOptions { +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/convention/ConventionService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/convention/ConventionService.java new file mode 100644 index 000000000..cfc2c3a02 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/convention/ConventionServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/convention/ConventionServiceImpl.java new file mode 100644 index 000000000..2ae59083c --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/InAppNotificationEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/InAppNotificationEntity.java new file mode 100644 index 000000000..ac42461f6 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/InAppNotificationEntity.java @@ -0,0 +1,169 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.InAppNotificationPriority; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationInAppTracking; +import gr.cite.notification.data.conventers.InAppNotificationPriorityConverter; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.conventers.NotificationInAppTrackingConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"ntf_InAppNotification\"") +public class InAppNotificationEntity extends TenantScopedBaseEntity { + public static class Field { + public static final String ID = "id"; + public static final String USER_ID = "userId"; + public static final String IS_ACTIVE = "isActive"; + public static final String TYPE = "type"; + public static final String READ_TIME = "readTime"; + public static final String TRACKING_STATE = "trackingState"; + public static final String PRIORITY = "priority"; + public static final String SUBJECT = "subject"; + public static final String BODY = "body"; + public static final String EXTRA_DATA = "extraData"; + public static final String CREATED_AT = "createdAt"; + public static final String UPDATED_AT = "updatedAt"; + } + + @Id + @Column(name = "id", columnDefinition = "uuid", nullable = false) + private UUID id; + + @Column(name = "user", columnDefinition = "uuid", nullable = false) + private UUID userId; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + @Column(name = "type", columnDefinition = "uuid", nullable = false) + private UUID type; + + @Column(name = "read_time") + private Instant readTime; + + @Column(name = "tracking_state", nullable = false) + @Convert(converter = NotificationInAppTrackingConverter.class) + private NotificationInAppTracking trackingState; + + @Column(name = "priority", nullable = false) + @Convert(converter = InAppNotificationPriorityConverter.class) + private InAppNotificationPriority priority; + + @Column(name = "subject") + private String subject; + + @Column(name = "body") + private String body; + + @Column(name = "extra_data") + private String extraData; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + @Column(name = "updated_at", nullable = false) + @Version + private Instant updatedAt; + + 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 IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public Instant getReadTime() { + return readTime; + } + + public void setReadTime(Instant readTime) { + this.readTime = readTime; + } + + public NotificationInAppTracking getTrackingState() { + return trackingState; + } + + public void setTrackingState(NotificationInAppTracking trackingState) { + this.trackingState = trackingState; + } + + public InAppNotificationPriority getPriority() { + return priority; + } + + public void setPriority(InAppNotificationPriority priority) { + this.priority = priority; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getExtraData() { + return extraData; + } + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/NotificationEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/NotificationEntity.java new file mode 100644 index 000000000..f4925acbc --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/NotificationEntity.java @@ -0,0 +1,216 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.*; +import gr.cite.notification.data.conventers.*; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"ntf_Notification\"") +public class NotificationEntity extends TenantScopedBaseEntity { + + public static class Field { + public final static String _id = "id"; + public static final String _userId = "userId"; + public static final String _type = "type"; + public static final String _contactTypeHint = "contactTypeHint"; + public static final String _contactHint = "contactHint"; + public final static String _notifiedAt = "notifiedAt"; + public final static String _isActive = "isActive"; + public final static String _createdAt = "createdAt"; + public final static String _updatedAt = "updatedAt"; + public final static String _data = "data"; + public final static String _retryCount = "retryCount"; + public final static String _notifyState = "notifyState"; + public final static String _notifiedWith = "notifiedWith"; + public final static String _tenantId = "tenantId"; + public final static String _trackingState = "trackingState"; + public final static String _trackingProcess = "trackingProcess"; + public final static String _trackingData = "trackingData"; + } + + @Id + @Column(name = "\"id\"", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + @Column(name = "\"user\"", columnDefinition = "uuid") + private UUID userId; + + @Column(name = "\"type\"", columnDefinition = "uuid", nullable = false) + private UUID type; + + @Column(name = "\"contact_type_hint\"") + @Convert(converter = NotificationContactTypeConverter.class) + private NotificationContactType contactTypeHint; + + @Column(name = "\"contact_hint\"") + private String contactHint; + + @Column(name = "\"notify_state\"", nullable = false) + @Convert(converter = NotificationNotifyStateConverter.class) + private NotificationNotifyState notifyState; + + @Column(name = "\"notified_with\"") + @Convert(converter = NotificationContactTypeConverter.class) + private NotificationContactType notifiedWith; + @Column(name = "\"notified_at\"") + private Instant notifiedAt; + + @Column(name = "\"data\"") + private String data; + + @Column(name = "\"retry_count\"") + private Integer retryCount; + + @Column(name = "\"is_active\"", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + @Column(name = "\"created_at\"", nullable = false) + private Instant createdAt; + + @Column(name = "\"updated_at\"", nullable = false) + private Instant updatedAt; + + @Column(name = "\"tracking_state\"", nullable = false) + @Convert(converter = NotificationTrackingStateConverter.class) + private NotificationTrackingState trackingState; + + @Column(name = "\"tracking_process\"", nullable = false) + @Convert(converter = NotificationTrackingProcessConverter.class) + private NotificationTrackingProcess trackingProcess; + + @Column(name = "\"tracking_data\"") + private String trackingData; + + 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 UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public NotificationContactType getContactTypeHint() { + return contactTypeHint; + } + + public void setContactTypeHint(NotificationContactType contactTypeHint) { + this.contactTypeHint = contactTypeHint; + } + + public String getContactHint() { + return contactHint; + } + + public void setContactHint(String contactHint) { + this.contactHint = contactHint; + } + + public NotificationContactType getNotifiedWith() { + return notifiedWith; + } + + public void setNotifiedWith(NotificationContactType notifiedWith) { + this.notifiedWith = notifiedWith; + } + + public NotificationNotifyState getNotifyState() { + return notifyState; + } + + public void setNotifyState(NotificationNotifyState notifyState) { + this.notifyState = notifyState; + } + + public Instant getNotifiedAt() { + return notifiedAt; + } + + public void setNotifiedAt(Instant notifiedAt) { + this.notifiedAt = notifiedAt; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + 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 NotificationTrackingState getTrackingState() { + return trackingState; + } + + public void setTrackingState(NotificationTrackingState trackingState) { + this.trackingState = trackingState; + } + + public NotificationTrackingProcess getTrackingProcess() { + return trackingProcess; + } + + public void setTrackingProcess(NotificationTrackingProcess trackingProcess) { + this.trackingProcess = trackingProcess; + } + + public String getTrackingData() { + return trackingData; + } + + public void setTrackingData(String trackingData) { + this.trackingData = trackingData; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java new file mode 100644 index 000000000..ac0cf52ce --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java @@ -0,0 +1,176 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.queueinbox.entity.QueueInbox; +import gr.cite.queueinbox.entity.QueueInboxStatus; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"ntf_QueueInbox\"") +public class QueueInboxEntity implements QueueInbox { + @Id + @Column(name = "\"id\"", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + public final static String _id = "id"; + + @Column(name = "\"queue\"", nullable = false, length = 50) + private String queue; + public final static String _queue = "queue"; + + @Column(name = "\"exchange\"", nullable = false, length = 50) + private String exchange; + public final static String _exchange = "exchange"; + + @Column(name = "\"route\"", nullable = false, length = 50) + private String route; + public final static String _route = "route"; + + @Column(name = "\"application_id\"", nullable = false, length = 100) + private String applicationId; + public final static String _applicationId = "applicationId"; + + @Column(name = "\"message_id\"", columnDefinition = "uuid", nullable = false) + private UUID messageId; + public final static String _messageId = "messageId"; + + @Column(name = "\"message\"", columnDefinition = "json", nullable = false) + private String message; + public final static String _message = "message"; + + @Column(name = "\"retry_count\"", nullable = true) + private Integer retryCount; + public final static String _retryCount = "retryCount"; + + @Column(name = "\"tenant\"", columnDefinition = "uuid", nullable = true) + private UUID tenantId; + public final static String _tenantId = "tenantId"; + + @Column(name = "\"is_active\"", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + public final static String _isActive = "isActive"; + + @Column(name = "\"status\"", length = 50, nullable = false) + @Enumerated(EnumType.STRING) + private QueueInboxStatus status; + public final static String _status = "status"; + + @Column(name = "\"created_at\"", nullable = false) + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + @Column(name = "\"updated_at\"", nullable = false) + @Version + private Instant updatedAt; + public final static String _updatedAt = "updatedAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + 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 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; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + 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 getQueue() { + return queue; + } + + public void setQueue(String queue) { + this.queue = queue; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + @Override + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + public QueueInboxStatus getStatus() { + return status; + } + + public void setStatus(QueueInboxStatus status) { + this.status = status; + } +} + diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java new file mode 100644 index 000000000..4ebf0a938 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java @@ -0,0 +1,175 @@ +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 javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"ntf_QueueOutbox\"") +public class QueueOutboxEntity implements QueueOutbox { + @Id + @Column(name = "\"id\"", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + public final static String _id = "id"; + + @Column(name = "\"exchange\"", nullable = false, length = 50) + private String exchange; + public final static String _exchange = "exchange"; + + @Column(name = "\"route\"", length = 50) + private String route; + public final static String _route = "route"; + + @Column(name = "\"message_id\"", columnDefinition = "uuid", nullable = false) + private UUID messageId; + public final static String _messageId = "messageId"; + + @Column(name = "\"message\"", columnDefinition = "json", nullable = false) + private String message; + public final static String _message = "message"; + + @Column(name = "\"notify_status\"", length = 20, nullable = false) + @Enumerated(EnumType.STRING) + private QueueOutboxNotifyStatus notifyStatus; + public final static String _notifyStatus = "notifyStatus"; + + @Column(name = "\"retry_count\"", nullable = false) + private int retryCount; + public final static String _retryCount = "retryCount"; + + @Column(name = "\"published_at\"", nullable = true) + private Instant publishedAt; + public final static String _publishedAt = "publishedAt"; + + @Column(name = "\"confirmed_at\"", nullable = true) + private Instant confirmedAt; + public final static String _confirmedAt = "confirmedAt"; + + @Column(name = "\"tenant\"", columnDefinition = "uuid", nullable = true) + private UUID tenantId; + public final static String _tenantId = "tenantId"; + + @Column(name = "\"is_active\"", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + public final static String _isActive = "isActive"; + + @Column(name = "\"created_at\"", nullable = false) + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + @Column(name = "\"updated_at\"", nullable = false) + private Instant updatedAt; + public final static String _updatedAt = "updatedAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + 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 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; + } + + public QueueOutboxNotifyStatus getNotifyStatus() { + return notifyStatus; + } + + public void setNotifyStatus(QueueOutboxNotifyStatus notifyStatus) { + this.notifyStatus = notifyStatus; + } + + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + public Instant getPublishedAt() { + return publishedAt; + } + + public void setPublishedAt(Instant publishedAt) { + this.publishedAt = publishedAt; + } + + public Instant getConfirmedAt() { + return confirmedAt; + } + + public void setConfirmedAt(Instant confirmedAt) { + this.confirmedAt = confirmedAt; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + 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; + } +} + diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java new file mode 100644 index 000000000..b7744dd08 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantConfigurationEntity.java @@ -0,0 +1,95 @@ +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 javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"ntf_TenantConfiguration\"") +public class TenantConfigurationEntity extends TenantScopedBaseEntity { + + public static class Field { + public static final String ID = "id"; + public static final String TENANT_ID = "tenantId"; + public static final String TYPE = "type"; + public static final String VALUE = "value"; + public static final String IS_ACTIVE = "isActive"; + public static final String CREATED_AT = "createdAt"; + public static final String UPDATED_AT = "updatedAt"; + } + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + + @Column(name = "type", nullable = false) + @Convert(converter = TenantConfigurationTypeConverter.class) + private TenantConfigurationType type; + + @Column(name = "value", nullable = false) + private String value; + + @Column(name = "is_active", nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + 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; + } + + 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantEntity.java new file mode 100644 index 000000000..74bb43f5e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantEntity.java @@ -0,0 +1,110 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; + +import javax.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 final static String _id = "id"; + + @Column(name = "code", length = 200, nullable = false) + private String code; + public final static String _code = "code"; + + @Column(name = "name", length = 500, nullable = false) + private String name; + public final static String _name = "name"; + + @Column(name = "description", nullable = false) + private String description; + public final static String _description = "description"; + + @Column(name = "is_active", length = 20, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + public final static String _isActive = "isActive"; + + @Column(name = "config") + private String config; + public final static String _config = "config"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + public final static String _updatedAt = "updatedAt"; + + 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 String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public String getConfig() { + return config; + } + + public void setConfig(String config) { + this.config = config; + } + + 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java new file mode 100644 index 000000000..4426b514b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantScopedEntityManager.java @@ -0,0 +1,94 @@ +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 org.hibernate.Session; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.persistence.PersistenceContext; +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 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantUserEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantUserEntity.java new file mode 100644 index 000000000..a0823546d --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/TenantUserEntity.java @@ -0,0 +1,75 @@ +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 javax.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 final static String _id = "id"; + + @Column(name = "user", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID userId; + public final static String _userId = "userId"; + + @Column(name = "is_active", length = 100, nullable = false) + @Convert(converter = IsActiveConverter.class) + private IsActive isActive; + public final static String _isActive = "isActive"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + public final static String _updatedAt = "updatedAt"; + + 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 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java new file mode 100644 index 000000000..678d3aa78 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserContactInfoEntity.java @@ -0,0 +1,91 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.ContactInfoType; +import gr.cite.notification.data.conventers.ContactInfoTypeConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.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 final static String _type = "type"; + + @Column(name = "value", nullable = false) + private String value; + public final static String _value = "value"; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + + 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; + } +} + diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserEntity.java new file mode 100644 index 000000000..20b69be02 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserEntity.java @@ -0,0 +1,91 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.conventers.IsActiveConverter; + +import javax.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 final static String _id = "id"; + + @Column(name = "name", length = UserEntity._nameLength, nullable = true) + private String name = null; + public final static String _name = "name"; + public final static int _nameLength = 250; + + + @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"; + + @Column(name = "additional_info", nullable = true) + private String additionalInfo; + public final static String _additionalInfo = "additionalInfo"; + + 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 getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(String additionalInfo) { + this.additionalInfo = additionalInfo; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserNotificationPreferenceEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserNotificationPreferenceEntity.java new file mode 100644 index 000000000..05d1e149c --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserNotificationPreferenceEntity.java @@ -0,0 +1,83 @@ +package gr.cite.notification.data; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.composite.CompositeUserNotificationPreferenceId; +import gr.cite.notification.data.conventers.NotificationContactTypeConverter; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"ntf_UserNotificationPreference\"") +@IdClass(CompositeUserNotificationPreferenceId.class) +public class UserNotificationPreferenceEntity extends TenantScopedBaseEntity { + + public static class Field { + public static final String USER_ID = "userId"; + public static final String TYPE = "type"; + public static final String CHANNEL = "channel"; + public static final String ORDINAL = "ordinal"; + public static final String CREATED_AT = "createdAt"; + } + + @Id + @Column(name = "user", columnDefinition = "uuid", nullable = false) + private UUID userId; + + @Id + @Column(name = "type", columnDefinition = "uuid", nullable = false) + private UUID type; + + @Id + @Column(name = "channel", nullable = false) + @Convert(converter = NotificationContactTypeConverter.class) + private NotificationContactType channel; + + @Column(name = "ordinal", nullable = false) + private Integer ordinal; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public NotificationContactType getChannel() { + return channel; + } + + public void setChannel(NotificationContactType channel) { + this.channel = channel; + } + + 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/composite/CompositeUserNotificationPreferenceId.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/composite/CompositeUserNotificationPreferenceId.java new file mode 100644 index 000000000..4f132e979 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/composite/CompositeUserNotificationPreferenceId.java @@ -0,0 +1,45 @@ +package gr.cite.notification.data.composite; + +import gr.cite.notification.common.enums.NotificationContactType; + +import java.io.Serializable; +import java.util.UUID; + +public class CompositeUserNotificationPreferenceId implements Serializable { + private UUID userId; + private UUID type; + private NotificationContactType channel; + + public CompositeUserNotificationPreferenceId() { + } + + public CompositeUserNotificationPreferenceId(UUID userId, UUID type, NotificationContactType channel) { + this.userId = userId; + this.type = type; + this.channel = channel; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public NotificationContactType getChannel() { + return channel; + } + + public void setChannel(NotificationContactType channel) { + this.channel = channel; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java new file mode 100644 index 000000000..5a2859c6f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/ContactInfoTypeConverter.java @@ -0,0 +1,12 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.ContactInfoType; + +import javax.persistence.Converter; + +@Converter +public class ContactInfoTypeConverter extends DatabaseEnumConverter{ + protected ContactInfoType of(Short i) { + return ContactInfoType.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/DatabaseEnum.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/DatabaseEnum.java new file mode 100644 index 000000000..4dd378c02 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java new file mode 100644 index 000000000..0962df9fc --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/DatabaseEnumConverter.java @@ -0,0 +1,21 @@ +package gr.cite.notification.data.conventers; + + +import javax.persistence.AttributeConverter; +import javax.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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/InAppNotificationPriorityConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/InAppNotificationPriorityConverter.java new file mode 100644 index 000000000..6bc531660 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/InAppNotificationPriorityConverter.java @@ -0,0 +1,12 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.InAppNotificationPriority; + +import javax.persistence.Converter; + +@Converter +public class InAppNotificationPriorityConverter extends DatabaseEnumConverter { + public InAppNotificationPriority of(Short i) { + return InAppNotificationPriority.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java new file mode 100644 index 000000000..cf8b1a095 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/IsActiveConverter.java @@ -0,0 +1,13 @@ +package gr.cite.notification.data.conventers; + + +import gr.cite.notification.common.enums.IsActive; + +import javax.persistence.Converter; + +@Converter +public class IsActiveConverter extends DatabaseEnumConverter { + public IsActive of(Short i) { + return IsActive.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationContactTypeConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationContactTypeConverter.java new file mode 100644 index 000000000..889c4a81b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationContactTypeConverter.java @@ -0,0 +1,24 @@ +package gr.cite.notification.data.conventers; + + +import gr.cite.notification.common.enums.NotificationContactType; + +import javax.persistence.Converter; + +@Converter +public class NotificationContactTypeConverter extends DatabaseEnumConverter { + public NotificationContactType of(Short i) { + return NotificationContactType.of(i); + } + + @Override + public Short convertToDatabaseColumn(NotificationContactType value) { + if (value == null) return null; + return value.getValue(); + } + + @Override + public NotificationContactType convertToEntityAttribute(Short dbData) { + return dbData == null ? null : this.of(dbData); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationInAppTrackingConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationInAppTrackingConverter.java new file mode 100644 index 000000000..831281c9a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationInAppTrackingConverter.java @@ -0,0 +1,12 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.NotificationInAppTracking; + +import javax.persistence.Converter; + +@Converter +public class NotificationInAppTrackingConverter extends DatabaseEnumConverter { + public NotificationInAppTracking of(Short i) { + return NotificationInAppTracking.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationNotifyStateConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationNotifyStateConverter.java new file mode 100644 index 000000000..1a03b8726 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationNotifyStateConverter.java @@ -0,0 +1,24 @@ +package gr.cite.notification.data.conventers; + + +import gr.cite.notification.common.enums.NotificationNotifyState; + +import javax.persistence.Converter; + +@Converter +public class NotificationNotifyStateConverter extends DatabaseEnumConverter { + public NotificationNotifyState of(Short i) { + return NotificationNotifyState.of(i); + } + + @Override + public Short convertToDatabaseColumn(NotificationNotifyState value) { + if (value == null) return null; + return value.getValue(); + } + + @Override + public NotificationNotifyState convertToEntityAttribute(Short dbData) { + return dbData == null ? null : this.of(dbData); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationTrackingProcessConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationTrackingProcessConverter.java new file mode 100644 index 000000000..64e7ada64 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationTrackingProcessConverter.java @@ -0,0 +1,12 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.notification.common.enums.NotificationTrackingProcess; + +import javax.persistence.Converter; + +@Converter +public class NotificationTrackingProcessConverter extends DatabaseEnumConverter { + public NotificationTrackingProcess of(Short i) { + return NotificationTrackingProcess.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationTrackingStateConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationTrackingStateConverter.java new file mode 100644 index 000000000..25109651f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/NotificationTrackingStateConverter.java @@ -0,0 +1,13 @@ +package gr.cite.notification.data.conventers; + + +import gr.cite.notification.common.enums.NotificationTrackingState; + +import javax.persistence.Converter; + +@Converter +public class NotificationTrackingStateConverter extends DatabaseEnumConverter { + public NotificationTrackingState of(Short i) { + return NotificationTrackingState.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java new file mode 100644 index 000000000..2ade1f572 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/TenantConfigurationTypeConverter.java @@ -0,0 +1,13 @@ +package gr.cite.notification.data.conventers; + + +import gr.cite.notification.common.enums.TenantConfigurationType; + +import javax.persistence.Converter; + +@Converter +public class TenantConfigurationTypeConverter extends DatabaseEnumConverter { + public TenantConfigurationType of(Short i) { + return TenantConfigurationType.of(i); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantFilterAspect.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantFilterAspect.java new file mode 100644 index 000000000..5c6f90bab --- /dev/null +++ b/dmp-backend/notification-service/notification/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 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; +import javax.persistence.EntityManager; + +@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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantListener.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantListener.java new file mode 100644 index 000000000..57d59ae06 --- /dev/null +++ b/dmp-backend/notification-service/notification/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 org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.management.InvalidApplicationException; +import javax.persistence.PrePersist; +import javax.persistence.PreRemove; +import javax.persistence.PreUpdate; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java new file mode 100644 index 000000000..01705eb10 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/tenant/TenantScopedBaseEntity.java @@ -0,0 +1,37 @@ +package gr.cite.notification.data.tenant; + +import gr.cite.notification.common.scope.tenant.TenantScoped; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.ParamDef; + +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; +import java.util.UUID; + +@MappedSuperclass +//@Getter +//@Setter +//@NoArgsConstructor +@FilterDef(name = TenantScopedBaseEntity.tenantFilter, parameters = {@ParamDef(name = TenantScopedBaseEntity.tenantFilterTenantParam, type = "string")}) +@Filter(name = "tenantFilter", condition = "tenant = (cast(:tenantId as uuid))") +@EntityListeners(TenantListener.class) +public abstract class TenantScopedBaseEntity implements TenantScoped, Serializable { + private static final long serialVersionUID = 1L; + public static final String tenantFilter = "tenantFilter"; + public static final String tenantFilterTenantParam = "tenantId"; + + @Column(name = "tenant", columnDefinition = "uuid", nullable = true) + 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/errorcode/ErrorDescription.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/errorcode/ErrorDescription.java new file mode 100644 index 000000000..eb1279ae6 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusConfiguration.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusConfiguration.java new file mode 100644 index 000000000..08adb6f11 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusProperties.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/errorcode/ErrorThesaurusProperties.java new file mode 100644 index 000000000..d19b62512 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/EventBroker.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/EventBroker.java new file mode 100644 index 000000000..3bc5a14b8 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/TenantTouchedEvent.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/TenantTouchedEvent.java new file mode 100644 index 000000000..29be1cb7e --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/UserAddedToTenantEvent.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/UserAddedToTenantEvent.java new file mode 100644 index 000000000..c6c0c0f58 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/UserRemovedFromTenantEvent.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/UserRemovedFromTenantEvent.java new file mode 100644 index 000000000..6a175e34f --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/UserTouchedEvent.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/event/UserTouchedEvent.java new file mode 100644 index 000000000..44b83cd9a --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleConfiguration.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleConfiguration.java new file mode 100644 index 000000000..b6699c02f --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleProperties.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleProperties.java new file mode 100644 index 000000000..296b55a92 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleService.java new file mode 100644 index 000000000..f2f54e6e7 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/locale/LocaleServiceImpl.java new file mode 100644 index 000000000..71bdb83e7 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/InAppNotification.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/InAppNotification.java new file mode 100644 index 000000000..7af31d850 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/InAppNotification.java @@ -0,0 +1,155 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.InAppNotificationPriority; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationInAppTracking; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; +public class InAppNotification { + public static class Field { + public static final String ID = "id"; + public static final String USER = "user"; + public static final String IS_ACTIVE = "isActive"; + public static final String TYPE = "type"; + public static final String READ_TIME = "readTime"; + public static final String TRACKING_STATE = "trackingState"; + public static final String PRIORITY = "priority"; + public static final String SUBJECT = "subject"; + public static final String BODY = "body"; + public static final String EXTRA_DATA = "extraData"; + public static final String CREATED_AT = "createdAt"; + public static final String UPDATED_AT = "updatedAt"; + public static final String TENANT = "tenant"; + public static final String HASH = "hash"; + } + + private UUID id; + private User user; + private IsActive isActive; + private UUID type; + private Instant readTime; + private NotificationInAppTracking trackingState; + private InAppNotificationPriority priority; + private String subject; + private String body; + private String extraData; + private Instant createdAt; + private Instant updatedAt; + private Tenant tenant; + private String hash; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public Instant getReadTime() { + return readTime; + } + + public void setReadTime(Instant readTime) { + this.readTime = readTime; + } + + public NotificationInAppTracking getTrackingState() { + return trackingState; + } + + public void setTrackingState(NotificationInAppTracking trackingState) { + this.trackingState = trackingState; + } + + public InAppNotificationPriority getPriority() { + return priority; + } + + public void setPriority(InAppNotificationPriority priority) { + this.priority = priority; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getExtraData() { + return extraData; + } + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + 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; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Tenant getTenant() { + return tenant; + } + + public void setTenant(Tenant tenant) { + this.tenant = tenant; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/Notification.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/Notification.java new file mode 100644 index 000000000..16248f64a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/Notification.java @@ -0,0 +1,131 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationContactType; + +import java.time.Instant; +import java.util.UUID; + +public class Notification { + + private UUID id; + public final static String _id = "id"; + + private Tenant tenant; + public final static String _tenant = "tenant"; + + private User user; + public static final String _user = "user"; + + private UUID type; + public static final String _type = "type"; + + private NotificationContactType contactTypeHint; + public static final String _contactTypeHint = "contactTypeHint"; + + private String contactHint; + public static final String _contactHint = "contactHint"; + + private Instant notifiedAt; + public final static String _notifiedAt = "notifiedAt"; + + private String hash; + public final static String _hash = "hash"; + + private IsActive isActive; + public final static String _isActive = "isActive"; + + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + private Instant updatedAt; + public final static String _updatedAt = "updatedAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Tenant getTenant() { + return tenant; + } + + public void setTenant(Tenant tenant) { + this.tenant = tenant; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public gr.cite.notification.common.enums.NotificationContactType getContactTypeHint() { + return contactTypeHint; + } + + public void setContactTypeHint(gr.cite.notification.common.enums.NotificationContactType contactTypeHint) { + this.contactTypeHint = contactTypeHint; + } + + public String getContactHint() { + return contactHint; + } + + public void setContactHint(String contactHint) { + this.contactHint = contactHint; + } + + public Instant getNotifiedAt() { + return notifiedAt; + } + + public void setNotifiedAt(Instant notifiedAt) { + this.notifiedAt = notifiedAt; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/SendNotificationResult.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/SendNotificationResult.java new file mode 100644 index 000000000..b59a27807 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/SendNotificationResult.java @@ -0,0 +1,47 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.NotificationContactType; + +public class SendNotificationResult { + private Boolean success; + private NotificationContactType contactType; + private String trackingData; + + public SendNotificationResult() { + } + + public SendNotificationResult(Boolean success, NotificationContactType contactType) { + this.success = success; + this.contactType = contactType; + } + + public SendNotificationResult(Boolean success, NotificationContactType contactType, String trackingData) { + this.success = success; + this.contactType = contactType; + this.trackingData = trackingData; + } + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public NotificationContactType getContactType() { + return contactType; + } + + public void setContactType(NotificationContactType contactType) { + this.contactType = contactType; + } + + public String getTrackingData() { + return trackingData; + } + + public void setTrackingData(String trackingData) { + this.trackingData = trackingData; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/Tenant.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/Tenant.java new file mode 100644 index 000000000..1d86f9ebe --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/Tenant.java @@ -0,0 +1,109 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.model.tenantconfig.TenantConfig; + +import java.time.Instant; +import java.util.UUID; + +public class Tenant { + + private UUID id; + public final static String _id = "id"; + + private String code; + public final static String _code = "code"; + + private String name; + public final static String _name = "name"; + + private String description; + public final static String _description = "description"; + + private IsActive isActive; + public final static String _isActive = "isActive"; + + private TenantConfig config; + public final static String _config = "config"; + + private Instant createdAt; + public final static String _createdAt = "createdAt"; + + private Instant updatedAt; + public final static String _updatedAt = "updatedAt"; + + public final static String _hash = "hash"; + private String 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 String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public TenantConfig getConfig() { + return config; + } + + public void setConfig(TenantConfig config) { + this.config = config; + } + + 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/TenantConfiguration.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/TenantConfiguration.java new file mode 100644 index 000000000..62b678ca6 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/TenantConfiguration.java @@ -0,0 +1,126 @@ +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 gr.cite.notification.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; + +import java.time.Instant; +import java.util.UUID; + +public class TenantConfiguration { + + public static class Field { + public static final String ID = "id"; + public static final String TENANT_ID = "tenantId"; + public static final String TYPE = "type"; + public static final String VALUE = "value"; + public static final String EMAIL_CLIENT_DATA = "emailClientData"; + public static final String DEFAULT_USER_LOCALE_DATA = "defaultUserLocaleData"; + public static final String NOTIFIER_LIST_DATA = "notifierListData"; + public static final String IS_ACTIVE = "isActive"; + public static final String CREATED_AT = "createdAt"; + public static final String UPDATED_AT = "updatedAt"; + public static final String HASH = "hash"; + } + private UUID id; + private UUID tenantId; + private TenantConfigurationType type; + private String value; + private EmailClientConfigurationDataContainer emailClientData; + private DefaultUserLocaleConfigurationDataContainer defaultUserLocaleData; + private NotifierListConfigurationDataContainer notifierListData; + private IsActive isActive; + private Instant createdAt; + private Instant updatedAt; + private String 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 NotifierListConfigurationDataContainer getNotifierListData() { + return notifierListData; + } + + public void setNotifierListData(NotifierListConfigurationDataContainer notifierListData) { + this.notifierListData = notifierListData; + } + + 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/User.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/User.java new file mode 100644 index 000000000..e232b39cc --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/User.java @@ -0,0 +1,81 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.IsActive; + +import java.time.Instant; +import java.util.List; +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 final static String _hash = "hash"; + private String hash; + + public final static 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserContactInfo.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserContactInfo.java new file mode 100644 index 000000000..280126100 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserContactInfo.java @@ -0,0 +1,88 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.ContactInfoType; + +import java.time.Instant; +import java.util.UUID; + +public class UserContactInfo { + + private UUID id; + public static final String _id = "id"; + + private User user; + public static final String _user = "user"; + + private ContactInfoType type; + public static final String _type = "type"; + + private String value; + public static final String _value = "value"; + + private Integer ordinal; + public static final String _ordinal = "ordinal"; + + private Tenant tenant; + public static final String _tenant = "tenant"; + + private Instant createdAt; + public static final String _createdAt = "createdAt"; + + + 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 User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + 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 Tenant getTenant() { + return tenant; + } + + public void setTenant(Tenant tenant) { + this.tenant = tenant; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserNotificationPreference.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserNotificationPreference.java new file mode 100644 index 000000000..797ad7b92 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserNotificationPreference.java @@ -0,0 +1,76 @@ +package gr.cite.notification.model; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.composite.CompositeUserNotificationPreferenceId; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +public class UserNotificationPreference { + + public static class Field { + public static final String USER_ID = "userId"; + public static final String TYPE = "type"; + public static final String CHANNEL = "channel"; + public static final String ORDINAL = "ordinal"; + public static final String CREATED_AT = "createdAt"; + public static final String TENANT_ID = "tenantId"; + } + + private UUID userId; + private UUID type; + private UUID tenantId; + private NotificationContactType channel; + private Integer ordinal; + private Instant createdAt; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + public NotificationContactType getChannel() { + return channel; + } + + public void setChannel(NotificationContactType channel) { + this.channel = channel; + } + + 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/BaseBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/BaseBuilder.java new file mode 100644 index 000000000..087df8b45 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/InAppNotificationBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/InAppNotificationBuilder.java new file mode 100644 index 000000000..8b89f2321 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/InAppNotificationBuilder.java @@ -0,0 +1,141 @@ +package gr.cite.notification.model.builder; + + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.model.InAppNotification; +import gr.cite.notification.model.Notification; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.model.User; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.query.UserQuery; +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.BaseFieldSet; +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.*; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class InAppNotificationBuilder extends BaseBuilder { + + private final QueryFactory queryFactory; + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public InAppNotificationBuilder( + ConventionService conventionService, + QueryFactory queryFactory, BuilderFactory builderFactory, JsonHandlingService jsonHandlingService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(InAppNotificationBuilder.class))); + this.queryFactory = queryFactory; + this.builderFactory = builderFactory; + } + + public InAppNotificationBuilder 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 tenantFields = fields.extractPrefixed(this.asPrefix(InAppNotification.Field.TENANT)); + Map tenantItemsMap = this.collectTenants(tenantFields, data); + + FieldSet userFields = fields.extractPrefixed(this.asPrefix(InAppNotification.Field.USER)); + Map userItemsMap = this.collectUsers(userFields, data); + + List models = new ArrayList<>(); + + for (InAppNotificationEntity d : data) { + InAppNotification m = new InAppNotification(); + if (fields.hasField(this.asIndexer(InAppNotification.Field.ID))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.TYPE))) m.setType(d.getType()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.IS_ACTIVE))) m.setIsActive(d.getIsActive()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.CREATED_AT))) m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.UPDATED_AT))) m.setUpdatedAt(d.getUpdatedAt()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.HASH))) m.setHash(this.hashValue(d.getUpdatedAt())); + if (fields.hasField(this.asIndexer(InAppNotification.Field.READ_TIME))) m.setReadTime(d.getReadTime()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.TRACKING_STATE))) m.setTrackingState(d.getTrackingState()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.BODY))) m.setBody(d.getBody()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.EXTRA_DATA))) m.setExtraData(d.getExtraData()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.PRIORITY))) m.setPriority(d.getPriority()); + if (fields.hasField(this.asIndexer(InAppNotification.Field.SUBJECT))) m.setSubject(d.getSubject()); + if (!tenantFields.isEmpty() && tenantItemsMap != null && tenantItemsMap.containsKey(d.getTenantId())) m.setTenant(tenantItemsMap.get(d.getTenantId())); + if (!userFields.isEmpty() && userItemsMap != null && userItemsMap.containsKey(d.getUserId())) m.setUser(userItemsMap.get(d.getUserId())); + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } + + private Map collectTenants(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", Tenant.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(Tenant._id))) { + itemMap = this.asEmpty( + data.stream().map(InAppNotificationEntity::getTenantId).distinct().collect(Collectors.toList()), + x -> { + Tenant item = new Tenant(); + item.setId(x); + return item; + }, + Tenant::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(Tenant._id); + TenantQuery q = this.queryFactory.query(TenantQuery.class).authorize(this.authorize).ids(data.stream().map(InAppNotificationEntity::getTenantId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(TenantBuilder.class).authorize(this.authorize).asForeignKey(q, clone, Tenant::getId); + } + if (!fields.hasField(Tenant._id)) { + itemMap.values().stream().filter(Objects::nonNull).peek(x -> x.setId(null)).collect(Collectors.toList()); + } + + return itemMap; + } + + private Map collectUsers(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", User.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(User._id))) { + itemMap = this.asEmpty( + data.stream().map(InAppNotificationEntity::getUserId).distinct().collect(Collectors.toList()), + x -> { + User item = new User(); + item.setId(x); + return item; + }, + User::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(User._id); + UserQuery q = this.queryFactory.query(UserQuery.class).authorize(this.authorize).ids(data.stream().map(InAppNotificationEntity::getUserId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(UserBuilder.class).authorize(this.authorize).asForeignKey(q, clone, User::getId); + } + if (!fields.hasField(User._id)) { + itemMap.values().stream().filter(Objects::nonNull).peek(x -> x.setId(null)).collect(Collectors.toList()); + } + + return itemMap; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/NotificationBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/NotificationBuilder.java new file mode 100644 index 000000000..26082a3ae --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/NotificationBuilder.java @@ -0,0 +1,137 @@ +package gr.cite.notification.model.builder; + + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.model.User; +import gr.cite.notification.model.Notification; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.query.UserQuery; +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.BaseFieldSet; +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.*; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class NotificationBuilder extends BaseBuilder { + + private final QueryFactory queryFactory; + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public NotificationBuilder( + ConventionService conventionService, + QueryFactory queryFactory, BuilderFactory builderFactory, JsonHandlingService jsonHandlingService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(NotificationBuilder.class))); + this.queryFactory = queryFactory; + this.builderFactory = builderFactory; + } + + public NotificationBuilder 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 tenantFields = fields.extractPrefixed(this.asPrefix(Notification._tenant)); + Map tenantItemsMap = this.collectTenants(tenantFields, data); + + FieldSet userFields = fields.extractPrefixed(this.asPrefix(Notification._user)); + Map userItemsMap = this.collectUsers(userFields, data); + + List models = new ArrayList<>(); + + for (NotificationEntity d : data) { + Notification m = new Notification(); + if (fields.hasField(this.asIndexer(Notification._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(Notification._contactHint))) m.setContactHint(d.getContactHint()); + if (fields.hasField(this.asIndexer(Notification._contactTypeHint))) m.setContactTypeHint(d.getContactTypeHint()); + if (fields.hasField(this.asIndexer(Notification._notifiedAt))) m.setNotifiedAt(d.getNotifiedAt()); + if (fields.hasField(this.asIndexer(Notification._type))) m.setType(d.getType()); + if (fields.hasField(this.asIndexer(Notification._isActive))) m.setIsActive(d.getIsActive()); + if (fields.hasField(this.asIndexer(Notification._createdAt))) m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(Notification._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); + if (fields.hasField(this.asIndexer(Notification._hash))) m.setHash(this.hashValue(d.getUpdatedAt())); + if (!tenantFields.isEmpty() && tenantItemsMap != null && tenantItemsMap.containsKey(d.getTenantId())) m.setTenant(tenantItemsMap.get(d.getTenantId())); + if (!userFields.isEmpty() && userItemsMap != null && userItemsMap.containsKey(d.getUserId())) m.setUser(userItemsMap.get(d.getUserId())); + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } + + private Map collectTenants(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", Tenant.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(Tenant._id))) { + itemMap = this.asEmpty( + data.stream().map(NotificationEntity::getTenantId).distinct().collect(Collectors.toList()), + x -> { + Tenant item = new Tenant(); + item.setId(x); + return item; + }, + Tenant::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(Tenant._id); + TenantQuery q = this.queryFactory.query(TenantQuery.class).authorize(this.authorize).ids(data.stream().map(NotificationEntity::getTenantId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(TenantBuilder.class).authorize(this.authorize).asForeignKey(q, clone, Tenant::getId); + } + if (!fields.hasField(Tenant._id)) { + itemMap.values().stream().filter(Objects::nonNull).peek(x -> x.setId(null)).collect(Collectors.toList()); + } + + return itemMap; + } + + private Map collectUsers(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", User.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(User._id))) { + itemMap = this.asEmpty( + data.stream().map(NotificationEntity::getUserId).distinct().collect(Collectors.toList()), + x -> { + User item = new User(); + item.setId(x); + return item; + }, + User::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(User._id); + UserQuery q = this.queryFactory.query(UserQuery.class).authorize(this.authorize).ids(data.stream().map(NotificationEntity::getUserId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(UserBuilder.class).authorize(this.authorize).asForeignKey(q, clone, User::getId); + } + if (!fields.hasField(User._id)) { + itemMap.values().stream().filter(Objects::nonNull).peek(x -> x.setId(null)).collect(Collectors.toList()); + } + + return itemMap; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/TenantBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/TenantBuilder.java new file mode 100644 index 000000000..19c20508d --- /dev/null +++ b/dmp-backend/notification-service/notification/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.common.XmlHandlingService; +import gr.cite.notification.common.types.tenant.TenantConfigEntity; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.model.builder.tenantconfig.TenantConfigBuilder; +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 final XmlHandlingService xmlHandlingService; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public TenantBuilder(ConventionService conventionService, BuilderFactory builderFactory, XmlHandlingService xmlHandlingService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(TenantBuilder.class))); + this.builderFactory = builderFactory; + this.xmlHandlingService = xmlHandlingService; + } + + 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<>(); + + FieldSet configFields = fields.extractPrefixed(this.asPrefix(Tenant._config)); + + 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._name))) m.setName(d.getName()); + if(fields.hasField(this.asIndexer(Tenant._description))) m.setDescription(d.getDescription()); + if(fields.hasField(this.asIndexer(Tenant._isActive))) m.setIsActive(d.getIsActive()); + if (!configFields.isEmpty() && d.getConfig() != null){ + TenantConfigEntity config = this.xmlHandlingService.fromXmlSafe(TenantConfigEntity.class, d.getConfig()); + m.setConfig(this.builderFactory.builder(TenantConfigBuilder.class).authorize(this.authorize).build(configFields, config)); + } 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java new file mode 100644 index 000000000..20281e814 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/TenantConfigurationBuilder.java @@ -0,0 +1,139 @@ +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.enums.TenantConfigurationType; +import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleConfigurationDataContainer; +import gr.cite.notification.common.types.tenantconfiguration.EmailClientConfigurationDataContainer; +import gr.cite.notification.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.model.Tenant; +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.Field.ID))) m.setId(d.getId()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.TENANT_ID))) m.setTenantId(d.getTenantId()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.TYPE))) m.setType(d.getType()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.VALUE))) m.setValue(d.getValue()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA))) { + try { + m.setDefaultUserLocaleData(mapper.readValue(d.getValue(), DefaultUserLocaleConfigurationDataContainer.class)); + + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if (!fields.extractPrefixed(this.asIndexer(TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA)).isEmpty()) { + try { + DefaultUserLocaleConfigurationDataContainer container = mapper.readValue(d.getValue(), DefaultUserLocaleConfigurationDataContainer.class); + if (container != null) { + m.setDefaultUserLocaleData(new DefaultUserLocaleConfigurationDataContainer()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA, DefaultUserLocaleConfigurationDataContainer.Field.LANGUAGE))) + m.getDefaultUserLocaleData().setLanguage(container.getLanguage()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA, DefaultUserLocaleConfigurationDataContainer.Field.TIME_ZONE))) + m.getDefaultUserLocaleData().setTimeZone(container.getTimeZone()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA, DefaultUserLocaleConfigurationDataContainer.Field.CULTURE))) + m.getDefaultUserLocaleData().setCulture(container.getCulture()); + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA))) { + try { + m.setEmailClientData(mapper.readValue(d.getValue(), EmailClientConfigurationDataContainer.class)); + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if (!fields.extractPrefixed(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA)).isEmpty()) { + try { + EmailClientConfigurationDataContainer container = mapper.readValue(d.getValue(), EmailClientConfigurationDataContainer.class); + if (container != null) { + m.setEmailClientData(new EmailClientConfigurationDataContainer()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.ENABLE_SSL))) + m.getEmailClientData().setEnableSSL(container.getEnableSSL()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.REQUIRE_CREDENTIALS))) + m.getEmailClientData().setRequireCredentials(container.getRequireCredentials()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.HOST_SERVER))) + m.getEmailClientData().setHostServer(container.getHostServer()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.HOST_PORT_NO))) + m.getEmailClientData().setHostPortNo(container.getHostPortNo()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.CERTIFICATE_PATH))) + m.getEmailClientData().setCertificatePath(container.getCertificatePath()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.EMAIL_ADDRESS))) + m.getEmailClientData().setEmailAddress(container.getEmailAddress()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.EMAIL_USER_NAME))) + m.getEmailClientData().setEmailUserName(container.getEmailUserName()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.EMAIL_CLIENT_DATA, EmailClientConfigurationDataContainer.Field.EMAIL_PASSWORD))) + m.getEmailClientData().setEmailPassword(container.getEmailPassword()); + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.NOTIFIER_LIST_DATA))) { + try { + m.setNotifierListData(mapper.readValue(d.getValue(), NotifierListConfigurationDataContainer.class)); + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if (!fields.extractPrefixed(this.asIndexer(TenantConfiguration.Field.NOTIFIER_LIST_DATA)).isEmpty()) { + try { + NotifierListConfigurationDataContainer container = mapper.readValue(d.getValue(), NotifierListConfigurationDataContainer.class); + if (container != null) { + m.setNotifierListData(new NotifierListConfigurationDataContainer()); + if (fields.hasField(this.asIndexer(TenantConfiguration.Field.NOTIFIER_LIST_DATA, NotifierListConfigurationDataContainer.Field.NOTIFIERS))) + m.getNotifierListData().setNotifiers(container.getNotifiers()); + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + } + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.IS_ACTIVE))) m.setIsActive(d.getIsActive()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.CREATED_AT))) m.setCreatedAt(d.getCreatedAt()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.UPDATED_AT))) m.setUpdatedAt(d.getUpdatedAt()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserBuilder.java new file mode 100644 index 000000000..403e0e694 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserBuilder.java @@ -0,0 +1,67 @@ +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.*; +import java.util.stream.Collectors; + +@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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserContactInfoBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserContactInfoBuilder.java new file mode 100644 index 000000000..4a5d948e2 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserContactInfoBuilder.java @@ -0,0 +1,128 @@ +package gr.cite.notification.model.builder; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.UserContactInfoEntity; +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; +import gr.cite.notification.model.Tenant; +import gr.cite.notification.model.User; +import gr.cite.notification.model.UserContactInfo; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.query.UserQuery; +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.BaseFieldSet; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserContactInfoBuilder extends BaseBuilder { + + private final BuilderFactory builderFactory; + private final QueryFactory queryFactory; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + public UserContactInfoBuilder(ConventionService conventionService, LoggerService logger, BuilderFactory builderFactory, QueryFactory queryFactory) { + super(conventionService, logger); + this.builderFactory = builderFactory; + this.queryFactory = queryFactory; + } + + public UserContactInfoBuilder 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 userFields = fields.extractPrefixed(this.asPrefix(UserContactInfo._user)); + Map userMap = this.collectUsers(userFields, data); + + FieldSet tenantFields = fields.extractPrefixed(this.asPrefix(UserContactInfo._tenant)); + Map tenantMap = this.collectTenants(tenantFields, data); + + List models = new ArrayList<>(); + + for (UserContactInfoEntity d : data) { + UserContactInfo m = new UserContactInfo(); + if (fields.hasField(this.asIndexer(UserContactInfo._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(UserContactInfo._createdAt))) m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(UserContactInfo._ordinal))) m.setOrdinal(d.getOrdinal()); + if (fields.hasField(this.asIndexer(UserContactInfo._value))) m.setValue(d.getValue()); + if (fields.hasField(this.asIndexer(UserContactInfo._type))) m.setType(d.getType()); + if (!userFields.isEmpty() && userMap != null && userMap.containsKey(d.getUserId())) m.setUser(userMap.get(d.getUserId())); + if (!tenantFields.isEmpty() && tenantMap != null && tenantMap.containsKey(d.getTenantId())) m.setTenant(tenantMap.get(d.getTenantId())); + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } + + private Map collectUsers(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", User.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(User._id))) { + itemMap = this.asEmpty( + data.stream().map(UserContactInfoEntity::getUserId).distinct().collect(Collectors.toList()), + x -> { + User item = new User(); + item.setId(x); + return item; + }, + User::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(User._id); + UserQuery q = this.queryFactory.query(UserQuery.class).authorize(this.authorize).ids(data.stream().map(UserContactInfoEntity::getUserId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(UserBuilder.class).authorize(this.authorize).asForeignKey(q, clone, User::getId); + } + if (!fields.hasField(User._id)) { + itemMap = itemMap.values().stream().filter(Objects::nonNull).peek(x -> x.setId(null)).collect(Collectors.toMap(User::getId, Function.identity())); + } + + return itemMap; + } + + private Map collectTenants(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", Tenant.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(Tenant._id))) { + itemMap = this.asEmpty( + data.stream().map(TenantScopedBaseEntity::getTenantId).distinct().collect(Collectors.toList()), + x -> { + Tenant item = new Tenant(); + item.setId(x); + return item; + }, + Tenant::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(Tenant._id); + TenantQuery q = this.queryFactory.query(TenantQuery.class).authorize(this.authorize).ids(data.stream().map(TenantScopedBaseEntity::getTenantId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(TenantBuilder.class).authorize(this.authorize).asForeignKey(q, clone, Tenant::getId); + } + if (!fields.hasField(Tenant._id)) { + itemMap = itemMap.values().stream().filter(Objects::nonNull).peek(x -> x.setId(null)).collect(Collectors.toMap(Tenant::getId, Function.identity())); + } + + return itemMap; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserNotificationPreferenceBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserNotificationPreferenceBuilder.java new file mode 100644 index 000000000..b62e089c9 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/UserNotificationPreferenceBuilder.java @@ -0,0 +1,61 @@ +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.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantConfigurationEntity; +import gr.cite.notification.data.UserNotificationPreferenceEntity; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.notification.model.UserNotificationPreference; +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 UserNotificationPreferenceBuilder extends BaseBuilder { + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + private final ObjectMapper mapper; + @Autowired + public UserNotificationPreferenceBuilder(ConventionService conventionService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(UserNotificationPreferenceBuilder.class))); + this.mapper = new ObjectMapper(); + } + + public UserNotificationPreferenceBuilder 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(UserNotificationPreferenceEntity d : data){ + UserNotificationPreference m = new UserNotificationPreference(); + if(fields.hasField(this.asIndexer(UserNotificationPreference.Field.USER_ID))) m.setUserId(d.getUserId()); + if(fields.hasField(this.asIndexer(UserNotificationPreference.Field.TENANT_ID))) m.setTenantId(d.getTenantId()); + if(fields.hasField(this.asIndexer(UserNotificationPreference.Field.TYPE))) m.setType(d.getType()); + if(fields.hasField(this.asIndexer(UserNotificationPreference.Field.CHANNEL))) m.setChannel(d.getChannel()); + if(fields.hasField(this.asIndexer(UserNotificationPreference.Field.ORDINAL))) m.setOrdinal(d.getOrdinal()); + if(fields.hasField(this.asIndexer(TenantConfiguration.Field.CREATED_AT))) m.setCreatedAt(d.getCreatedAt()); + models.add(m); + } + this.logger.debug("build {} items",Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantConfigBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantConfigBuilder.java new file mode 100644 index 000000000..57ad4bea8 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantDepositConfigBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantDepositConfigBuilder.java new file mode 100644 index 000000000..ec7c4b901 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantFileTransformersBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantFileTransformersBuilder.java new file mode 100644 index 000000000..5ea102750 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantSourceBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/builder/tenantconfig/TenantSourceBuilder.java new file mode 100644 index 000000000..0260ab410 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/BaseCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/BaseCensor.java new file mode 100644 index 000000000..12d1b9360 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.java new file mode 100644 index 000000000..bb1451d8e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.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 InAppNotificationCensor extends BaseCensor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationCensor.class)); + private final AuthorizationService authService; + + @Autowired + public InAppNotificationCensor(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.BrowseNotification); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/NotificationCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/NotificationCensor.java new file mode 100644 index 000000000..26be1e959 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/NotificationCensor.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 NotificationCensor extends BaseCensor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationCensor.class)); + private final AuthorizationService authService; + + @Autowired + public NotificationCensor(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.BrowseNotification); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/TenantCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/TenantCensor.java new file mode 100644 index 000000000..4440362e2 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/TenantConfigurationCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/TenantConfigurationCensor.java new file mode 100644 index 000000000..d3b099193 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserCensor.java new file mode 100644 index 000000000..ffe0677b3 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserContactInfoCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserContactInfoCensor.java new file mode 100644 index 000000000..046d69608 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserContactInfoCensor.java @@ -0,0 +1,52 @@ +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.UserContactInfo; +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 UserContactInfoCensor extends BaseCensor { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserContactInfoCensor.class)); + + protected final AuthorizationService authService; + protected final CensorFactory censorFactory; + + @Autowired + public UserContactInfoCensor( + 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.BrowseUserContactInfo); + FieldSet tenantFields = fields.extractPrefixed(this.asIndexerPrefix(UserContactInfo._tenant)); + this.censorFactory.censor(TenantCensor.class).censor(tenantFields); + FieldSet userFields = fields.extractPrefixed(this.asIndexerPrefix(UserContactInfo._user)); + this.censorFactory.censor(UserCensor.class).censor(userFields, userId); + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.java new file mode 100644 index 000000000..99f0a7090 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.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 UserNotificationPreferenceCensor extends BaseCensor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserNotificationPreferenceCensor.class)); + private final AuthorizationService authService; + + @Autowired + public UserNotificationPreferenceCensor(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.BrowseUserNotificationPreference); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/InAppNotificationDeleter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/InAppNotificationDeleter.java new file mode 100644 index 000000000..68ac201c6 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/InAppNotificationDeleter.java @@ -0,0 +1,78 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.query.InAppNotificationQuery; +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 InAppNotificationDeleter implements Deleter { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationDeleter.class)); + + private final TenantScopedEntityManager entityManager; + protected final QueryFactory queryFactory; + private final DeleterFactory deleterFactory; + + @Autowired + public InAppNotificationDeleter( + 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(InAppNotificationQuery.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(InAppNotificationEntity::getId).distinct().collect(Collectors.toList()); + + Instant now = Instant.now(); + + for (InAppNotificationEntity 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/NotificationDeleter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/NotificationDeleter.java new file mode 100644 index 000000000..b4587862e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/NotificationDeleter.java @@ -0,0 +1,78 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.query.NotificationQuery; +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 NotificationDeleter implements Deleter { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationDeleter.class)); + + private final TenantScopedEntityManager entityManager; + protected final QueryFactory queryFactory; + private final DeleterFactory deleterFactory; + + @Autowired + public NotificationDeleter( + 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(NotificationQuery.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(NotificationEntity::getId).distinct().collect(Collectors.toList()); + + Instant now = Instant.now(); + + for (NotificationEntity 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/TenantConfigurationDeleter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/TenantConfigurationDeleter.java new file mode 100644 index 000000000..ffbb084fc --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/TenantDeleter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/TenantDeleter.java new file mode 100644 index 000000000..db4900766 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/UserContactInfoDeleter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/UserContactInfoDeleter.java new file mode 100644 index 000000000..fce879690 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/UserContactInfoDeleter.java @@ -0,0 +1,74 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.data.UserContactInfoEntity; +import gr.cite.notification.model.persist.UserContactInfoPersist; +import gr.cite.notification.query.UserContactInfoQuery; +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 UserContactInfoDeleter implements Deleter { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserContactInfoDeleter.class)); + + private final TenantScopedEntityManager entityManager; + private final QueryFactory queryFactory; + private final DeleterFactory deleterFactory; + + @Autowired + public UserContactInfoDeleter( + 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(UserContactInfoQuery.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 (UserContactInfoEntity item : data) { + logger.trace("deleting item {}", item); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java new file mode 100644 index 000000000..5b8cec189 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/deleter/UserDeleter.java @@ -0,0 +1,73 @@ +package gr.cite.notification.model.deleter; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.data.*; +import gr.cite.notification.query.*; +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 UserDeleter implements Deleter { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserDeleter.class)); + + private final TenantScopedEntityManager entityManager; + private final QueryFactory queryFactory; + private final DeleterFactory deleterFactory; + + @Autowired + public UserDeleter( + 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(e -> e.size()).orElse(0)).And("ids", ids)); + List datas = this.queryFactory.query(UserQuery.class).ids(ids).collect(); + logger.trace("retrieved {} items", Optional.ofNullable(datas).map(e -> e.size()).orElse(0)); + this.deleteAndSave(datas); + } + + public void deleteAndSave(List datas) throws InvalidApplicationException { + logger.debug("will delete {} items", Optional.ofNullable(datas).map(e -> e.size()).orElse(0)); + this.delete(datas); + 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(x -> x.size()).orElse(0)); + if (datas == null || datas.isEmpty()) return; + + Instant now = Instant.now(); + + for (UserEntity 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationPersist.java new file mode 100644 index 000000000..f2bdc7088 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationPersist.java @@ -0,0 +1,138 @@ +package gr.cite.notification.model.persist; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.enums.NotificationNotifyState; +import gr.cite.notification.common.validation.ValidId; + +import javax.validation.constraints.NotNull; +import java.time.Instant; +import java.util.UUID; + +public class NotificationPersist { + + public static class Field { + public static final String _id = "id"; + public static final String _userId = "userId"; + public static final String _type = "type"; + public static final String _contactTypeHint = "contactTypeHint"; + public static final String _contactHint = "contactHint"; + public final static String _notifiedAt = "notifiedAt"; + public final static String _notifyState = "notifyState"; + public final static String _notifiedWith = "notifiedWith"; + public final static String _data = "data"; + public final static String _retryCount = "retryCount"; + } + + @ValidId(message = "{validation.invalidid}") + private UUID id; + + @NotNull(message = "{validation.empty}") + private UUID userId; + + private UUID type; + + private gr.cite.notification.common.enums.NotificationContactType contactTypeHint; + + private String contactHint; + + private Instant notifiedAt; + + private NotificationNotifyState notifyState; + + private NotificationContactType notifiedWith; + + private String data; + + private Integer retryCount; + + private String hash; + + 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 UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public gr.cite.notification.common.enums.NotificationContactType getContactTypeHint() { + return contactTypeHint; + } + + public void setContactTypeHint(gr.cite.notification.common.enums.NotificationContactType contactTypeHint) { + this.contactTypeHint = contactTypeHint; + } + + public String getContactHint() { + return contactHint; + } + + public void setContactHint(String contactHint) { + this.contactHint = contactHint; + } + + public Instant getNotifiedAt() { + return notifiedAt; + } + + public void setNotifiedAt(Instant notifiedAt) { + this.notifiedAt = notifiedAt; + } + + public NotificationNotifyState getNotifyState() { + return notifyState; + } + + public void setNotifyState(NotificationNotifyState notifyState) { + this.notifyState = notifyState; + } + + public gr.cite.notification.common.enums.NotificationContactType getNotifiedWith() { + return notifiedWith; + } + + public void setNotifiedWith(gr.cite.notification.common.enums.NotificationContactType notifiedWith) { + this.notifiedWith = notifiedWith; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserContactInfoPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserContactInfoPersist.java new file mode 100644 index 000000000..ce22bf2fa --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserContactInfoPersist.java @@ -0,0 +1,117 @@ +package gr.cite.notification.model.persist; + +import gr.cite.notification.common.enums.ContactInfoType; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.validation.FieldNotNullIfOtherSet; +import gr.cite.notification.common.validation.ValidId; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.time.Instant; +import java.util.UUID; + +@FieldNotNullIfOtherSet(message = "{validation.hashempty}") +public class UserContactInfoPersist { + + @ValidId(message = "{validation.invalidid}") + private ID id; + + @NotNull(message = "{validation.empty}") + @NotEmpty(message = "{validation.empty}") + private String value; + + @NotNull(message = "{validation.empty}") + @ValidId(message = "{validation.invalidid}") + private UUID tenantId; + + private Instant createdAt; + + private Instant updatedAt; + + private IsActive isActive; + + private String hash; + + public ID getId() { + return id; + } + + public void setId(ID id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public UUID getTenantId() { + return tenantId; + } + + 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; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public static class ID { + + @NotNull(message = "{validation.empty}") + @ValidId(message = "{validation.invalidid}") + private UUID userId; + + @NotNull(message = "{validation.empty}") + private ContactInfoType type; + + 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; + } + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserNotificationPreferencePersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserNotificationPreferencePersist.java new file mode 100644 index 000000000..7d7a3c550 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserNotificationPreferencePersist.java @@ -0,0 +1,29 @@ +package gr.cite.notification.model.persist; + +import gr.cite.notification.common.enums.NotificationContactType; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class UserNotificationPreferencePersist { + + private UUID userId; + private Map> notificationPreferences; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Map> getNotificationPreferences() { + return notificationPreferences; + } + + public void setNotificationPreferences(Map> notificationPreferences) { + this.notificationPreferences = notificationPreferences; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java new file mode 100644 index 000000000..27ddc86bb --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/UserTouchedIntegrationEventPersist.java @@ -0,0 +1,49 @@ +package gr.cite.notification.model.persist; + +import gr.cite.notification.common.validation.ValidId; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.UUID; + +public class UserTouchedIntegrationEventPersist { + @ValidId(message = "{validation.invalidid}") + @NotNull(message = "{validation.empty}") + private UUID id; + + @NotNull(message = "{validation.empty}") + @NotEmpty(message = "{validation.empty}") + @Size(max = 200, message = "{validation.largerthanmax}") + private String firstName; + + @NotNull(message = "{validation.empty}") + @NotEmpty(message = "{validation.empty}") + @Size(max = 200, message = "{validation.largerthanmax}") + private String lastName; + + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java new file mode 100644 index 000000000..4529dc988 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationEmailClientPersist.java @@ -0,0 +1,99 @@ +package gr.cite.notification.model.persist.tenantconfiguration; + +import gr.cite.notification.common.validation.ValidId; + +import java.util.UUID; + +public class TenantConfigurationEmailClientPersist { + @ValidId(message = "{validation.invalidid}") + private UUID id; + private Boolean requireCredentials; + private Boolean enableSSL; + private String certificatePath; + private String hostServer; + private Integer hostPortNo; + private String emailAddress; + private String emailUserName; + private String emailPassword; + private String 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationNotifierListPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationNotifierListPersist.java new file mode 100644 index 000000000..11247b19b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationNotifierListPersist.java @@ -0,0 +1,39 @@ +package gr.cite.notification.model.persist.tenantconfiguration; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.validation.ValidId; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class TenantConfigurationNotifierListPersist { + @ValidId(message = "{validation.invalidid}") + private UUID id; + private Map> notifiers; + private String hash; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Map> getNotifiers() { + return notifiers; + } + + public void setNotifiers(Map> notifiers) { + this.notifiers = notifiers; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java new file mode 100644 index 000000000..e9105c39e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/tenantconfiguration/TenantConfigurationUserLocaleIntegrationPersist.java @@ -0,0 +1,41 @@ +package gr.cite.notification.model.persist.tenantconfiguration; + +public class TenantConfigurationUserLocaleIntegrationPersist { + private String language; + private String timeZone; + private String 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; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java new file mode 100644 index 000000000..cbba2d2fc --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantConfig.java @@ -0,0 +1,28 @@ +package gr.cite.notification.model.tenantconfig; + + +public class TenantConfig { + + public final static String _deposit = "deposit"; + private TenantDepositConfig deposit; + + public final static 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantDepositConfig.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantDepositConfig.java new file mode 100644 index 000000000..8c468e2fc --- /dev/null +++ b/dmp-backend/notification-service/notification/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 final static String _sources = "sources"; + private List sources; + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantFileTransformersConfig.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantFileTransformersConfig.java new file mode 100644 index 000000000..ffada6eef --- /dev/null +++ b/dmp-backend/notification-service/notification/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 final static String _sources = "sources"; + private List sources; + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantSource.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/tenantconfig/TenantSource.java new file mode 100644 index 000000000..4cd422fe1 --- /dev/null +++ b/dmp-backend/notification-service/notification/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 final static String _url = "url"; + private String url; + + public final static String _codes = "codes"; + private List codes; + + public final static String _issuerUrl = "issuerUrl"; + private String issuerUrl; + + public final static String _clientId = "clientId"; + private String clientId; + + public final static String _clientSecret = "clientSecret"; + private String clientSecret; + + public final static 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/user/PublicUser.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/user/PublicUser.java new file mode 100644 index 000000000..157203a68 --- /dev/null +++ b/dmp-backend/notification-service/notification/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 final static String _id = "id"; + private UUID id; + + public final static 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/InAppNotificationQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/InAppNotificationQuery.java new file mode 100644 index 000000000..98472d2ed --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/InAppNotificationQuery.java @@ -0,0 +1,247 @@ +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.*; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.model.InAppNotification; +import gr.cite.notification.model.InAppNotification; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.Predicate; +import java.time.Instant; +import java.util.*; + + +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class InAppNotificationQuery extends QueryBase { + private Collection ids; + private List excludeIds; + private List userId; + private List type; + private Collection isActives; + private List tenantIds; + private List trackingState; + private Boolean isRead; + private Instant from; + private Instant to; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + public InAppNotificationQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public InAppNotificationQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public InAppNotificationQuery ids(Collection values) { + this.ids = values; + return this; + } + + public InAppNotificationQuery excludeIds(UUID... value) { + this.excludeIds = Arrays.asList(value); + return this; + } + + public InAppNotificationQuery excludeIds(List values) { + this.excludeIds = values; + return this; + } + + public InAppNotificationQuery userId(UUID... value) { + this.userId = Arrays.asList(value); + return this; + } + + public InAppNotificationQuery userId(List values) { + this.userId = values; + return this; + } + + public InAppNotificationQuery type(UUID... value) { + this.type = Arrays.asList(value); + return this; + } + + public InAppNotificationQuery type(List values) { + this.type = values; + return this; + } + + public InAppNotificationQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public InAppNotificationQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public InAppNotificationQuery Tenants(List value) { + this.tenantIds = value; + return this; + } + + public InAppNotificationQuery Tenants(UUID... value) { + this.tenantIds = Arrays.asList(value); + return this; + } + + public InAppNotificationQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public InAppNotificationQuery trackingState(List values) { + this.trackingState = values; + return this; + } + + public InAppNotificationQuery trackingState(NotificationInAppTracking... value) { + this.trackingState = List.of(value); + return this; + } + + public InAppNotificationQuery isRead(Boolean isRead) { + this.isRead = isRead; + return this; + } + + public InAppNotificationQuery isRead() { + this.isRead = true; + return this; + } + + public InAppNotificationQuery from(Instant from) { + this.from = from; + return this; + } + + public InAppNotificationQuery to(Instant to) { + this.to = to; + return this; + } + + public InAppNotificationQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return this.isNullOrEmpty(this.ids) + && this.isNullOrEmpty(this.isActives) + && this.isNullOrEmpty(this.type) + && this.isNullOrEmpty(this.excludeIds) + && this.isNullOrEmpty(this.userId) + && this.isNullOrEmpty(this.trackingState); + } + + @Override + protected Class entityClass() { + return InAppNotificationEntity.class; + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + predicates.add(queryContext.Root.get(InAppNotificationEntity.Field.ID).in(ids)); + } + + if (this.excludeIds != null) { + predicates.add(queryContext.CriteriaBuilder.not(queryContext.Root.get(InAppNotificationEntity.Field.ID).in(ids))); + } + + if (this.userId != null) { + predicates.add(queryContext.Root.get(InAppNotificationEntity.Field.USER_ID).in(userId)); + } + + if (this.isActives != null) { + predicates.add(queryContext.Root.get(InAppNotificationEntity.Field.IS_ACTIVE).in(isActives)); + } + + if (this.tenantIds != null) { + predicates.add(queryContext.Root.get(InAppNotificationEntity._tenantId).in(tenantIds)); + } + + if (this.type != null) { + predicates.add(queryContext.Root.get(InAppNotificationEntity.Field.TYPE).in(this.type)); + } + + if (this.trackingState != null) { + predicates.add(queryContext.Root.get(InAppNotificationEntity.Field.TRACKING_STATE).in(trackingState)); + } + + if (isRead != null) { + predicates.add(isRead ? queryContext.CriteriaBuilder.isNotNull(queryContext.Root.get(InAppNotificationEntity.Field.READ_TIME)) : queryContext.CriteriaBuilder.isNull(queryContext.Root.get(InAppNotificationEntity.Field.READ_TIME))); + } + + if (from != null) { + predicates.add(queryContext.CriteriaBuilder.greaterThanOrEqualTo(queryContext.Root.get(InAppNotificationEntity.Field.CREATED_AT), from)); + } + + if (to != null) { + predicates.add(queryContext.CriteriaBuilder.lessThanOrEqualTo(queryContext.Root.get(InAppNotificationEntity.Field.CREATED_AT), from)); + } + + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(InAppNotification.Field.ID)) return InAppNotificationEntity.Field.ID; + else if (item.match(InAppNotification.Field.CREATED_AT)) return InAppNotificationEntity.Field.CREATED_AT; + else if (item.match(InAppNotification.Field.USER)) return InAppNotificationEntity.Field.USER_ID; + else if (item.match(InAppNotification.Field.IS_ACTIVE)) return InAppNotificationEntity.Field.IS_ACTIVE; + else if (item.match(InAppNotification.Field.READ_TIME)) return InAppNotificationEntity.Field.READ_TIME; + else if (item.match(InAppNotification.Field.TRACKING_STATE)) return InAppNotificationEntity.Field.TRACKING_STATE; + else if (item.match(InAppNotification.Field.BODY)) return InAppNotificationEntity.Field.BODY; + else if (item.match(InAppNotification.Field.EXTRA_DATA)) return InAppNotificationEntity.Field.EXTRA_DATA; + else if (item.match(InAppNotification.Field.PRIORITY)) return InAppNotificationEntity.Field.PRIORITY; + else if (item.match(InAppNotification.Field.SUBJECT)) return InAppNotificationEntity.Field.SUBJECT; + else if (item.match(InAppNotification.Field.TENANT)) return InAppNotificationEntity._tenantId; + else if (item.match(InAppNotification.Field.TYPE)) return InAppNotificationEntity.Field.TYPE; + else if (item.match(InAppNotification.Field.UPDATED_AT)) return InAppNotificationEntity.Field.UPDATED_AT; + else return null; + } + + @Override + protected InAppNotificationEntity convert(Tuple tuple, Set columns) { + InAppNotificationEntity item = new InAppNotificationEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.ID, UUID.class)); + item.setType(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.TYPE, UUID.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.USER_ID, UUID.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity._tenantId, UUID.class)); + item.setTrackingState(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.TRACKING_STATE, NotificationInAppTracking.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.CREATED_AT, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.UPDATED_AT, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.IS_ACTIVE, IsActive.class)); + item.setExtraData(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.EXTRA_DATA, String.class)); + item.setBody(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.BODY, String.class)); + item.setSubject(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.SUBJECT, String.class)); + item.setPriority(QueryBase.convertSafe(tuple, columns, InAppNotificationEntity.Field.PRIORITY, InAppNotificationPriority.class)); + return item; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationQuery.java new file mode 100644 index 000000000..a7590183a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationQuery.java @@ -0,0 +1,288 @@ +package gr.cite.notification.query; + +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.common.enums.*; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.model.Notification; +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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.Predicate; +import java.time.Instant; +import java.util.*; + + +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class NotificationQuery extends QueryBase { + private Collection ids; + private Collection isActives; + private List tenantIds; + private List notifyState; + private List notifiedWith; + private Boolean notifiedWithHasValue; + private Boolean notifiedAtHasValue; + private List type; + private List contactType; + private Integer retryThreshold; + private Instant createdAfter; + private List trackingState; + + private List trackingProgress; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + public NotificationQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public NotificationQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public NotificationQuery ids(Collection values) { + this.ids = values; + return this; + } + + public NotificationQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public NotificationQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public NotificationQuery Tenants(List value) { + this.tenantIds = value; + return this; + } + + public NotificationQuery Tenants(UUID... value) { + this.tenantIds = Arrays.asList(value); + return this; + } + + public NotificationQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public NotificationQuery notifyState(NotificationNotifyState... notifyState) { + this.notifyState = List.of(notifyState); + return this; + } + + public NotificationQuery notifyState(List notifyState) { + this.notifyState = notifyState; + return this; + } + + public NotificationQuery notifiedWith(NotificationContactType... notifiedWith) { + this.notifiedWith = List.of(notifiedWith); + return this; + } + + public NotificationQuery notifiedWith(List notifiedWith) { + this.notifiedWith = notifiedWith; + return this; + } + + public NotificationQuery notifiedWithHasValue(Boolean notifiedWithHasValue) { + this.notifiedWithHasValue = notifiedWithHasValue; + return this; + } + + public NotificationQuery notifiedWithHasValue() { + this.notifiedWithHasValue = true; + return this; + } + + public NotificationQuery notifiedAtHasValue(Boolean notifiedAtHasValue) { + this.notifiedAtHasValue = notifiedAtHasValue; + return this; + } + + public NotificationQuery notifiedAtHasValue() { + this.notifiedAtHasValue = true; + return this; + } + + public NotificationQuery type(UUID... type) { + this.type = List.of(type); + return this; + } + + public NotificationQuery type(List type) { + this.type = type; + return this; + } + + public NotificationQuery contactType(NotificationContactType... contactType) { + this.contactType = List.of(contactType); + return this; + } + + public NotificationQuery contactType(List contactType) { + this.contactType = contactType; + return this; + } + + public NotificationQuery retryThreshold(Integer retryThreshold) { + this.retryThreshold = retryThreshold; + return this; + } + + public NotificationQuery createdAfter(Instant createdAfter) { + this.createdAfter = createdAfter; + return this; + } + + public NotificationQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + public NotificationQuery trackingState(NotificationTrackingState... trackingState) { + this.trackingState = List.of(trackingState); + return this; + } + + public NotificationQuery trackingState(List trackingState) { + this.trackingState = trackingState; + return this; + } + + public NotificationQuery trackingProgress(NotificationTrackingProcess... trackingProgress) { + this.trackingProgress = List.of(trackingProgress); + return this; + } + + public NotificationQuery trackingProgress(List trackingProgress) { + this.trackingProgress = trackingProgress; + return this; + } + + public NotificationQuery ordering(Ordering ordering) { + this.setOrder(ordering); + return this; + } + + @Override + protected Boolean isFalseQuery() { + return this.isNullOrEmpty(this.ids) + && this.isNullOrEmpty(this.isActives) + && this.isNullOrEmpty(this.notifyState); + } + + @Override + protected Class entityClass() { + return NotificationEntity.class; + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._id).in(ids)); + } + + if (this.isActives != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._isActive).in(isActives)); + } + + if (this.tenantIds != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._tenantId).in(tenantIds)); + } + + if (this.notifyState != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._notifyState).in(notifyState)); + } + + if (this.notifiedWith != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._notifiedWith).in(notifiedWith)); + } + + if (notifiedWithHasValue != null) { + Predicate hasValuePredicate = notifiedWithHasValue ? queryContext.CriteriaBuilder.isNotNull(queryContext.Root.get(NotificationEntity.Field._notifiedWith)) : queryContext.CriteriaBuilder.isNull(queryContext.Root.get(NotificationEntity.Field._notifiedWith)); + predicates.add(hasValuePredicate); + } + + if (notifiedAtHasValue != null) { + Predicate hasValuePredicate = notifiedAtHasValue ? queryContext.CriteriaBuilder.isNotNull(queryContext.Root.get(NotificationEntity.Field._notifiedAt)) : queryContext.CriteriaBuilder.isNull(queryContext.Root.get(NotificationEntity.Field._notifiedAt)); + predicates.add(hasValuePredicate); + } + + if (this.type != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._type).in(this.type)); + } + + if (this.contactType != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._contactTypeHint).in(this.contactType)); + } + + if (this.retryThreshold != null) { + predicates.add(queryContext.CriteriaBuilder.le(queryContext.Root.get(NotificationEntity.Field._retryCount), this.retryThreshold)); + } + + if (this.createdAfter != null) { + predicates.add(queryContext.CriteriaBuilder.greaterThan(queryContext.Root.get(NotificationEntity.Field._createdAt), this.createdAfter)); + } + + if (this.trackingState != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._trackingState).in(trackingState)); + } + + if (this.trackingProgress != null) { + predicates.add(queryContext.Root.get(NotificationEntity.Field._trackingProcess).in(trackingProgress)); + } + + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(Notification._id)) return NotificationEntity.Field._id; + else if (item.match(Notification._contactHint)) return NotificationEntity.Field._contactHint; + else if (item.match(Notification._createdAt)) return NotificationEntity.Field._createdAt; + else if (item.match(Notification._isActive)) return NotificationEntity.Field._isActive; + else if (item.match(Notification._contactTypeHint)) return NotificationEntity.Field._contactTypeHint; + else if (item.match(Notification._updatedAt)) return NotificationEntity.Field._updatedAt; + else if (item.match(Notification._notifiedAt)) return NotificationEntity.Field._notifiedAt; + else if (item.match(Notification._tenant)) return NotificationEntity.Field._tenantId; + else if (item.match(Notification._user)) return NotificationEntity.Field._userId; + else if (item.match(Notification._type)) return NotificationEntity.Field._type; + else return null; + } + + @Override + protected NotificationEntity convert(Tuple tuple, Set columns) { + NotificationEntity item = new NotificationEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._id, UUID.class)); + item.setContactHint(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._contactHint, String.class)); + item.setContactTypeHint(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._contactTypeHint, NotificationContactType.class)); + item.setNotifiedAt(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._notifiedAt, Instant.class)); + item.setType(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._type, UUID.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._userId, UUID.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._tenantId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, NotificationEntity.Field._isActive, IsActive.class)); + return item; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/QueueInboxQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/QueueInboxQuery.java new file mode 100644 index 000000000..4e9e6ccbe --- /dev/null +++ b/dmp-backend/notification-service/notification/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 org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java new file mode 100644 index 000000000..b8f6f78b9 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/QueueOutboxQuery.java @@ -0,0 +1,236 @@ +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 org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java new file mode 100644 index 000000000..e3758f168 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/TenantConfigurationQuery.java @@ -0,0 +1,119 @@ +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.data.TenantEntity; +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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +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.Field.ID).in(ids)); + } + + if (this.isActives != null) { + predicates.add(queryContext.Root.get(TenantConfigurationEntity.Field.IS_ACTIVE).in(isActives)); + } + + if (type != null) { + predicates.add(queryContext.Root.get(TenantConfigurationEntity.Field.TYPE).in(type)); + } + if (predicates.size() > 0) { + 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.Field.ID)) return TenantConfigurationEntity.Field.ID; + else if (item.match(TenantConfiguration.Field.TENANT_ID)) return TenantConfigurationEntity.Field.TENANT_ID; + else if (item.match(TenantConfiguration.Field.CREATED_AT)) return TenantConfigurationEntity.Field.CREATED_AT; + else if (item.match(TenantConfiguration.Field.IS_ACTIVE)) return TenantConfigurationEntity.Field.IS_ACTIVE; + else if (item.match(TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA)) return TenantConfiguration.Field.DEFAULT_USER_LOCALE_DATA; + else if (item.match(TenantConfiguration.Field.EMAIL_CLIENT_DATA)) return TenantConfiguration.Field.EMAIL_CLIENT_DATA; + else if (item.match(TenantConfiguration.Field.NOTIFIER_LIST_DATA)) return TenantConfiguration.Field.NOTIFIER_LIST_DATA; + else if (item.match(TenantConfiguration.Field.TYPE)) return TenantConfigurationEntity.Field.TYPE; + else if (item.match(TenantConfiguration.Field.UPDATED_AT)) return TenantConfigurationEntity.Field.UPDATED_AT; + else if (item.match(TenantConfiguration.Field.VALUE)) return TenantConfigurationEntity.Field.VALUE; + else return null; + } + + @Override + protected TenantConfigurationEntity convert(Tuple tuple, Set columns) { + TenantConfigurationEntity item = new TenantConfigurationEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.ID, UUID.class)); + item.setValue(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.VALUE, String.class)); + item.setType(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.TYPE, TenantConfigurationType.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.TENANT_ID, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.CREATED_AT, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.UPDATED_AT, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, TenantConfigurationEntity.Field.IS_ACTIVE, IsActive.class)); + return item; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/TenantQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/TenantQuery.java new file mode 100644 index 000000000..0b0c2d653 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/TenantQuery.java @@ -0,0 +1,127 @@ +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 org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.Predicate; +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.size() > 0) { + 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._name)) return TenantEntity._name; + else if (item.match(Tenant._description)) return TenantEntity._description; + else if (item.prefix(Tenant._config)) return TenantEntity._config; + 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.setName(QueryBase.convertSafe(tuple, columns, TenantEntity._name, String.class)); + item.setConfig(QueryBase.convertSafe(tuple, columns, TenantEntity._config, 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java new file mode 100644 index 000000000..a8f851b58 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java @@ -0,0 +1,221 @@ +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.ContactInfoType; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.UserContactInfoEntity; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.model.User; +import gr.cite.notification.model.UserContactInfo; +import gr.cite.notification.model.persist.UserContactInfoPersist; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Subquery; +import java.time.Instant; +import java.util.*; + +@Component +@RequestScope +public class UserContactInfoQuery extends QueryBase { + + private Collection ids; + private Collection userIds; + private Collection tenantIds; + private Collection isActives; + private Collection type; + private UserQuery userQuery; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + private final UserScope userScope; + private final AuthorizationService authService; + + public UserContactInfoQuery( + UserScope userScope, + AuthorizationService authService + ) { + this.userScope = userScope; + this.authService = authService; + } + + public UserContactInfoQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public UserContactInfoQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public UserContactInfoQuery ids(Collection values) { + this.ids = values; + return this; + } + + public UserContactInfoQuery userIds(UUID value) { + this.userIds = List.of(value); + return this; + } + + public UserContactInfoQuery userIds(UUID... value) { + this.userIds = Arrays.asList(value); + return this; + } + + public UserContactInfoQuery userIds(Collection values) { + this.userIds = values; + return this; + } + + public UserContactInfoQuery tenantIds(UUID value) { + this.tenantIds = List.of(value); + return this; + } + + public UserContactInfoQuery tenantIds(UUID... value) { + this.tenantIds = Arrays.asList(value); + return this; + } + + public UserContactInfoQuery tenantIds(Collection values) { + this.tenantIds = values; + return this; + } + + public UserContactInfoQuery isActive(IsActive value) { + this.isActives = List.of(value); + return this; + } + + public UserContactInfoQuery isActive(IsActive... value) { + this.isActives = Arrays.asList(value); + return this; + } + + public UserContactInfoQuery isActive(Collection values) { + this.isActives = values; + return this; + } + + public UserContactInfoQuery type(ContactInfoType value) { + this.type = List.of(value); + return this; + } + + public UserContactInfoQuery type(ContactInfoType... value) { + this.type = Arrays.asList(value); + return this; + } + + public UserContactInfoQuery type(Collection values) { + this.type = values; + return this; + } + + public UserContactInfoQuery userSubQuery(UserQuery subQuery) { + this.userQuery = subQuery; + return this; + } + + public UserContactInfoQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return this.isEmpty(this.userIds) || this.isEmpty(this.tenantIds) || this.isEmpty(this.isActives) || this.isEmpty(this.type) || this.isFalseQuery(this.userQuery); + } + + @Override + protected Class entityClass() { + return UserContactInfoEntity.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.BrowseUserContactInfo)) 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(UserContactInfoEntity._userId), 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.userIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._userId)); + for (UUID item : this.userIds) inClause.value(item); + predicates.add(inClause); + } + if (this.tenantIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._tenantId)); + for (UUID item : this.tenantIds) inClause.value(item); + predicates.add(inClause); + } + if (this.type != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._type)); + for (ContactInfoType item : this.type) inClause.value(item); + predicates.add(inClause); + } + if (this.userQuery != null) { + Subquery subQuery = queryContext.Query.subquery(this.userQuery.entityClass()); + this.applySubQuery(this.userQuery, queryContext.CriteriaBuilder, subQuery); + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._userId)).value(subQuery)); + } + + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(UserContactInfo._id)) return UserContactInfoEntity._id; + else if (item.prefix(UserContactInfo._tenant)) return UserContactInfoEntity._tenantId; + else if (item.match(UserContactInfo._type)) return UserContactInfoEntity._type; + else if (item.match(UserContactInfo._value)) return UserContactInfoEntity._value; + else if (item.match(UserContactInfo._createdAt)) return UserContactInfoEntity._createdAt; + else if (item.match(UserContactInfo._user, User._id)) return UserContactInfoEntity._userId; + else if (item.prefix(UserContactInfo._user)) return UserContactInfoEntity._userId; + else return null; + } + + @Override + protected UserContactInfoEntity convert(Tuple tuple, Set columns) { + UserContactInfoEntity item = new UserContactInfoEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, UserContactInfoEntity._id, UUID.class)); + item.setType(QueryBase.convertSafe(tuple, columns, UserContactInfoEntity._type, ContactInfoType.class)); + item.setValue(QueryBase.convertSafe(tuple, columns, UserContactInfoEntity._value, String.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, UserContactInfoEntity._userId, UUID.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, UserContactInfoEntity._tenantId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserContactInfoEntity._createdAt, Instant.class)); + return item; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserNotificationPreferenceQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserNotificationPreferenceQuery.java new file mode 100644 index 000000000..977d3baa4 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserNotificationPreferenceQuery.java @@ -0,0 +1,111 @@ +package gr.cite.notification.query; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.UserNotificationPreferenceEntity; +import gr.cite.notification.model.UserNotificationPreference; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.Predicate; +import java.time.Instant; +import java.util.*; + +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class UserNotificationPreferenceQuery extends QueryBase { + private List userId; + private List type; + private List channel; + + public UserNotificationPreferenceQuery userId(UUID... userId) { + this.userId = List.of(userId); + return this; + } + + public UserNotificationPreferenceQuery userId(List userId) { + this.userId = userId; + return this; + } + + public UserNotificationPreferenceQuery type(UUID... type) { + this.type = List.of(type); + return this; + } + + public UserNotificationPreferenceQuery type(List type) { + this.type = type; + return this; + } + + public UserNotificationPreferenceQuery channel(NotificationContactType... channel) { + this.channel = List.of(channel); + return this; + } + + public UserNotificationPreferenceQuery channel(List channel) { + this.channel = channel; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return this.isNullOrEmpty(this.userId) && this.isNullOrEmpty(this.type) && this.isNullOrEmpty(this.channel); + } + + @Override + protected Class entityClass() { + return UserNotificationPreferenceEntity.class; + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.userId != null) { + predicates.add(queryContext.Root.get(UserNotificationPreferenceEntity.Field.USER_ID).in(this.userId)); + } + + if (this.type != null) { + predicates.add(queryContext.Root.get(UserNotificationPreferenceEntity.Field.TYPE).in(this.type)); + } + + if (this.channel != null) { + predicates.add(queryContext.Root.get(UserNotificationPreferenceEntity.Field.CHANNEL).in(this.channel)); + } + + if (predicates.size() > 0) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(UserNotificationPreference.Field.USER_ID)) return UserNotificationPreferenceEntity.Field.USER_ID; + else if (item.match(UserNotificationPreference.Field.TENANT_ID)) return UserNotificationPreferenceEntity._tenantId; + else if (item.match(UserNotificationPreference.Field.TYPE)) return UserNotificationPreferenceEntity.Field.TYPE; + else if (item.match(UserNotificationPreference.Field.CHANNEL)) return UserNotificationPreferenceEntity.Field.CHANNEL; + else if (item.match(UserNotificationPreference.Field.ORDINAL)) return UserNotificationPreferenceEntity.Field.ORDINAL; + else if (item.match(UserNotificationPreference.Field.CREATED_AT)) return UserNotificationPreferenceEntity.Field.CREATED_AT; + else return null; + } + + @Override + protected UserNotificationPreferenceEntity convert(Tuple tuple, Set columns) { + UserNotificationPreferenceEntity item = new UserNotificationPreferenceEntity(); + item.setUserId(QueryBase.convertSafe(tuple, columns, UserNotificationPreferenceEntity.Field.USER_ID, UUID.class)); + item.setChannel(QueryBase.convertSafe(tuple, columns, UserNotificationPreferenceEntity.Field.CHANNEL, NotificationContactType.class)); + item.setType(QueryBase.convertSafe(tuple, columns, UserNotificationPreferenceEntity.Field.TYPE, UUID.class)); + item.setTenantId(QueryBase.convertSafe(tuple, columns, UserNotificationPreferenceEntity._tenantId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserNotificationPreferenceEntity.Field.CREATED_AT, Instant.class)); + item.setOrdinal(QueryBase.convertSafe(tuple, columns, UserNotificationPreferenceEntity.Field.ORDINAL, Integer.class)); + return item; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserQuery.java new file mode 100644 index 000000000..9fb299b91 --- /dev/null +++ b/dmp-backend/notification-service/notification/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.PublicUser; +import gr.cite.notification.model.User; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/InAppNotificationLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/InAppNotificationLookup.java new file mode 100644 index 000000000..899908759 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/InAppNotificationLookup.java @@ -0,0 +1,123 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationInAppTracking; +import gr.cite.notification.query.InAppNotificationQuery; +import gr.cite.notification.query.NotificationQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.time.Instant; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +public class InAppNotificationLookup extends Lookup { + private Collection ids; + private List excludeIds; + private List userId; + private List type; + private Collection isActive; + private List tenantIds; + private List trackingState; + private Boolean isRead; + private Instant from; + private Instant to; + + public Collection getIds() { + return ids; + } + + public void setIds(Collection ids) { + this.ids = ids; + } + + public List getExcludeIds() { + return excludeIds; + } + + public void setExcludeIds(List excludeIds) { + this.excludeIds = excludeIds; + } + + public List getUserId() { + return userId; + } + + public void setUserId(List userId) { + this.userId = userId; + } + + public List getType() { + return type; + } + + public void setType(List type) { + this.type = type; + } + + public Collection getIsActive() { + return isActive; + } + + public void setIsActive(Collection isActive) { + this.isActive = isActive; + } + + public List getTenantIds() { + return tenantIds; + } + + public void setTenantIds(List tenantIds) { + this.tenantIds = tenantIds; + } + + public List getTrackingState() { + return trackingState; + } + + public void setTrackingState(List trackingState) { + this.trackingState = trackingState; + } + + public Boolean getRead() { + return isRead; + } + + public void setRead(Boolean read) { + isRead = read; + } + + public Instant getFrom() { + return from; + } + + public void setFrom(Instant from) { + this.from = from; + } + + public Instant getTo() { + return to; + } + + public void setTo(Instant to) { + this.to = to; + } + + public InAppNotificationQuery enrich(QueryFactory queryFactory) { + InAppNotificationQuery query = queryFactory.query(InAppNotificationQuery.class); + if (this.isActive != null) query.isActive(this.isActive); + if (this.ids != null) query.ids(this.ids); + if (this.type != null) query.type(this.type); + if (this.isRead != null) query.isRead(this.isRead); + if (this.excludeIds != null) query.excludeIds(this.excludeIds); + if (this.to != null) query.to(this.to); + if (this.trackingState != null) query.trackingState(this.trackingState); + if (this.userId != null) query.userId(this.userId); + + this.enrichCommon(query); + + return query; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/NotificationLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/NotificationLookup.java new file mode 100644 index 000000000..28f40fcfb --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/NotificationLookup.java @@ -0,0 +1,127 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.enums.NotificationNotifyState; +import gr.cite.notification.query.NotificationQuery; +import gr.cite.notification.query.TenantQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class NotificationLookup extends Lookup { + private List isActive; + private List ids; + private List notifyState = new ArrayList<>(); + private List notifiedWith = new ArrayList<>(); + private Boolean notifiedWithHasValue; + private Boolean notifiedAtHasValue; + private List type = new ArrayList<>(); + private List contactType = new ArrayList<>(); + private Integer retryThreshold; + private Instant createdAfter; + + + 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 List getNotifyState() { + return notifyState; + } + + public void setNotifyState(List notifyState) { + this.notifyState = notifyState; + } + + public List getNotifiedWith() { + return notifiedWith; + } + + public void setNotifiedWith(List notifiedWith) { + this.notifiedWith = notifiedWith; + } + + public Boolean getNotifiedWithHasValue() { + return notifiedWithHasValue; + } + + public void setNotifiedWithHasValue(Boolean notifiedWithHasValue) { + this.notifiedWithHasValue = notifiedWithHasValue; + } + + public Boolean getNotifiedAtHasValue() { + return notifiedAtHasValue; + } + + public void setNotifiedAtHasValue(Boolean notifiedAtHasValue) { + this.notifiedAtHasValue = notifiedAtHasValue; + } + + public List getType() { + return type; + } + + public void setType(List type) { + this.type = type; + } + + public List getContactType() { + return contactType; + } + + public void setContactType(List contactType) { + this.contactType = contactType; + } + + public Integer getRetryThreshold() { + return retryThreshold; + } + + public void setRetryThreshold(Integer retryThreshold) { + this.retryThreshold = retryThreshold; + } + + public Instant getCreatedAfter() { + return createdAfter; + } + + public void setCreatedAfter(Instant createdAfter) { + this.createdAfter = createdAfter; + } + + public NotificationQuery enrich(QueryFactory queryFactory) { + NotificationQuery query = queryFactory.query(NotificationQuery.class); + if (this.isActive != null) query.isActive(this.isActive); + if (this.ids != null) query.ids(this.ids); + if (this.notifiedAtHasValue != null) query.notifiedAtHasValue(this.notifiedAtHasValue); + if (this.notifyState != null) query.notifyState(this.notifyState); + if (this.contactType != null) query.contactType(this.contactType); + if (this.createdAfter != null) query.createdAfter(this.createdAfter); + if (this.notifiedWithHasValue != null) query.notifiedWithHasValue(this.notifiedWithHasValue); + if (this.notifiedWith != null) query.notifiedWith(this.notifiedWith); + if (this.retryThreshold != null) query.retryThreshold(this.retryThreshold); + if (this.type != null) query.type(this.type); + + this.enrichCommon(query); + + return query; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/NotifierListLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/NotifierListLookup.java new file mode 100644 index 000000000..b3960b828 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/NotifierListLookup.java @@ -0,0 +1,16 @@ +package gr.cite.notification.query.lookup; + +import java.util.Set; +import java.util.UUID; + +public class NotifierListLookup { + private Set notificationTypes; + + public Set getNotificationTypes() { + return notificationTypes; + } + + public void setNotificationTypes(Set notificationTypes) { + this.notificationTypes = notificationTypes; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java new file mode 100644 index 000000000..b31ea5f62 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/TenantConfigurationLookup.java @@ -0,0 +1,53 @@ +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.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 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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/TenantLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/TenantLookup.java new file mode 100644 index 000000000..21886546e --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserContactInfoLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserContactInfoLookup.java new file mode 100644 index 000000000..308cbcd46 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserContactInfoLookup.java @@ -0,0 +1,55 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.query.UserContactInfoQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.util.List; +import java.util.UUID; + +public class UserContactInfoLookup extends Lookup { + + private List isActive; + private List userIds; + private List tenantIds; + + private UserLookup userSubQuery; + + public List getIsActive() { + return isActive; + } + + public void setIsActive(List isActive) { + this.isActive = isActive; + } + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + public List getTenantIds() { + return tenantIds; + } + + public void setTenantIds(List tenantIds) { + this.tenantIds = tenantIds; + } + + public UserContactInfoQuery enrich(QueryFactory queryFactory) { + UserContactInfoQuery query = queryFactory.query(UserContactInfoQuery.class); + if (this.isActive != null) query.isActive(this.isActive); + if (this.userIds != null) query.userIds(this.userIds); + if (this.tenantIds != null) query.tenantIds(this.tenantIds); + if (this.userSubQuery != null) query.userSubQuery(this.userSubQuery.enrich(queryFactory)); + + this.enrichCommon(query); + + return query; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserLookup.java new file mode 100644 index 000000000..f96b12909 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserNotificationPreferenceLookup.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserNotificationPreferenceLookup.java new file mode 100644 index 000000000..c98ae5161 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/lookup/UserNotificationPreferenceLookup.java @@ -0,0 +1,54 @@ +package gr.cite.notification.query.lookup; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.enums.TenantConfigurationType; +import gr.cite.notification.query.TenantConfigurationQuery; +import gr.cite.notification.query.UserNotificationPreferenceQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.util.List; +import java.util.UUID; + +public class UserNotificationPreferenceLookup extends Lookup { + private List userId; + private List type; + private List channel; + + public List getUserId() { + return userId; + } + + public void setUserId(List userId) { + this.userId = userId; + } + + public List getType() { + return type; + } + + public void setType(List type) { + this.type = type; + } + + public List getChannel() { + return channel; + } + + public void setChannel(List channel) { + this.channel = channel; + } + + public UserNotificationPreferenceQuery enrich(QueryFactory queryFactory) { + UserNotificationPreferenceQuery query = queryFactory.query(UserNotificationPreferenceQuery.class); + if (this.userId != null) query.userId(this.userId); + if (this.channel != null) query.channel(this.channel); + if (this.type != null) query.type(this.type); + + this.enrichCommon(query); + + return query; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/NotificationScheduleTask.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/NotificationScheduleTask.java new file mode 100644 index 000000000..80b50e14f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/NotificationScheduleTask.java @@ -0,0 +1,158 @@ +package gr.cite.notification.schedule; + +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.query.NotificationQuery; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.schedule.model.CandidateInfo; +import gr.cite.notification.schedule.model.MiniTenant; +import gr.cite.notification.service.notification.NotificationService; +import gr.cite.notification.service.notificationscheduling.NotificationSchedulingService; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class NotificationScheduleTask { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationScheduleTask.class)); + + private final ApplicationContext applicationContext; + public NotificationScheduleTask(ApplicationContext applicationContext, NotificationProperties properties) { + this.applicationContext = applicationContext; + long intervalSeconds = properties.getTask().getProcessor().getIntervalSeconds(); + if (properties.getTask().getProcessor().getEnable() && intervalSeconds > 0) { + logger.info("Notification task will be scheduled to run every {} seconds", intervalSeconds); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + //GK: Fixed rate is heavily unpredictable and it will not scale well on a very heavy workload + scheduler.scheduleWithFixedDelay(this::process, 10, intervalSeconds, TimeUnit.SECONDS); + } + } + + public void process() { + try { + Instant lastCandidateNotificationCreationTimestamp = null; + while (true) { + CandidateInfo candidateInfo = this.candidateToNotify(lastCandidateNotificationCreationTimestamp); + if (candidateInfo == null) break; + + lastCandidateNotificationCreationTimestamp = candidateInfo.getNotificationCreatedAt(); + Boolean shouldOmit = this.shouldOmitNotify(candidateInfo); + if (shouldOmit) { + continue; + } + Boolean shouldAwait = this.shouldWait(candidateInfo); + if (shouldAwait) { + continue; + } + this.notify(candidateInfo.getNotificationId()); + } + + lastCandidateNotificationCreationTimestamp = null; + while(true) { + CandidateInfo candidateInfo = this.candidateToTrack(lastCandidateNotificationCreationTimestamp); + if (candidateInfo == null) break; + + lastCandidateNotificationCreationTimestamp = candidateInfo.getNotificationCreatedAt(); + Boolean shouldOmit = this.shouldOmitTracking(candidateInfo); + if (shouldOmit) { + continue; + } + this.track(candidateInfo.getNotificationId()); + } + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + } + + private CandidateInfo candidateToNotify(Instant lastCandidateNotificationCreationTimestamp) { + CandidateInfo candidateInfo = null; + try { + NotificationSchedulingService schedulingService = this.applicationContext.getBean(NotificationSchedulingService.class); + candidateInfo = schedulingService.candidateToNotify(lastCandidateNotificationCreationTimestamp); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + return candidateInfo; + } + + private Boolean shouldWait(CandidateInfo candidateInfo) { + Boolean shouldWait = false; + try { + NotificationSchedulingService schedulingService = applicationContext.getBean(NotificationSchedulingService.class); + shouldWait = schedulingService.shouldWait(candidateInfo); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + return shouldWait; + } + + private Boolean shouldOmitNotify(CandidateInfo candidateInfo) { + Boolean shouldOmit = false; + try { + NotificationSchedulingService schedulingService = applicationContext.getBean(NotificationSchedulingService.class); + shouldOmit = schedulingService.shouldOmitNotify(candidateInfo.getNotificationId()); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + return shouldOmit; + } + + private Boolean notify(UUID notificationId) { + Boolean success = null; + try { + NotificationSchedulingService schedulingService = applicationContext.getBean(NotificationSchedulingService.class); + success = schedulingService.notify(notificationId); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + + return success; + } + + private CandidateInfo candidateToTrack(Instant lastCandidateNotificationCreationTimestamp) { + CandidateInfo candidateInfo = null; + try { + NotificationSchedulingService schedulingService = this.applicationContext.getBean(NotificationSchedulingService.class); + candidateInfo = schedulingService.candidateToTrack(lastCandidateNotificationCreationTimestamp); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + return candidateInfo; + } + + private Boolean shouldOmitTracking(CandidateInfo candidateInfo) { + Boolean shouldOmit = false; + try { + NotificationSchedulingService schedulingService = applicationContext.getBean(NotificationSchedulingService.class); + shouldOmit = schedulingService.shouldOmitTracking(candidateInfo.getNotificationId()); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + return shouldOmit; + } + + private Boolean track(UUID notificationId) { + Boolean success = null; + try { + NotificationSchedulingService schedulingService = applicationContext.getBean(NotificationSchedulingService.class); + success = schedulingService.track(notificationId); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + + return success; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/model/CandidateInfo.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/model/CandidateInfo.java new file mode 100644 index 000000000..f8ffe4e1b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/model/CandidateInfo.java @@ -0,0 +1,45 @@ +package gr.cite.notification.schedule.model; + +import gr.cite.notification.common.enums.NotificationNotifyState; + +import java.time.Instant; +import java.util.UUID; + +public class CandidateInfo { + private UUID notificationId; + private NotificationNotifyState previousState; + private Instant notificationCreatedAt; + + public CandidateInfo() { + } + + public CandidateInfo(UUID notificationId, NotificationNotifyState previousState, Instant notificationCreatedAt) { + this.notificationId = notificationId; + this.previousState = previousState; + this.notificationCreatedAt = notificationCreatedAt; + } + + public UUID getNotificationId() { + return notificationId; + } + + public void setNotificationId(UUID notificationId) { + this.notificationId = notificationId; + } + + public NotificationNotifyState getPreviousState() { + return previousState; + } + + public void setPreviousState(NotificationNotifyState previousState) { + this.previousState = previousState; + } + + public Instant getNotificationCreatedAt() { + return notificationCreatedAt; + } + + public void setNotificationCreatedAt(Instant notificationCreatedAt) { + this.notificationCreatedAt = notificationCreatedAt; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/model/MiniTenant.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/model/MiniTenant.java new file mode 100644 index 000000000..09ed0592f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/schedule/model/MiniTenant.java @@ -0,0 +1,32 @@ +package gr.cite.notification.schedule.model; + +import java.util.UUID; + +public class MiniTenant { + private UUID id; + private String tenantCode; + + public MiniTenant() { + } + + public MiniTenant(UUID id, String tenantCode) { + this.id = id; + this.tenantCode = tenantCode; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/AuthnSandbox.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/AuthnSandbox.java new file mode 100644 index 000000000..3e5a408b3 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/channelResolution/ChannelResolutionService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/channelResolution/ChannelResolutionService.java new file mode 100644 index 000000000..18ec2085b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/channelResolution/ChannelResolutionService.java @@ -0,0 +1,14 @@ +package gr.cite.notification.service.channelResolution; + +import gr.cite.notification.common.enums.NotificationContactType; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface ChannelResolutionService { + + List resolve(UUID type); + List resolve(UUID type, UUID userId); + Map> resolve(UUID type, List userIds); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/channelResolution/ChannelResolutionServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/channelResolution/ChannelResolutionServiceImpl.java new file mode 100644 index 000000000..1495a556a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/channelResolution/ChannelResolutionServiceImpl.java @@ -0,0 +1,114 @@ +package gr.cite.notification.service.channelResolution; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.config.notification.NotificationConfig; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.model.UserNotificationPreference; +import gr.cite.notification.service.tenantconfiguration.TenantConfigurationService; +import gr.cite.notification.service.userNotificationPreference.UserNotificationPreferenceService; +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.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.*; +import java.util.stream.Collectors; + +@Component +@RequestScope +public class ChannelResolutionServiceImpl implements ChannelResolutionService{ + private static final LoggerService loggerService = new LoggerService(LoggerFactory.getLogger(ChannelResolutionServiceImpl.class)); + + private final ErrorThesaurusProperties errors; + private final Map> globalPoliciesMap; + private final TenantConfigurationService tenantConfigurationService; + private final UserNotificationPreferenceService userNotificationPreferenceService; + + @Autowired + public ChannelResolutionServiceImpl(ErrorThesaurusProperties errors, + @Qualifier(NotificationConfig.BeanQualifier.GLOBAL_POLICIES_MAP) + Map> globalPoliciesMap, + TenantConfigurationService tenantConfigurationService, UserNotificationPreferenceService userNotificationPreferenceService) { + this.errors = errors; + this.globalPoliciesMap = globalPoliciesMap; + this.tenantConfigurationService = tenantConfigurationService; + this.userNotificationPreferenceService = userNotificationPreferenceService; + } + + private List lookupGlobalPolicy(UUID type) { + if (!this.globalPoliciesMap.containsKey(type)) throw new MyApplicationException(this.errors.getSystemError().getCode(), this.errors.getSystemError().getMessage()); + return this.globalPoliciesMap.get(type); + } + + @Override + public List resolve(UUID type) { + return this.lookupGlobalPolicy(type); + } + + @Override + public List resolve(UUID type, UUID userId) { + if (userId == null) return this.resolve(type); + + Map> resolved = this.resolve(type, List.of(userId)); + if (resolved == null || !resolved.containsKey(userId)) return new ArrayList<>(); + return resolved.get(userId); + } + + @Override + public Map> resolve(UUID type, List userIds) { + List users = userIds.stream().distinct().collect(Collectors.toList()); + + List globals = this.lookupGlobalPolicy(type); + List tenantPolicies = this.lookupOrCollectTenantPolicies(type); + Map> userPolicies = this.lookupOrCollectUserPolicies(users, type); + Map> curatedUserPolicies = new HashMap<>(); + for (UUID userId: users) + { + List curatedTypes = null; + if (userPolicies == null || !userPolicies.containsKey(userId)) + { + if (tenantPolicies != null) + { + curatedTypes = tenantPolicies; + } + else + { + curatedTypes = globals; + } + } + else + { + curatedTypes = new ArrayList<>(); + for (NotificationContactType userPolicy : userPolicies.get(userId)) + { + if (globals == null || !globals.contains(userPolicy)) continue; + if (tenantPolicies != null && !tenantPolicies.contains(userPolicy)) continue; + curatedTypes.add(userPolicy); + } + } + curatedUserPolicies.put(userId, curatedTypes); + } + + return curatedUserPolicies; + } + + private List lookupOrCollectTenantPolicies(UUID type) { + NotifierListConfigurationDataContainer container = this.tenantConfigurationService.collectTenantNotifierList(); + if (container == null || container.getNotifiers() == null) return null; + return container.getNotifiers().get(type); + } + + private Map> lookupOrCollectUserPolicies(List users, UUID type) { + Map> contactsByUser = new HashMap<>(); + Map> userNotificationPreferences = this.userNotificationPreferenceService.collectUserNotificationPreferences(users); + for (Map.Entry> notificationPreference: userNotificationPreferences.entrySet()) + { + contactsByUser.put(notificationPreference.getKey(), notificationPreference.getValue().stream().filter(x -> x.getType() != null && x.getType() == type && x.getChannel() != null).sorted(Comparator.comparingInt(x -> x.getOrdinal())).map(x -> x.getChannel()).collect(Collectors.toList())); + } + return contactsByUser; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/ContactExtractor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/ContactExtractor.java new file mode 100644 index 000000000..4fed2e792 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/ContactExtractor.java @@ -0,0 +1,12 @@ +package gr.cite.notification.service.contact.extractor; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.service.contact.model.Contact; + +public interface ContactExtractor { + + Contact extract(NotificationEntity notification); + + NotificationContactType supports(); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/ContactExtractorFactory.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/ContactExtractorFactory.java new file mode 100644 index 000000000..01780a6cb --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/ContactExtractorFactory.java @@ -0,0 +1,25 @@ +package gr.cite.notification.service.contact.extractor; + +import gr.cite.notification.common.enums.NotificationContactType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class ContactExtractorFactory { + + private final Map contactExtractorMap; + + + @Autowired + public ContactExtractorFactory(List contactExtractorList) { + this.contactExtractorMap = contactExtractorList.stream().collect(Collectors.toMap(ContactExtractor::supports, contactExtractor -> contactExtractor)); + } + + public ContactExtractor fromContactType(NotificationContactType type) { + return this.contactExtractorMap.getOrDefault(type, null); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/EmailContactExtractor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/EmailContactExtractor.java new file mode 100644 index 000000000..f678b8fcf --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/EmailContactExtractor.java @@ -0,0 +1,117 @@ +package gr.cite.notification.service.contact.extractor; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.StringUtils; +import gr.cite.notification.common.enums.ContactInfoType; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.notification.ContactPair; +import gr.cite.notification.common.types.notification.EmailOverrideMode; +import gr.cite.notification.common.types.notification.NotificationContactData; +import gr.cite.notification.config.notification.NotificationConfig; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.data.UserContactInfoEntity; +import gr.cite.notification.model.UserContactInfo; +import gr.cite.notification.query.UserContactInfoQuery; +import gr.cite.notification.service.contact.model.Contact; +import gr.cite.notification.service.contact.model.EmailContact; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +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.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class EmailContactExtractor implements ContactExtractor { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(EmailContactExtractor.class)); + + private final JsonHandlingService jsonHandlingService; + private final QueryFactory queryFactory; + private final Map> flowMap; + + @Autowired + public EmailContactExtractor(JsonHandlingService jsonHandlingService, + @Qualifier(NotificationConfig.BeanQualifier.FLOW_MAP) + Map> flowMap, + QueryFactory queryFactory + ) { + this.jsonHandlingService = jsonHandlingService; + this.queryFactory = queryFactory; + this.flowMap = flowMap; + } + + @Override + public Contact extract(NotificationEntity notification) { + EmailContact emailContact = new EmailContact(); + NotificationContactData data = this.jsonHandlingService.fromJsonSafe(NotificationContactData.class, notification.getContactHint()); + + List contactPairs = data != null && data.getContacts() != null ? data.getContacts().stream().filter(contactPair -> contactPair.getType().equals(ContactInfoType.Email) && !StringUtils.isNullOrEmpty(contactPair.getContact())).collect(Collectors.toList()) : null; + + if (contactPairs != null && !contactPairs.isEmpty()) { + emailContact.setEmails(contactPairs.stream().map(ContactPair::getContact).collect(Collectors.toList())); + } + else if (notification.getUserId() != null) { + logger.trace("extracting from user"); + UserContactInfoEntity userContactInfo = this.queryFactory.query(UserContactInfoQuery.class) + .userIds(notification.getUserId()) + .isActive(IsActive.Active) + .type(ContactInfoType.Email).firstAs(new BaseFieldSet().ensure(UserContactInfo._value)); + + if (userContactInfo != null && userContactInfo.getValue() != null && !userContactInfo.getValue().isBlank()) emailContact.setEmails(List.of(userContactInfo.getValue())); + } + else { + logger.warn("Could not retrieve contact information for notification {}", notification.getId()); + } + NotificationProperties.Flow options = this.locateFlowOptions(notification.getType()); + + List defaultCCs = options.getCc() != null ? options.getCc().stream().filter(x -> x != null && !x.isBlank()).collect(Collectors.toList()) : new ArrayList<>(); + List defaultBCCs = options.getBcc() != null ? options.getBcc().stream().filter(x -> x != null && !x.isBlank()).collect(Collectors.toList()) : new ArrayList<>(); + + List ccContacts = data != null && data.getCc() != null ? data.getCc().stream().filter(contactPair -> contactPair.getType().equals(ContactInfoType.Email) && !StringUtils.isNullOrEmpty(contactPair.getContact())).map(ContactPair::getContact).collect(Collectors.toList()) : new ArrayList<>(); + List bccContacts = data != null && data.getBcc() != null ? data.getBcc().stream().filter(contactPair -> contactPair.getType().equals(ContactInfoType.Email) && !StringUtils.isNullOrEmpty(contactPair.getContact())).map(ContactPair::getContact).collect(Collectors.toList()) : new ArrayList<>(); + + emailContact.setCcEmails(this.mergeContacts(options.getCcMode(), defaultCCs, ccContacts).stream().distinct().collect(Collectors.toList())); + emailContact.setBccEmails(this.mergeContacts(options.getBccMode(), defaultBCCs, bccContacts).stream().distinct().collect(Collectors.toList())); + + return emailContact; + } + + private List mergeContacts(EmailOverrideMode overrideMode, List defaultContacts, List messageContacts) { + switch (overrideMode) { + case NotOverride: + return defaultContacts; + case Replace: + return messageContacts != null && !messageContacts.isEmpty() ? messageContacts : defaultContacts; + case Additive: { + List contacts = new ArrayList<>(); + if (messageContacts != null) contacts.addAll(messageContacts); + if (defaultContacts != null) contacts.addAll(defaultContacts); + return contacts; + } + default: + throw new MyApplicationException("Invalid type " + overrideMode.toString()); + } + } + + private NotificationProperties.Flow locateFlowOptions(UUID type) { + return this.flowMap.get("email").getOrDefault(type, null); + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.EMAIL; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/InAppContactExtractor.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/InAppContactExtractor.java new file mode 100644 index 000000000..a68520def --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/extractor/InAppContactExtractor.java @@ -0,0 +1,29 @@ +package gr.cite.notification.service.contact.extractor; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.service.contact.model.Contact; +import gr.cite.notification.service.contact.model.InAppContact; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class InAppContactExtractor implements ContactExtractor{ + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppContactExtractor.class)); + @Override + public Contact extract(NotificationEntity notification) { + if (notification.getUserId() == null) + { + logger.error("Could not retrieve contact information for notification " + notification.getId()); + return null; + } + logger.trace("extracting from notification"); + return new InAppContact(notification.getUserId()); + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.IN_APP; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/Contact.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/Contact.java new file mode 100644 index 000000000..4117ffea0 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/Contact.java @@ -0,0 +1,4 @@ +package gr.cite.notification.service.contact.model; + +public interface Contact { +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/EmailContact.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/EmailContact.java new file mode 100644 index 000000000..55d5d4a61 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/EmailContact.java @@ -0,0 +1,42 @@ +package gr.cite.notification.service.contact.model; + +import java.util.List; + +public class EmailContact implements Contact{ + private List emails; + private List ccEmails; + private List bccEmails; + + public EmailContact() { + } + + public EmailContact(List emails, List ccEmails, List bccEmails) { + this.emails = emails; + this.ccEmails = ccEmails; + this.bccEmails = bccEmails; + } + + public List getEmails() { + return emails; + } + + public void setEmails(List emails) { + this.emails = emails; + } + + public List getCcEmails() { + return ccEmails; + } + + public void setCcEmails(List ccEmails) { + this.ccEmails = ccEmails; + } + + public List getBccEmails() { + return bccEmails; + } + + public void setBccEmails(List bccEmails) { + this.bccEmails = bccEmails; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/InAppContact.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/InAppContact.java new file mode 100644 index 000000000..ccb43834e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/contact/model/InAppContact.java @@ -0,0 +1,22 @@ +package gr.cite.notification.service.contact.model; + +import java.util.UUID; + +public class InAppContact implements Contact{ + private UUID userId; + + public InAppContact() { + } + + public InAppContact(UUID userId) { + this.userId = userId; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/formatting/FormattingService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/formatting/FormattingService.java new file mode 100644 index 000000000..eb4ddb426 --- /dev/null +++ b/dmp-backend/notification-service/notification/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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java new file mode 100644 index 000000000..4f7cfebe6 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/formatting/FormattingServiceImpl.java @@ -0,0 +1,175 @@ +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.data.UserEntity; +import gr.cite.notification.locale.LocaleService; +import gr.cite.notification.model.User; +import gr.cite.notification.query.UserQuery; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; +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/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationService.java new file mode 100644 index 000000000..fb0129b8a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationService.java @@ -0,0 +1,13 @@ +package gr.cite.notification.service.inappnotification; + +import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.UUID; + +public interface InAppNotificationService { + + void markAsRead(List ids); + void markAsRead(UUID id); + void deleteAndSave(UUID id) throws InvalidApplicationException; + void markAsReadAllUserNotification(UUID userId); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java new file mode 100644 index 000000000..9a0d4c17f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java @@ -0,0 +1,102 @@ +package gr.cite.notification.service.inappnotification; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.enums.NotificationInAppTracking; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.model.deleter.InAppNotificationDeleter; +import gr.cite.notification.query.InAppNotificationQuery; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyForbiddenException; +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.Service; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +@Service +@RequestScope +public class InAppNotificationServiceImpl implements InAppNotificationService{ + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationServiceImpl.class)); + + private final QueryFactory queryFactory; + private final TenantScopedEntityManager entityManager; + private final AuthorizationService authService; + private final DeleterFactory deleterFactory; + + @Autowired + public InAppNotificationServiceImpl(QueryFactory queryFactory, TenantScopedEntityManager entityManager, AuthorizationService authService, DeleterFactory deleterFactory) { + this.queryFactory = queryFactory; + this.entityManager = entityManager; + this.authService = authService; + this.deleterFactory = deleterFactory; + } + + public void markAsRead(UUID id) + { + this.markAsRead(List.of(id)); + } + + public void markAsRead(List ids) + { + try { + logger.debug(new DataLogEntry("marking as read in-app notifications", ids)); + + List items = this.queryFactory.query(InAppNotificationQuery.class) + .ids(ids) + .collect(); + + Instant now = Instant.now(); + for (InAppNotificationEntity item : items) { + item.setTrackingState(NotificationInAppTracking.DELIVERED); + item.setUpdatedAt(now); + item.setReadTime(now); + this.entityManager.merge(item); + this.entityManager.persist(item); + } + } catch (InvalidApplicationException e) { + logger.error(e.getMessage(), e); + } + } + + public void markAsReadAllUserNotification(UUID userId) + { + try { + logger.debug(new DataLogEntry("marking as read all user in-app notifications", userId)); + + if (userId == null || userId.equals(new UUID(0L, 0L))) return; + + List inAppNotificationEntities = this.queryFactory.query(InAppNotificationQuery.class) + .userId(userId) + .trackingState(NotificationInAppTracking.STORED) + .collect(); + Instant now = Instant.now(); + int bulkSize = this.entityManager.getBulkSize(); + this.entityManager.setBulkSize(inAppNotificationEntities.size()); + for (InAppNotificationEntity entity : inAppNotificationEntities) { + entity.setReadTime(now); + entity.setUpdatedAt(now); + entity.setTrackingState(NotificationInAppTracking.DELIVERED); + this.entityManager.merge(entity); + this.entityManager.persist(entity); + } + this.entityManager.setBulkSize(bulkSize); + } catch (InvalidApplicationException e) { + logger.error(e.getMessage(), e); + } + } + + + public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug("deleting tenant: {}", id); + this.authService.authorizeForce(Permission.DeleteNotification); + this.deleterFactory.deleter(InAppNotificationDeleter.class).deleteAndSaveByIds(List.of(id)); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/EmailMessageBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/EmailMessageBuilder.java new file mode 100644 index 000000000..fd84f8a52 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/EmailMessageBuilder.java @@ -0,0 +1,92 @@ +package gr.cite.notification.service.message.builder; + +import gr.cite.notification.cache.NotificationTemplateCache; +import gr.cite.notification.common.StringUtils; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.notification.FieldInfo; +import gr.cite.notification.config.notification.NotificationConfig; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.service.formatting.FormattingService; +import gr.cite.notification.service.message.common.MessageBuilderBase; +import gr.cite.notification.service.message.infobuilder.MessageInfoBuilderService; +import gr.cite.notification.service.message.model.EmailMessage; +import gr.cite.notification.service.message.model.Message; +import gr.cite.notification.service.message.model.MessageInfo; +import gr.cite.tools.cipher.CipherService; +import gr.cite.tools.cipher.config.CipherProfileProperties; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.Map; +import java.util.UUID; + +@Component +@RequestScope +public class EmailMessageBuilder extends MessageBuilderBase implements MessageBuilder{ + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(EmailMessageBuilder.class)); + private final NotificationProperties properties; + private final Map> flowMap; + private final MessageInfoBuilderService messageInfoBuilderService; + private final Map cipherFields; + + @Autowired + public EmailMessageBuilder(NotificationProperties properties, + @Qualifier(NotificationConfig.BeanQualifier.FLOW_MAP) + Map> flowMap, + MessageInfoBuilderService messageInfoBuilderService, + ErrorThesaurusProperties errors, + @Qualifier(NotificationConfig.BeanQualifier.CIPHER_FIELDS) + Map cipherFields, + CipherService cipherService, + CipherProfileProperties cipherProfileProperties, + FormattingService formattingService, + NotificationTemplateCache cache) { + super(logger, errors, cipherService, cipherProfileProperties, formattingService, cache); + this.properties = properties; + this.flowMap = flowMap; + this.messageInfoBuilderService = messageInfoBuilderService; + this.cipherFields = cipherFields; + } + + @Override + public Message buildMessage(NotificationEntity notification) { + MessageInfo messageInfo = this.messageInfoBuilderService.buildMessageInfo(notification); + NotificationProperties.Flow flow = this.flowMap.get("email").getOrDefault(notification.getType(), null); + if (flow == null) return null; + String subjectTemplate = null; + if(!StringUtils.isNullOrEmpty(flow.getSubjectKey())) subjectTemplate = messageInfo.getFields().stream().filter(fieldInfo -> fieldInfo.getKey().equals(flow.getSubjectKey())).findFirst().orElse(new FieldInfo()).getValue(); + if (StringUtils.isNullOrEmpty(subjectTemplate)) subjectTemplate = this.lookupOrReadLocalizedFile(this.getTemplate().getTemplateCache(), flow.getSubjectPath(), messageInfo.getLanguage()); + FieldFormatting subjectFormatting = this.buildFieldFormatting(flow.getSubjectFieldOptions()); + ReplaceResult subjectResult = this.buildTemplate(notification.getId(), subjectTemplate, messageInfo, flow.getSubjectFieldOptions(), subjectFormatting, cipherFields.get(notification.getType())); + + String bodyTemplate = null; + if(!StringUtils.isNullOrEmpty(flow.getBodyKey())) bodyTemplate = messageInfo.getFields().stream().filter(fieldInfo -> fieldInfo.getKey().equals(flow.getBodyKey())).findFirst().orElse(new FieldInfo()).getValue(); + if (StringUtils.isNullOrEmpty(bodyTemplate)) bodyTemplate = this.lookupOrReadLocalizedFile(this.getTemplate().getTemplateCache(), flow.getBodyPath(), messageInfo.getLanguage()); + FieldFormatting bodyFormatting = this.buildFieldFormatting(flow.getBodyFieldOptions()); + ReplaceResult bodyResult = this.buildTemplate(notification.getId(), bodyTemplate, messageInfo, flow.getBodyFieldOptions(), bodyFormatting, cipherFields.get(notification.getType())); + + if (bodyResult != null && subjectResult != null) { + EmailMessage emailMessage = new EmailMessage(); + emailMessage.setBody(bodyResult.getText()); + emailMessage.setSubject(subjectResult.getText()); + + return emailMessage; + } + return null; + } + + private NotificationProperties.Template getTemplate() { + return this.properties.getMessage().get("email"); + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.EMAIL; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/InAppMessageBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/InAppMessageBuilder.java new file mode 100644 index 000000000..986a49b5f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/InAppMessageBuilder.java @@ -0,0 +1,108 @@ +package gr.cite.notification.service.message.builder; + +import gr.cite.notification.cache.NotificationTemplateCache; +import gr.cite.notification.common.StringUtils; +import gr.cite.notification.common.enums.InAppNotificationPriority; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.notification.FieldInfo; +import gr.cite.notification.config.notification.NotificationConfig; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.service.formatting.FormattingService; +import gr.cite.notification.service.message.common.MessageBuilderBase; +import gr.cite.notification.service.message.infobuilder.MessageInfoBuilderService; +import gr.cite.notification.service.message.model.InAppMessage; +import gr.cite.notification.service.message.model.Message; +import gr.cite.notification.service.message.model.MessageInfo; +import gr.cite.tools.cipher.CipherService; +import gr.cite.tools.cipher.config.CipherProfileProperties; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@RequestScope +public class InAppMessageBuilder extends MessageBuilderBase implements MessageBuilder{ + private final static LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppMessageBuilder.class)); + private final MessageInfoBuilderService messageInfoBuilderService; + private final Map> flowMap; + private final NotificationProperties properties; + + @Autowired + public InAppMessageBuilder(MessageInfoBuilderService messageInfoBuilderService, + @Qualifier(NotificationConfig.BeanQualifier.FLOW_MAP) + Map> flowMap, + ErrorThesaurusProperties errors, NotificationProperties properties, + CipherService cipherService, + CipherProfileProperties cipherProfileProperties, + FormattingService formattingService, + NotificationTemplateCache cache) { + super(logger, errors, cipherService, cipherProfileProperties, formattingService, cache); + this.messageInfoBuilderService = messageInfoBuilderService; + this.flowMap = flowMap; + this.properties = properties; + } + + @Override + public Message buildMessage(NotificationEntity notification) { + MessageInfo messageInfo = this.messageInfoBuilderService.buildMessageInfo(notification); + if (messageInfo == null) + { + logger.error("Could not retrieve message info for notification " + notification.getId()); + return null; + } + + NotificationProperties.Flow options = this.flowMap.get("in-app").getOrDefault(notification.getType(), null); + + if (options == null) + { + logger.error("Could not retrieve flow options for notification " + notification.getId() + " of type " + notification.getType()); + return null; + } + + MessageBuilderBase.FieldCiphering ciphering = options.getCipherFields() == null || options.getCipherFields().isEmpty() ? new MessageBuilderBase.FieldCiphering() : new MessageBuilderBase.FieldCiphering(options.getCipherFields()); + + String subjectTemplate = null; + if (!StringUtils.isNullOrEmpty(options.getSubjectKey())) subjectTemplate = messageInfo.getFields().stream().filter(x -> x.getKey().equals(options.getSubjectKey())).findFirst().orElse(new FieldInfo()).getValue(); + if (StringUtils.isNullOrEmpty(subjectTemplate)) subjectTemplate = this.lookupOrReadLocalizedFile(this.getTemplate().getTemplateCache(), options.getSubjectPath(), messageInfo.getLanguage()); + FieldFormatting subjectFormatting = this.buildFieldFormatting(options.getSubjectFieldOptions()); + ReplaceResult subjectResult = this.buildTemplate(notification.getId(), subjectTemplate, messageInfo, options.getSubjectFieldOptions(), subjectFormatting, ciphering); + + String bodyTemplate = null; + if (!StringUtils.isNullOrEmpty(options.getBodyKey())) bodyTemplate = messageInfo.getFields().stream().filter(x -> x.getKey().equals(options.getBodyKey())).findFirst().orElse(new FieldInfo()).getValue(); + if (StringUtils.isNullOrEmpty(bodyTemplate)) bodyTemplate = this.lookupOrReadLocalizedFile(this.getTemplate().getTemplateCache(), options.getBodyPath(), messageInfo.getLanguage()); + FieldFormatting bodyFormatting = this.buildFieldFormatting(options.getBodyFieldOptions()); + ReplaceResult bodyResult = this.buildTemplate(notification.getId(), bodyTemplate, messageInfo, options.getBodyFieldOptions(), bodyFormatting, ciphering); + + String priorityString = ""; + + if (!StringUtils.isNullOrEmpty(options.getPriorityKey())) priorityString = messageInfo.getFields().stream().filter(x -> x.getKey().equals(options.getPriorityKey())).findFirst().orElse(new FieldInfo()).getValue(); + InAppNotificationPriority inAppNotificationPriority = InAppNotificationPriority.NORMAL; + if (!StringUtils.isNullOrEmpty(priorityString)) { + inAppNotificationPriority = InAppNotificationPriority.valueOf(priorityString.toLowerCase()); + } + + List extraData = null; + if (options.getExtraDataKeys() != null && !options.getExtraDataKeys().isEmpty()) extraData = messageInfo.getFields().stream().filter(x -> options.getExtraDataKeys().contains(x.getKey())).collect(Collectors.toList()); + + return new InAppMessage(subjectResult.getText(), bodyResult.getText(), notification.getType(), inAppNotificationPriority, extraData); + } + + private NotificationProperties.Template getTemplate() { + return this.properties.getMessage().get("in-app"); + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.IN_APP; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/MessageBuilder.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/MessageBuilder.java new file mode 100644 index 000000000..bfad76832 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/MessageBuilder.java @@ -0,0 +1,11 @@ +package gr.cite.notification.service.message.builder; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.service.message.model.Message; + +public interface MessageBuilder { + Message buildMessage(NotificationEntity notification); + + NotificationContactType supports(); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/MessageBuilderFactory.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/MessageBuilderFactory.java new file mode 100644 index 000000000..1958bebee --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/builder/MessageBuilderFactory.java @@ -0,0 +1,26 @@ +package gr.cite.notification.service.message.builder; + +import gr.cite.notification.common.enums.NotificationContactType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +@RequestScope +public class MessageBuilderFactory { + + private final Map messageBuilders; + + @Autowired + public MessageBuilderFactory(List messageBuilders) { + this.messageBuilders = messageBuilders.stream().collect(Collectors.toMap(MessageBuilder::supports, messageBuilder -> messageBuilder)); + } + + public MessageBuilder getFromType(NotificationContactType contactType) { + return messageBuilders.getOrDefault(contactType, null); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/common/MessageBuilderBase.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/common/MessageBuilderBase.java new file mode 100644 index 000000000..4557e8cfc --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/common/MessageBuilderBase.java @@ -0,0 +1,275 @@ +package gr.cite.notification.service.message.common; + +import gr.cite.notification.cache.NotificationTemplateCache; +import gr.cite.notification.common.StringUtils; +import gr.cite.notification.common.types.notification.FieldInfo; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.service.formatting.FormattingService; +import gr.cite.notification.service.message.model.MessageInfo; +import gr.cite.tools.cipher.CipherService; +import gr.cite.tools.cipher.config.CipherProfileProperties; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.logging.LoggerService; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.time.Instant; +import java.util.*; + +public abstract class MessageBuilderBase { + private final LoggerService logger; + private final ErrorThesaurusProperties errors; + private final CipherService cipherService; + private final CipherProfileProperties cipherProfileProperties; + + private final FormattingService formattingService; + private final NotificationTemplateCache cache; + + public MessageBuilderBase(LoggerService logger, ErrorThesaurusProperties errors, CipherService cipherService, CipherProfileProperties cipherProfileProperties, FormattingService formattingService, NotificationTemplateCache cache) { + this.logger = logger; + this.errors = errors; + this.cipherService = cipherService; + this.cipherProfileProperties = cipherProfileProperties; + this.formattingService = formattingService; + this.cache = cache; + } + + //GK: Unlike in C# in Java Map is just an interface + protected static class FieldFormatting extends HashMap { + public Boolean isSet(String key) { + return this.containsKey(key) && (this.get(key) != null && !this.get(key).isEmpty()); + } + } + + public static class FieldCiphering extends HashSet { + public FieldCiphering(List cipherFields) { + super(cipherFields); + } + + public FieldCiphering() { + } + + public Boolean isSet(String key) { + return this.contains(key); + } + } + + protected static class ReplaceResult { + private String text; + private Set replacedKeys; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Set getReplacedKeys() { + return replacedKeys; + } + + public void setReplacedKeys(Set replacedKeys) { + this.replacedKeys = replacedKeys; + } + } + + protected ReplaceResult applyFieldReplace(String text, MessageInfo messageInfo, FieldFormatting fieldFormatting, FieldCiphering fieldCiphering) { + ReplaceResult result = new ReplaceResult(); + result.setReplacedKeys(new HashSet<>()); + + if (messageInfo.getUserId() != null && (fieldCiphering != null && !fieldCiphering.isSet("{user-id}"))) { + String txtValue = fieldFormatting.isSet("{user-id}") ? String.format(fieldFormatting.get("{user-id}"), messageInfo.getUserId().toString()) : messageInfo.getUserId().toString(); + text = text.replace("{user-id}", txtValue); + result.getReplacedKeys().add("{user-id}"); + } + //GOTCHA: fieldCiphering not supported for language-code. will not replace + if (!StringUtils.isNullOrEmpty(messageInfo.getLanguage()) && (fieldCiphering != null && !fieldCiphering.isSet("{language-code}"))) { + String txtValue = fieldFormatting.isSet("{language-code}") ? String.format(fieldFormatting.get("{language-code}"), messageInfo.getLanguage()) : messageInfo.getLanguage(); + text = text.replace("{language-code}", txtValue); + result.getReplacedKeys().add("{language-code}"); + } + //GOTCHA: fieldCiphering not supported for language-code. will not replace + if (!StringUtils.isNullOrEmpty(messageInfo.getCulture()) && (fieldCiphering != null && !fieldCiphering.isSet("{culture-code}"))) { + String txtValue = fieldFormatting.isSet("{culture-code}") ? String.format(fieldFormatting.get("{culture-code}"), messageInfo.getCulture()) : messageInfo.getCulture(); + text = text.replace("{culture-code}", txtValue); + result.getReplacedKeys().add("{culture-code}"); + } + //GOTCHA: fieldCiphering not supported for timezone-code. will not replace + if (!StringUtils.isNullOrEmpty(messageInfo.getTimeZone()) && (fieldCiphering != null && !fieldCiphering.isSet("{timezone-code}"))) { + String txtValue = fieldFormatting.isSet("{timezone-code}") ? String.format(fieldFormatting.get("{timezone-code}"), messageInfo.getTimeZone()) : messageInfo.getTimeZone(); + text = text.replace("{timezone-code}", txtValue); + result.getReplacedKeys().add("{timezone-code}"); + } + //GOTCHA: fieldCiphering not supported for tenant-id. will not replace + if (messageInfo.getTenantId() !=null && !messageInfo.getTenantId().equals(new UUID(0L, 0L)) && (fieldCiphering != null && !fieldCiphering.isSet("{tenant-id}"))) { + String txtValue = fieldFormatting.isSet("{tenant-id}") ? String.format(fieldFormatting.get("{tenant-id}"), messageInfo.getTenantId().toString()) : messageInfo.getTenantId().toString(); + text = text.replace("{tenant-id}", txtValue); + result.getReplacedKeys().add("{tenant-id}"); + } + //GOTCHA: fieldCiphering not supported for tenant-code. will not replace + if (!StringUtils.isNullOrEmpty(messageInfo.getTenantCode()) && (fieldCiphering != null && !fieldCiphering.isSet("{tenant-code}"))) { + String txtValue = fieldFormatting.isSet("{tenant-code}") ? String.format(fieldFormatting.get("{tenant-code}"), messageInfo.getTenantCode()) : messageInfo.getTenantCode(); + text = text.replace("{tenant-code}", txtValue); + result.getReplacedKeys().add("{tenant-code}"); + } + + for (FieldInfo field : messageInfo.getFields()) { + FieldInfo theField = field; + if ((fieldCiphering != null && fieldCiphering.isSet(field.getKey())) && !StringUtils.isNullOrEmpty(field.getValue())) { + theField = new FieldInfo(); + theField.setKey(field.getKey()); + theField.setType(field.getType()); + theField.setValue(this.decipherValue(field)); + } + + String format = fieldFormatting.isSet(theField.getKey()) ? fieldFormatting.get(theField.getKey()) : null; + String value = this.format(messageInfo.getUserId(), theField, format); + if (StringUtils.isNullOrEmpty(value)) continue; + text = text.replace(theField.getKey(), value); + result.getReplacedKeys().add(theField.getKey()); + } + + result.setText(text); + + return result; + } + + protected FieldFormatting buildFieldFormatting(NotificationProperties.Flow.FieldOption options) { + FieldFormatting formatting = new FieldFormatting(); + if (options == null || options.getFormatting() == null) return formatting; + + for (Map.Entry pair : options.getFormatting().entrySet()) { + if (StringUtils.isNullOrEmpty(pair.getValue())) continue; + formatting.put(pair.getKey(), pair.getValue()); + } + return formatting; + } + + //TODO: Here check with a language accepted list and fallback to default + protected String lookupOrReadLocalizedFile(NotificationProperties.Template.TemplateCache templateCache, String path, String language) { + String filename = path.replace("{language}", language); + File file = null; + NotificationTemplateCache.NotificationTemplateCacheValue cacheKey = + new NotificationTemplateCache.NotificationTemplateCacheValue(templateCache.getPrefix(), + filename, + templateCache.getKeyPattern()); + String content = this.cache.lookup(cacheKey); + + if (!StringUtils.isNullOrEmpty(content)) return content; + try { + file = ResourceUtils.getFile(filename); + + } catch (FileNotFoundException e) { + throw new MyApplicationException(this.errors.getSystemError().getCode(), this.errors.getSystemError().getMessage()); + } + try { + content = new String(new FileInputStream(file).readAllBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + this.cache.put(cacheKey, content); + + return content; + } + + protected String decipherValue(FieldInfo field) { + try { + return this.cipherService.decryptSymetricAes(field.getValue(), this.cipherProfileProperties.getProfileMap().get("notification-profile-name")); + } catch (Exception ex) { + this.logger.error("could not decipher value " + field.getValue() + " of key " + field.getKey() + ". no value for key", ex); + return null; + } + } + + protected ReplaceResult buildTemplate(UUID notificationId, String template, MessageInfo messageInfo, NotificationProperties.Flow.FieldOption options, FieldFormatting formatting, FieldCiphering ciphering) { + ReplaceResult result = this.applyFieldReplace(template, messageInfo, formatting, ciphering); + if (options != null) { + Boolean allMandatory = this.ensureMandatoryFields(result, options.getMandatory() != null ? options.getMandatory() : new ArrayList<>()); + if (!allMandatory) { + logger.error("Could not replace all subject mandatory field for notification {}", notificationId); + return null; + } + if (options.getOptional() != null) { + for (FieldInfo field : options.getOptional()) { + result.setText(this.applyFieldReplace(result.getText(), field, "", formatting)); + } + } + } + return result; + } + + protected Boolean ensureMandatoryFields(ReplaceResult result, List mandatory) { + for (String field : mandatory) { + if (!result.getReplacedKeys().contains(field)) { + logger.warn("Mandatory field {} was not replaced in message", field); + return false; + } + } + return true; + } + + protected String applyFieldReplace(String text, FieldInfo field, String defaultValue, FieldFormatting formatting) { + String txtVal = defaultValue; + if (!StringUtils.isNullOrEmpty(field.getValue())) { + if (formatting.isSet(field.getKey())) txtVal = String.format(formatting.get(field.getKey()), field.getValue()); + else txtVal = field.getValue(); + } + + text = text.replace(field.getKey(), txtVal); + + return text; + } + + protected String format(UUID userId, FieldInfo field, String format) { + switch (field.getType()) { + case DateTime: + return this.dateTimeFormat(userId, field.getValue(), format); + case Decimal: + return this.decimalFormat(userId, field.getValue(), format); + case Double: + return this.doubleFormat(userId, field.getValue(), format); + case Integer: + return this.integerFormat(userId, field.getValue(), format); + case String: + default: + return this.stringFormat(field.getValue(), format); + } + } + + private String stringFormat(String value, String format) { + String val; + if (!StringUtils.isNullOrEmpty(format)) val = String.format(format, value); + else val = value; + + return val; + } + + private String dateTimeFormat(UUID userId, String value, String format) { + if (StringUtils.isNullOrEmpty(value)) return ""; + Instant time = Instant.parse(value); + return formattingService.format(time, userId, null, format, null); + } + + private String decimalFormat(UUID userId, String value, String format) { + if (StringUtils.isNullOrEmpty(value)) return ""; + Double aDouble = Double.parseDouble(value); + return formattingService.format(aDouble, userId, null, format, null); + } + + private String doubleFormat(UUID userId, String value, String format) { + return this.decimalFormat(userId, value, format); + } + + private String integerFormat(UUID userId, String value, String format) { + if (StringUtils.isNullOrEmpty(value)) return ""; + Integer aInteger = Integer.parseInt(value); + return formattingService.format(aInteger, userId, format, null); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/infobuilder/MessageBuilderServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/infobuilder/MessageBuilderServiceImpl.java new file mode 100644 index 000000000..ffa421517 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/infobuilder/MessageBuilderServiceImpl.java @@ -0,0 +1,126 @@ +package gr.cite.notification.service.message.infobuilder; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.StringUtils; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.common.types.notification.DataType; +import gr.cite.notification.common.types.notification.FieldInfo; +import gr.cite.notification.common.types.notification.NotificationFieldData; +import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleConfigurationDataContainer; +import gr.cite.notification.config.notification.NotificationConfig; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.locale.LocaleService; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.query.UserQuery; +import gr.cite.notification.service.contact.extractor.EmailContactExtractor; +import gr.cite.notification.service.message.model.MessageInfo; +import gr.cite.notification.service.tenantconfiguration.TenantConfigurationService; +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 org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequestScope +public class MessageBuilderServiceImpl implements MessageInfoBuilderService { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(EmailContactExtractor.class)); + + private final List staticFieldList; + private final JsonHandlingService jsonHandlingService; + private final QueryFactory queryFactory; + private final TenantConfigurationService tenantConfigurationService; + private final TenantScope scope; + private final ErrorThesaurusProperties errors; + private final LocaleService localeService; + + @Autowired + public MessageBuilderServiceImpl(@Qualifier(NotificationConfig.BeanQualifier.STATIC_FIELD_LIST) + List staticFieldList, + JsonHandlingService jsonHandlingService, + QueryFactory queryFactory, + TenantConfigurationService tenantConfigurationService, + TenantScope scope, ErrorThesaurusProperties errors, + LocaleService localeService) { + this.staticFieldList = staticFieldList; + this.jsonHandlingService = jsonHandlingService; + this.queryFactory = queryFactory; + this.tenantConfigurationService = tenantConfigurationService; + this.scope = scope; + this.errors = errors; + this.localeService = localeService; + } + + @Override + public MessageInfo buildMessageInfo(NotificationEntity notification) { + MessageInfo messageInfo = new MessageInfo(); + messageInfo.setUserId(notification.getUserId()); + messageInfo.setFields(staticFieldList.stream() + .filter(field -> !StringUtils.isNullOrEmpty(field.getKey()) && !StringUtils.isNullOrEmpty(field.getValue())) + .map(field -> { + FieldInfo fieldInfo = new FieldInfo(); + fieldInfo.setKey(field.getKey()); + fieldInfo.setType(DataType.valueOf(field.getType())); + fieldInfo.setValue(field.getValue()); + return fieldInfo; + }).collect(Collectors.toList())); + NotificationFieldData data = this.jsonHandlingService.fromJsonSafe(NotificationFieldData.class, notification.getData()); + if (data != null && data.getFields() != null) messageInfo.getFields().addAll(data.getFields()); + + UserEntity userProfile = null; + if (notification.getUserId() != null) { + userProfile = this.queryFactory.query(UserQuery.class) + .ids(notification.getUserId()) + .isActive(IsActive.Active).first(); + } + + if (userProfile != null) { +// messageInfo.setLanguage(userProfile.getLanguage()); +// messageInfo.setCulture(userProfile.getCulture()); +// messageInfo.setTimeZone(userProfile.getTimezone()); + } else { + DefaultUserLocaleConfigurationDataContainer defaultUserLocaleConfiguration = this.tenantConfigurationService.collectTenantUserLocale(); + if (defaultUserLocaleConfiguration != null) { + messageInfo.setLanguage(defaultUserLocaleConfiguration.getLanguage()); + messageInfo.setCulture(defaultUserLocaleConfiguration.getCulture()); + messageInfo.setTimeZone(defaultUserLocaleConfiguration.getTimeZone()); + } + } + + if (messageInfo.getLanguage() == null || messageInfo.getLanguage().isBlank()) messageInfo.setLanguage(this.localeService.language()); + if (messageInfo.getCulture() == null || messageInfo.getCulture().isBlank()) messageInfo.setCulture(this.localeService.cultureName()); + if (messageInfo.getTimeZone() == null || messageInfo.getTimeZone().isBlank()) messageInfo.setTimeZone(this.localeService.timezoneName()); + + if (this.scope.isMultitenant()) { + TenantEntity tenantInfo = this.queryFactory.query(TenantQuery.class) + .isActive(IsActive.Active).firstAs(new BaseFieldSet(TenantEntity._id, TenantEntity._code)); + + if (tenantInfo == null) { + try { + logger.error("Could not retrieve tenant info for notification {} in tenant {}", notification.getId(), scope.getTenant()); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + throw new MyForbiddenException(this.errors.getMissingTenant().getCode(), this.errors.getMissingTenant().getMessage()); + } + + messageInfo.setTenantId(tenantInfo.getId()); + messageInfo.setTenantCode(tenantInfo.getCode()); + } + + return messageInfo; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/infobuilder/MessageInfoBuilderService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/infobuilder/MessageInfoBuilderService.java new file mode 100644 index 000000000..619a78316 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/infobuilder/MessageInfoBuilderService.java @@ -0,0 +1,9 @@ +package gr.cite.notification.service.message.infobuilder; + +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.service.message.model.MessageInfo; + +public interface MessageInfoBuilderService { + + MessageInfo buildMessageInfo(NotificationEntity notification); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/EmailMessage.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/EmailMessage.java new file mode 100644 index 000000000..8c0e5d5dc --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/EmailMessage.java @@ -0,0 +1,22 @@ +package gr.cite.notification.service.message.model; + +public class EmailMessage implements Message{ + private String subject; + private String body; + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/InAppMessage.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/InAppMessage.java new file mode 100644 index 000000000..bf6b6ec5b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/InAppMessage.java @@ -0,0 +1,66 @@ +package gr.cite.notification.service.message.model; + +import gr.cite.notification.common.enums.InAppNotificationPriority; +import gr.cite.notification.common.types.notification.FieldInfo; + +import java.util.List; +import java.util.UUID; + +public class InAppMessage implements Message{ + private String subject; + private String body; + private UUID type; + private InAppNotificationPriority priority; + private List ExtraData; + + public InAppMessage() { + } + + public InAppMessage(String subject, String body, UUID type, InAppNotificationPriority priority, List extraData) { + this.subject = subject; + this.body = body; + this.type = type; + this.priority = priority; + ExtraData = extraData; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public UUID getType() { + return type; + } + + public void setType(UUID type) { + this.type = type; + } + + public InAppNotificationPriority getPriority() { + return priority; + } + + public void setPriority(InAppNotificationPriority priority) { + this.priority = priority; + } + + public List getExtraData() { + return ExtraData; + } + + public void setExtraData(List extraData) { + ExtraData = extraData; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/Message.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/Message.java new file mode 100644 index 000000000..116ebea8d --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/Message.java @@ -0,0 +1,4 @@ +package gr.cite.notification.service.message.model; + +public interface Message { +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/MessageInfo.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/MessageInfo.java new file mode 100644 index 000000000..d7fc78c98 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/message/model/MessageInfo.java @@ -0,0 +1,72 @@ +package gr.cite.notification.service.message.model; + +import gr.cite.notification.common.types.notification.FieldInfo; + +import java.util.List; +import java.util.UUID; + +public class MessageInfo { + private UUID userId; + private String language; + private String culture; + private String timeZone; + private UUID tenantId; + private String tenantCode; + private List fields; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + 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 List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notification/NotificationService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notification/NotificationService.java new file mode 100644 index 000000000..3cf6b5c29 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notification/NotificationService.java @@ -0,0 +1,21 @@ +package gr.cite.notification.service.notification; + +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.model.Notification; +import gr.cite.notification.model.SendNotificationResult; +import gr.cite.notification.model.persist.NotificationPersist; +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 NotificationService{ + + Notification persist(NotificationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException; + void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + SendNotificationResult doNotify(NotificationEntity notification); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notification/NotificationServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notification/NotificationServiceImpl.java new file mode 100644 index 000000000..26dc42061 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notification/NotificationServiceImpl.java @@ -0,0 +1,189 @@ +package gr.cite.notification.service.notification; + +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.enums.NotificationContactType; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.convention.ConventionService; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.model.SendNotificationResult; +import gr.cite.notification.model.builder.NotificationBuilder; +import gr.cite.notification.model.Notification; +import gr.cite.notification.model.deleter.NotificationDeleter; +import gr.cite.notification.model.persist.NotificationPersist; +import gr.cite.notification.query.NotificationQuery; +import gr.cite.notification.service.channelResolution.ChannelResolutionService; +import gr.cite.notification.service.contact.extractor.ContactExtractorFactory; +import gr.cite.notification.service.contact.model.Contact; +import gr.cite.notification.service.message.builder.MessageBuilderFactory; +import gr.cite.notification.service.message.model.Message; +import gr.cite.notification.service.notify.NotifierFactory; +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 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.Service; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Service +@RequestScope +public class NotificationServiceImpl implements NotificationService { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationServiceImpl.class)); + private final TenantScopedEntityManager entityManager; + private final AuthorizationService authService; + private final AuthorizationService authorizationService; + private final DeleterFactory deleterFactory; + private final BuilderFactory builderFactory; + private final ConventionService conventionService; + private final ErrorThesaurusProperties errors; + private final MessageSource messageSource; + private final ChannelResolutionService channelResolutionService; + private final MessageBuilderFactory messageBuilderFactory; + private final ContactExtractorFactory contactExtractorFactory; + private final NotifierFactory notifierFactory; + private final ApplicationContext applicationContext; + private final QueryFactory queryFactory; + + @Autowired + public NotificationServiceImpl( + TenantScopedEntityManager entityManager, + AuthorizationService authService, AuthorizationService authorizationService, + DeleterFactory deleterFactory, + BuilderFactory builderFactory, + ConventionService conventionService, + ErrorThesaurusProperties errors, + MessageSource messageSource, + ChannelResolutionService channelResolutionService, MessageBuilderFactory messageBuilderFactory, ContactExtractorFactory contactExtractorFactory, NotifierFactory notifierFactory, ApplicationContext applicationContext, QueryFactory queryFactory) { + this.entityManager = entityManager; + this.authService = authService; + this.authorizationService = authorizationService; + this.deleterFactory = deleterFactory; + this.builderFactory = builderFactory; + this.conventionService = conventionService; + this.errors = errors; + this.messageSource = messageSource; + this.channelResolutionService = channelResolutionService; + this.messageBuilderFactory = messageBuilderFactory; + this.contactExtractorFactory = contactExtractorFactory; + this.notifierFactory = notifierFactory; + this.applicationContext = applicationContext; + this.queryFactory = queryFactory; + } + + @Override + public Notification persist(NotificationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException { + logger.debug(new MapLogEntry("persisting notification").And("model", model).And("fields", fields)); + + this.authorizationService.authorizeForce(Permission.EditNotification); + + Boolean isUpdate = this.conventionService.isValidGuid(model.getId()); + + NotificationEntity data = null; + if (isUpdate) { + data = this.entityManager.find(NotificationEntity.class, model.getId()); + if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Notification.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); + } else { + data = new NotificationEntity(); + data.setId(UUID.randomUUID()); + data.setIsActive(IsActive.Active); + data.setCreatedAt(Instant.now()); + } + + data.setNotifiedAt(model.getNotifiedAt()); + data.setContactHint(model.getContactHint()); + data.setContactTypeHint(model.getContactTypeHint()); + data.setType(model.getType()); + data.setUserId(model.getUserId()); + data.setUpdatedAt(Instant.now()); + + if (isUpdate) this.entityManager.merge(data); + else this.entityManager.persist(data); + + this.entityManager.flush(); + + return this.builderFactory.builder(NotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(BaseFieldSet.build(fields, Notification._id, Notification._hash), data); + } + + public SendNotificationResult doNotify(NotificationEntity notification) { + List contactTypes = this.orderContactTypes(notification); + for (NotificationContactType contactType: contactTypes) { + SendNotificationResult result = this.sendNotification(notification, contactType); + if (result.getSuccess()) return result; + } + return null; + } + + private List orderContactTypes(NotificationEntity notification) { + List contactTypes = this.channelResolutionService.resolve(notification.getType(), notification.getUserId()); + if (notification.getContactTypeHint() == null) return contactTypes; + + List ordered = new ArrayList<>(Collections.singleton(notification.getContactTypeHint())); + for (NotificationContactType type: contactTypes) + { + if (type == notification.getContactTypeHint()) continue; + ordered.add(type); + } + + return ordered; + } + + private SendNotificationResult sendNotification(NotificationEntity notification, NotificationContactType contactType) { + String tracking = null; + try { + Message message = this.messageBuilderFactory.getFromType(contactType).buildMessage(notification); + if (message == null) { + return new SendNotificationResult(false, contactType); + } + Contact contact = contactExtractorFactory.fromContactType(contactType).extract(notification); + if (contact == null) { + return new SendNotificationResult(false, contactType); + } + TenantScope tenantScoped = applicationContext.getBean(TenantScope.class); + if (notification.getTenantId() != null){ + TenantEntity tenant = this.entityManager.find(TenantEntity.class, notification.getTenantId()); + tenantScoped.setTenant(tenant.getId(), tenant.getCode()); + } + + tracking = notifierFactory.fromContactType(contactType).notify(contact, message); + }catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + return new SendNotificationResult(false, contactType); + } + + return new SendNotificationResult(true, contactType, tracking); + } + + @Override + public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug("deleting tenant: {}", id); + this.authService.authorizeForce(Permission.DeleteNotification); + this.deleterFactory.deleter(NotificationDeleter.class).deleteAndSaveByIds(List.of(id)); + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationscheduling/NotificationSchedulingService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationscheduling/NotificationSchedulingService.java new file mode 100644 index 000000000..2f161f7b7 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationscheduling/NotificationSchedulingService.java @@ -0,0 +1,21 @@ +package gr.cite.notification.service.notificationscheduling; + +import gr.cite.notification.schedule.model.CandidateInfo; +import gr.cite.notification.schedule.model.MiniTenant; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +public interface NotificationSchedulingService { + + List getTenants(); + CandidateInfo candidateToNotify(Instant lastCandidateNotificationCreationTimestamp); + Boolean shouldWait(CandidateInfo candidateInfo); + Boolean shouldOmitNotify(UUID notificationId); + Boolean notify(UUID notificationId); + + CandidateInfo candidateToTrack(Instant lastCandidateNotificationCreationTimestamp); + Boolean shouldOmitTracking(UUID notificationId); + Boolean track(UUID notificationId); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationscheduling/NotificationSchedulingServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationscheduling/NotificationSchedulingServiceImpl.java new file mode 100644 index 000000000..2f67e396f --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationscheduling/NotificationSchedulingServiceImpl.java @@ -0,0 +1,381 @@ +package gr.cite.notification.service.notificationscheduling; + +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationNotifyState; +import gr.cite.notification.common.enums.NotificationTrackingProcess; +import gr.cite.notification.common.enums.NotificationTrackingState; +import gr.cite.notification.common.scope.fake.FakeRequestScope; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.config.notification.NotificationProperties; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.data.TenantEntity; +import gr.cite.notification.model.SendNotificationResult; +import gr.cite.notification.query.NotificationQuery; +import gr.cite.notification.query.TenantQuery; +import gr.cite.notification.schedule.model.CandidateInfo; +import gr.cite.notification.schedule.model.MiniTenant; +import gr.cite.notification.service.notification.NotificationService; +import gr.cite.notification.service.track.TrackingFactory; +import gr.cite.notification.service.track.model.TrackerResponse; +import gr.cite.tools.data.query.Ordering; +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.context.ApplicationContext; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.OptimisticLockException; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +public class NotificationSchedulingServiceImpl implements NotificationSchedulingService { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationSchedulingServiceImpl.class)); + private final ApplicationContext applicationContext; + private final NotificationProperties properties; + + public NotificationSchedulingServiceImpl(ApplicationContext applicationContext, NotificationProperties properties) { + this.applicationContext = applicationContext; + this.properties = properties; + } + + + @Override + public List getTenants() { + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + List tenantIds = List.of(new MiniTenant(new UUID(0L, 0L), "")); + TenantScope tenantScope = applicationContext.getBean(TenantScope.class); + if (tenantScope.isMultitenant()) { + FieldSet fieldSet = new BaseFieldSet(List.of(TenantEntity._id, TenantEntity._code)); + TenantQuery tenantQuery = this.applicationContext.getBean(TenantQuery.class); + List tenants = tenantQuery.isActive(IsActive.Active).collectAs(fieldSet); + tenantIds = tenants.stream().map(tenantEntity -> new MiniTenant(tenantEntity.getId(), tenantEntity.getCode())).collect(Collectors.toList()); + } + return tenantIds; + } + } + + @Override + public CandidateInfo candidateToNotify(Instant lastCandidateNotificationCreationTimestamp) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + CandidateInfo candidateInfo = null; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + NotificationQuery notificationQuery = applicationContext.getBean(NotificationQuery.class); + NotificationEntity candidates; + + notificationQuery = notificationQuery + .isActive(IsActive.Active) + .notifyState(NotificationNotifyState.PENDING, NotificationNotifyState.ERROR) + .retryThreshold(Math.toIntExact(this.properties.getTask().getProcessor().getOptions().getRetryThreshold())) + .createdAfter(lastCandidateNotificationCreationTimestamp) + .ordering(new Ordering().addAscending(NotificationEntity.Field._createdAt)); + candidates = notificationQuery.first(); + if (candidates != null) { + NotificationNotifyState previousState = candidates.getNotifyState(); + candidates.setNotifyState(NotificationNotifyState.PROCESSING); + //candidates.setUpdatedAt(Instant.now()); + candidates = entityManager.merge(candidates); + entityManager.persist(candidates); + entityManager.flush(); + + candidateInfo = new CandidateInfo(candidates.getId(), previousState, candidates.getCreatedAt()); + } + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return candidateInfo; + } + + @Override + public Boolean shouldWait(CandidateInfo candidateInfo) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + Boolean shouldWait = false; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + NotificationEntity notification = entityManager.find(NotificationEntity.class, candidateInfo.getNotificationId()); + if (notification.getRetryCount() != null && notification.getRetryCount() >= 1) { + int accumulatedRetry = 0; + int pastAccumulateRetry = 0; + NotificationProperties.Task.Processor.Options options = properties.getTask().getProcessor().getOptions(); + for (int i = 1; i <= notification.getRetryCount() + 1; i += 1) + accumulatedRetry += (i * options.getRetryThreshold()); + for (int i = 1; i <= notification.getRetryCount(); i += 1) + pastAccumulateRetry += (i * options.getRetryThreshold()); + int randAccumulatedRetry = ThreadLocalRandom.current().nextInt((int) (accumulatedRetry / 2), accumulatedRetry + 1); + long additionalTime = randAccumulatedRetry > options.getMaxRetryDelaySeconds() ? options.getMaxRetryDelaySeconds() : randAccumulatedRetry; + long retry = pastAccumulateRetry + additionalTime; + + Instant retryOn = notification.getCreatedAt().plusSeconds(retry); + boolean itIsTime = retryOn.isBefore(Instant.now()); + + if (!itIsTime) { + notification.setNotifyState(candidateInfo.getPreviousState()); + //notification.setUpdatedAt(Instant.now()); + notification = entityManager.merge(notification); + entityManager.persist(notification); + + } + shouldWait = !itIsTime; + } + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return shouldWait; + } + + @Override + public Boolean shouldOmitNotify(UUID notificationId) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + Boolean shouldOmit = false; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + NotificationEntity notification = entityManager.find(NotificationEntity.class, notificationId); + long age = Instant.now().getEpochSecond() - notification.getCreatedAt().getEpochSecond(); + long omitSeconds = properties.getTask().getProcessor().getOptions().getTooOldToSendSeconds(); + if (age >= omitSeconds) { + notification.setNotifyState(NotificationNotifyState.OMITTED); + //notification.setUpdatedAt(Instant.now()); + notification = entityManager.merge(notification); + entityManager.persist(notification); + shouldOmit = true; + } + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return shouldOmit; + + } + + @Override + public Boolean notify(UUID notificationId) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + Boolean success = null; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + NotificationQuery notificationQuery = applicationContext.getBean(NotificationQuery.class); + NotificationEntity notification = notificationQuery.ids(notificationId).first(); + if (notification == null) throw new IllegalArgumentException("notification is null"); + + SendNotificationResult result = null; + NotificationService notificationService = this.applicationContext.getBean(NotificationService.class); + result = notificationService.doNotify(notification); + + if (result != null && result.getSuccess()) { + notification.setNotifyState(NotificationNotifyState.SUCCESSFUL); + notification.setTrackingData(result.getTrackingData()); + notification.setNotifiedWith(result.getContactType()); + notification.setNotifiedAt(Instant.now()); + } else { + notification.setNotifyState(NotificationNotifyState.ERROR); + notification.setRetryCount(notification.getRetryCount() != null ? notification.getRetryCount() + 1 : 0); + notification.setNotifiedWith(null); + notification.setNotifiedAt(null); + } + //notification.setUpdatedAt(Instant.now()); + + NotificationEntity notification1 = entityManager.merge(notification); + entityManager.persist(notification1); + + + //we want to return false for notification we want to add in the skip bag + success = result != null && result.getSuccess(); + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return success; + } + + @Override + public CandidateInfo candidateToTrack(Instant lastCandidateNotificationCreationTimestamp) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + CandidateInfo candidateInfo = null; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + NotificationQuery notificationQuery = applicationContext.getBean(NotificationQuery.class); + NotificationEntity candidates; + + notificationQuery = notificationQuery + .isActive(IsActive.Active) + .notifyState(NotificationNotifyState.SUCCESSFUL) + .notifiedWithHasValue() + .notifiedWithHasValue() + .createdAfter(lastCandidateNotificationCreationTimestamp) + .trackingState(NotificationTrackingState.QUEUED, NotificationTrackingState.SENT, NotificationTrackingState.UNDEFINED) + .trackingProgress(NotificationTrackingProcess.PENDING); + candidates = notificationQuery.first(); + if (candidates != null) { + NotificationNotifyState previousState = candidates.getNotifyState(); + candidates.setTrackingProcess(NotificationTrackingProcess.PROCESSING); + //candidates.setUpdatedAt(Instant.now()); + candidates = entityManager.merge(candidates); + entityManager.persist(candidates); + candidateInfo = new CandidateInfo(candidates.getId(), previousState, candidates.getCreatedAt()); + } + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return candidateInfo; + } + + @Override + public Boolean shouldOmitTracking(UUID notificationId) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + Boolean shouldOmit = false; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + NotificationEntity notification = entityManager.find(NotificationEntity.class, notificationId); + long age = Instant.now().getEpochSecond() - notification.getNotifiedAt().getEpochSecond(); + long omitSeconds = properties.getTask().getProcessor().getOptions().getTooOldToTrackSeconds(); + if (age >= omitSeconds) { + notification.setTrackingProcess(NotificationTrackingProcess.OMITTED); + //notification.setUpdatedAt(Instant.now()); + notification = entityManager.merge(notification); + entityManager.persist(notification); + shouldOmit = true; + } + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return shouldOmit; + + } + + @Override + public Boolean track(UUID notificationId) { + EntityManager entityManager = null; + EntityTransaction transaction = null; + Boolean success = null; + try (FakeRequestScope fakeRequestScope = new FakeRequestScope()) { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + + NotificationQuery notificationQuery = applicationContext.getBean(NotificationQuery.class); + NotificationEntity notification = notificationQuery.ids(notificationId).first(); + if (notification == null) throw new IllegalArgumentException("notification is null"); + if (notification.getNotifiedWith() == null) throw new IllegalArgumentException("Notification's Notified With is null"); + if (notification.getNotifiedAt() == null) throw new IllegalArgumentException("Notification's Notified At is null"); + + TrackerResponse result = null; + try { + TrackingFactory trackingFactory = applicationContext.getBean(TrackingFactory.class); + result = trackingFactory.fromContactType(notification.getNotifiedWith()).track(notification); + } catch (Exception e) { + logger.error("Could not complete track for notification " + notification.getId(), e); + } + + if (result != null && notification.getTrackingProcess().equals(result.getTrackingProgress()) && notification.getTrackingState().equals(result.getTrackingState())) { + return false; + } + + if (result != null) { + notification.setTrackingState(result.getTrackingState()); + notification.setTrackingProcess(result.getTrackingProgress()); + notification.setTrackingData(result.getTrackingData()); + } else { + notification.setTrackingProcess(NotificationTrackingProcess.ERROR); + } + //notification.setUpdatedAt(Instant.now()); + + NotificationEntity notification1 = entityManager.merge(notification); + entityManager.persist(notification1); + + + //we want to return false for notification we want to add in the skip bag + success = result != null && !notification.getTrackingProcess().equals(NotificationTrackingProcess.ERROR); + transaction.commit(); + } catch (OptimisticLockException e) { + logger.error("Optimistic Lock Error occurred on Notification persist"); + if (transaction != null) transaction.rollback(); + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); + } + return success; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/EmailNotifier.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/EmailNotifier.java new file mode 100644 index 000000000..8d46a8f8e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/EmailNotifier.java @@ -0,0 +1,67 @@ +package gr.cite.notification.service.notify; + +import gr.cite.notification.common.StringUtils; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.config.email.EmailProperties; +import gr.cite.notification.service.contact.model.Contact; +import gr.cite.notification.service.contact.model.EmailContact; +import gr.cite.notification.service.message.model.EmailMessage; +import gr.cite.notification.service.message.model.Message; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.nio.charset.Charset; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@Component +public class EmailNotifier implements Notify{ + private final static LoggerService logger = new LoggerService(LoggerFactory.getLogger(EmailNotifier.class)); + + private final JavaMailSender javaMailSender; + private final EmailProperties emailProperties; + + @Autowired + public EmailNotifier(JavaMailSender javaMailSender, EmailProperties emailProperties) { + this.javaMailSender = javaMailSender; + this.emailProperties = emailProperties; + } + + @Override + public String notify(Contact contact, Message message) { + EmailContact emailContact = (EmailContact) contact; + EmailMessage emailMessage = (EmailMessage) message; + + if (emailContact == null || emailContact.getEmails() == null || emailContact.getEmails().stream().allMatch(StringUtils::isNullOrEmpty)) throw new IllegalArgumentException("contact not provided"); + if (emailMessage == null) throw new IllegalArgumentException("message not provided"); + + try { + MimeMessage mailMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mailMessage, UTF_8.name()); + helper.setTo(emailContact.getEmails().toArray(new String[0])); + helper.setCc(emailContact.getCcEmails().toArray(new String[0])); + helper.setBcc(emailContact.getBccEmails().toArray(new String[0])); + helper.setSubject(emailMessage.getSubject()); + helper.setText(emailMessage.getBody(), true); + helper.setFrom(emailProperties.getAddress()); + javaMailSender.send(mailMessage); + } catch (MessagingException e) { + logger.error(e.getLocalizedMessage(), e); + throw new RuntimeException(e); + } + return null; + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.EMAIL; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/InAppNotifier.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/InAppNotifier.java new file mode 100644 index 000000000..79e2d397b --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/InAppNotifier.java @@ -0,0 +1,90 @@ +package gr.cite.notification.service.notify; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.enums.NotificationInAppTracking; +import gr.cite.notification.common.scope.tenant.TenantScope; +import gr.cite.notification.common.types.notification.InAppTrackingData; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.service.contact.model.Contact; +import gr.cite.notification.service.contact.model.InAppContact; +import gr.cite.notification.service.message.model.InAppMessage; +import gr.cite.notification.service.message.model.Message; +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.stereotype.Component; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import java.time.Instant; +import java.util.UUID; + +@Component +public class InAppNotifier implements Notify{ + private final static LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotifier.class)); + + private final JsonHandlingService jsonHandlingService; + private final ApplicationContext applicationContext; + + @Autowired + public InAppNotifier(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext) { + this.jsonHandlingService = jsonHandlingService; + this.applicationContext = applicationContext; + } + + @Override + public String notify(Contact contact, Message message) { + String data = null; + EntityManager entityManager = null; + EntityTransaction transaction = null; + try { + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + + entityManager = entityManagerFactory.createEntityManager(); + transaction = entityManager.getTransaction(); + transaction.begin(); + InAppContact inAppContact = (InAppContact) contact; + if (inAppContact == null) throw new IllegalArgumentException("contact not provided"); + + InAppMessage inAppMessage = (InAppMessage) message; + if (inAppMessage == null) throw new IllegalArgumentException("message not provided"); + + TenantScope tenantScope = applicationContext.getBean(TenantScope.class); + InAppNotificationEntity inApp = new InAppNotificationEntity(); + + inApp.setId(UUID.randomUUID()); + inApp.setUserId(inAppContact.getUserId()); + inApp.setIsActive(IsActive.Active); + inApp.setType(inAppMessage.getType()); + inApp.setTrackingState(NotificationInAppTracking.STORED); + inApp.setPriority(inAppMessage.getPriority()); + inApp.setSubject(inAppMessage.getSubject()); + inApp.setBody(inAppMessage.getBody()); + inApp.setExtraData(inAppMessage.getExtraData() != null ? this.jsonHandlingService.toJsonSafe(inAppMessage.getExtraData()) : null); + inApp.setCreatedAt(Instant.now()); + inApp.setTenantId(tenantScope.getTenant()); + + inApp = entityManager.merge(inApp); + entityManager.persist(inApp); + + InAppTrackingData trackingData = new InAppTrackingData(inApp.getId()); + data = this.jsonHandlingService.toJsonSafe(trackingData); + transaction.commit(); + } catch (Exception e) { + if (transaction != null) transaction.rollback(); + logger.error(e.getMessage(), e); + } finally { + if (entityManager != null) entityManager.close(); + } + return data; + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.IN_APP; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/NotifierFactory.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/NotifierFactory.java new file mode 100644 index 000000000..749524670 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/NotifierFactory.java @@ -0,0 +1,22 @@ +package gr.cite.notification.service.notify; + +import gr.cite.notification.common.enums.NotificationContactType; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class NotifierFactory { + + private Map notifyMap; + + public NotifierFactory(List notifies) { + this.notifyMap = notifies.stream().collect(Collectors.toMap(Notify::supports, notify -> notify)); + } + + public Notify fromContactType(NotificationContactType type) { + return this.notifyMap.getOrDefault(type, null); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/Notify.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/Notify.java new file mode 100644 index 000000000..b1b2082de --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notify/Notify.java @@ -0,0 +1,12 @@ +package gr.cite.notification.service.notify; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.service.contact.model.Contact; +import gr.cite.notification.service.message.model.Message; + +public interface Notify { + + String notify(Contact contact, Message message); + + NotificationContactType supports(); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java new file mode 100644 index 000000000..eb295e696 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java @@ -0,0 +1,26 @@ +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.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.model.TenantConfiguration; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationEmailClientPersist; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationNotifierListPersist; +import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationUserLocaleIntegrationPersist; +import gr.cite.tools.fieldset.FieldSet; + +import javax.management.InvalidApplicationException; +import java.util.Set; +import java.util.UUID; + +public interface TenantConfigurationService { + EmailClientConfigurationDataContainer collectTenantEmailClient(); + DefaultUserLocaleConfigurationDataContainer collectTenantUserLocale(); + NotifierListConfigurationDataContainer collectTenantNotifierList(); + NotifierListConfigurationDataContainer collectTenantAvailableNotifierList(Set notificationTypes); + TenantConfiguration persist(TenantConfigurationEmailClientPersist emailClientPersist, FieldSet fieldSet); + TenantConfiguration persist(TenantConfigurationUserLocaleIntegrationPersist userLocaleIntegrationPersist, FieldSet fieldSet); + TenantConfiguration persist(TenantConfigurationNotifierListPersist notifierListPersist, FieldSet fieldSet); + void deleteAndSave(UUID id) throws InvalidApplicationException; + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java new file mode 100644 index 000000000..e5fd90cf2 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java @@ -0,0 +1,245 @@ +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.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +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.TenantConfigurationNotifierListPersist; +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.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@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 NotifierListConfigurationDataContainer collectTenantNotifierList() { + TenantConfigurationQuery query = applicationContext.getBean(TenantConfigurationQuery.class); + TenantConfigurationEntity configurationEntity = query.isActive(IsActive.Active).type(TenantConfigurationType.NOTIFIER_LIST).first(); + String data = configurationEntity != null ? configurationEntity.getValue() : null; + if (data == null) return null; + + try { + NotifierListConfigurationDataContainer notifierListData = this.jsonHandlingService.fromJson(NotifierListConfigurationDataContainer.class, data); + return notifierListData; + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + @Override + public NotifierListConfigurationDataContainer collectTenantAvailableNotifierList(Set notificationTypes) { + 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.Field.ID, TenantConfiguration.Field.HASH)), data); + return persisted; + } + + @Override + public TenantConfiguration persist(TenantConfigurationNotifierListPersist notifierListPersist, FieldSet fieldSet) { + NotifierListConfigurationDataContainer container = new NotifierListConfigurationDataContainer(); + container.setNotifiers(notifierListPersist.getNotifiers()); + try { + String value = jsonHandlingService.toJson(container); + return this.persist(notifierListPersist.getId(), notifierListPersist.getHash(), TenantConfigurationType.NOTIFIER_LIST, value, fieldSet); + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + @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.Field.ID)).stream().map(TenantConfigurationEntity::getId).collect(Collectors.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.Field.ID, TenantConfiguration.Field.HASH)), data); + return persisted; + //return null; + + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/EmailTracker.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/EmailTracker.java new file mode 100644 index 000000000..f7e43610a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/EmailTracker.java @@ -0,0 +1,32 @@ +package gr.cite.notification.service.track; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.enums.NotificationTrackingProcess; +import gr.cite.notification.common.enums.NotificationTrackingState; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.service.track.model.TrackerResponse; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class EmailTracker implements Track { + private final static LoggerService logger = new LoggerService(LoggerFactory.getLogger(EmailTracker.class)); + + @Override + public TrackerResponse track(NotificationEntity notification) { +// if (notification.getTrackingProgress() != NotificationTrackingProgress.PROCESSING) + if(1==1) + { + this.logger.warn("notification " + notification.getId() + " was send for tracking but it is not locked for processing"); + return null; + } + + return new TrackerResponse(NotificationTrackingState.NA, NotificationTrackingProcess.COMPLETED, null); + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.EMAIL; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/InAppTracker.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/InAppTracker.java new file mode 100644 index 000000000..85431023a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/InAppTracker.java @@ -0,0 +1,115 @@ +package gr.cite.notification.service.track; + +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.*; +import gr.cite.notification.common.types.notification.InAppTrackingData; +import gr.cite.notification.common.types.notification.TrackingTrace; +import gr.cite.notification.data.InAppNotificationEntity; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.query.InAppNotificationQuery; +import gr.cite.notification.service.track.model.TrackerResponse; +import gr.cite.notification.service.track.model.TrackingTraceData; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +@Component +public class InAppTracker implements Track{ + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppTracker.class)); + + private final JsonHandlingService jsonHandlingService; + private final QueryFactory queryFactory; + + @Autowired + public InAppTracker(JsonHandlingService jsonHandlingService, QueryFactory queryFactory) { + this.jsonHandlingService = jsonHandlingService; + this.queryFactory = queryFactory; + } + + @Override + public TrackerResponse track(NotificationEntity notification) { +// if (notification.getTrackingProgress() != NotificationTrackingProgress.PROCESSING) + if (1==1) + { + logger.warn("notification " + notification.getId() + " was send for tracking but it is not locked for processing"); + return null; + } + +// InAppTrackingData data = this.jsonHandlingService.fromJsonSafe(InAppTrackingData.class, notification.getTrackingData()); + InAppTrackingData data = null; + if (data == null) + { + logger.warn("could not find tracking info in notification " + notification.getId()); + return null; + } + + InAppNotificationEntity inApp = this.queryFactory.query(InAppNotificationQuery.class) + .ids(data.getInAppNotificationId()).first(); + + NotificationTrackingProcess completed = NotificationTrackingProcess.PENDING; + NotificationTrackingState state = this.evaluateState(inApp); + switch (state) + { + case DELIVERED: + case UNDELIVERED: + case FAILED: + case UNSENT: { completed = NotificationTrackingProcess.COMPLETED; break; } + case QUEUED: + case SENT: { completed = NotificationTrackingProcess.PENDING; break; } + //Something went wrong + case NA: + case UNDEFINED: + default: { completed = NotificationTrackingProcess.ERROR; break; } + } + + TrackingTraceData traceData = new TrackingTraceData (inApp.getTrackingState().toString(), inApp.getReadTime(), inApp.getIsActive() == IsActive.Active); + List traces = data.getTraces() != null ? data.getTraces() : new ArrayList<>(); + TrackingTrace lastTrace = traces.stream().max(Comparator.comparing(TrackingTrace::getAt)).orElse(null); + + Boolean hasTrackingChanges = false; + if (lastTrace != null) + { + TrackingTraceData lastTrackingTraceData = this.jsonHandlingService.fromJsonSafe(TrackingTraceData.class, lastTrace.getData()); + hasTrackingChanges = lastTrackingTraceData == null || !lastTrackingTraceData.equals(traceData); + } + else + { + hasTrackingChanges = true; + } + + if (hasTrackingChanges) + { + traces.add(new TrackingTrace(Instant.now(), this.jsonHandlingService.toJsonSafe(traceData))); + } + InAppTrackingData newData = new InAppTrackingData(data.getInAppNotificationId(), traces); + + TrackerResponse trackerResponse = new TrackerResponse(state, completed, this.jsonHandlingService.toJsonSafe(newData)); + + return trackerResponse; + } + + private NotificationTrackingState evaluateState(InAppNotificationEntity notification) + { + if (notification == null) return NotificationTrackingState.FAILED; + if (notification.getIsActive() == IsActive.Inactive && notification.getTrackingState() == NotificationInAppTracking.STORED) return NotificationTrackingState.UNDELIVERED; + + switch (notification.getTrackingState()) + { + case DELIVERED: return NotificationTrackingState.DELIVERED; + case STORED: return NotificationTrackingState.SENT; + default: return NotificationTrackingState.UNDELIVERED; + } + } + + @Override + public NotificationContactType supports() { + return NotificationContactType.IN_APP; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/Track.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/Track.java new file mode 100644 index 000000000..9231168f5 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/Track.java @@ -0,0 +1,12 @@ +package gr.cite.notification.service.track; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.data.NotificationEntity; +import gr.cite.notification.service.track.model.TrackerResponse; + +public interface Track { + + TrackerResponse track(NotificationEntity notification); + + NotificationContactType supports(); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/TrackingFactory.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/TrackingFactory.java new file mode 100644 index 000000000..6c1d56a17 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/TrackingFactory.java @@ -0,0 +1,22 @@ +package gr.cite.notification.service.track; + +import gr.cite.notification.common.enums.NotificationContactType; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class TrackingFactory { + + private Map trackMap; + + public TrackingFactory(List notifies) { + this.trackMap = notifies.stream().collect(Collectors.toMap(Track::supports, track -> track)); + } + + public Track fromContactType(NotificationContactType type) { + return this.trackMap.getOrDefault(type, null); + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/model/TrackerResponse.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/model/TrackerResponse.java new file mode 100644 index 000000000..d289d36f5 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/model/TrackerResponse.java @@ -0,0 +1,43 @@ +package gr.cite.notification.service.track.model; + +import gr.cite.notification.common.enums.NotificationTrackingProcess; +import gr.cite.notification.common.enums.NotificationTrackingState; + +public class TrackerResponse { + private NotificationTrackingState trackingState; + private NotificationTrackingProcess trackingProgress; + private String trackingData; + + public TrackerResponse() { + } + + public TrackerResponse(NotificationTrackingState trackingState, NotificationTrackingProcess trackingProgress, String trackingData) { + this.trackingState = trackingState; + this.trackingProgress = trackingProgress; + this.trackingData = trackingData; + } + + public NotificationTrackingState getTrackingState() { + return trackingState; + } + + public void setTrackingState(NotificationTrackingState trackingState) { + this.trackingState = trackingState; + } + + public NotificationTrackingProcess getTrackingProgress() { + return trackingProgress; + } + + public void setTrackingProgress(NotificationTrackingProcess trackingProgress) { + this.trackingProgress = trackingProgress; + } + + public String getTrackingData() { + return trackingData; + } + + public void setTrackingData(String trackingData) { + this.trackingData = trackingData; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/model/TrackingTraceData.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/model/TrackingTraceData.java new file mode 100644 index 000000000..4da338e23 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/track/model/TrackingTraceData.java @@ -0,0 +1,62 @@ +package gr.cite.notification.service.track.model; + +import java.time.Instant; + +public class TrackingTraceData { + private String state; + private Instant readTime; + private Boolean isActive; + + public TrackingTraceData() { + } + + public TrackingTraceData(String state, Instant readTime, Boolean isActive) { + this.state = state; + this.readTime = readTime; + this.isActive = isActive; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Instant getReadTime() { + return readTime; + } + + public void setReadTime(Instant readTime) { + this.readTime = readTime; + } + + public Boolean getActive() { + return isActive; + } + + public void setActive(Boolean active) { + isActive = active; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TrackingTraceData that = (TrackingTraceData) o; + + if (!state.equals(that.state)) return false; + if (!readTime.equals(that.readTime)) return false; + return isActive.equals(that.isActive); + } + + @Override + public int hashCode() { + int result = state.hashCode(); + result = 31 * result + readTime.hashCode(); + result = 31 * result + isActive.hashCode(); + return result; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceService.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceService.java new file mode 100644 index 000000000..1a6931496 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceService.java @@ -0,0 +1,20 @@ +package gr.cite.notification.service.userNotificationPreference; + +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.model.UserNotificationPreference; +import gr.cite.notification.model.persist.UserNotificationPreferencePersist; +import gr.cite.tools.fieldset.FieldSet; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public interface UserNotificationPreferenceService { + + List persist(UserNotificationPreferencePersist model, FieldSet fieldSet); + NotifierListConfigurationDataContainer collectUserAvailableNotifierList(Set notificationTypes); + List collectUserNotificationPreferences(UUID id); + Map> collectUserNotificationPreferences(List ids); +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java new file mode 100644 index 000000000..85ca77a0a --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java @@ -0,0 +1,205 @@ +package gr.cite.notification.service.userNotificationPreference; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.OwnedResource; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.enums.NotificationContactType; +import gr.cite.notification.common.types.tenantconfiguration.NotifierListConfigurationDataContainer; +import gr.cite.notification.config.notification.NotificationConfig; +import gr.cite.notification.data.TenantScopedEntityManager; +import gr.cite.notification.data.UserNotificationPreferenceEntity; +import gr.cite.notification.errorcode.ErrorThesaurusProperties; +import gr.cite.notification.model.UserNotificationPreference; +import gr.cite.notification.model.builder.UserNotificationPreferenceBuilder; +import gr.cite.notification.model.persist.UserNotificationPreferencePersist; +import gr.cite.notification.query.UserNotificationPreferenceQuery; +import gr.cite.notification.service.tenantconfiguration.TenantConfigurationService; +import gr.cite.tools.data.builder.BuilderFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +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 org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequestScope +public class UserNotificationPreferenceServiceImpl implements UserNotificationPreferenceService{ + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserNotificationPreferenceServiceImpl.class)); + + private final QueryFactory queryFactory; + private final BuilderFactory builderFactory; + private final AuthorizationService authService; + private final TenantConfigurationService tenantConfigurationService; + private final Map> globalPoliciesMap; + private final ErrorThesaurusProperties errors; + private final TenantScopedEntityManager entityManager; + + @Autowired + public UserNotificationPreferenceServiceImpl(QueryFactory queryFactory, + BuilderFactory builderFactory, + AuthorizationService authService, + TenantConfigurationService tenantConfigurationService, + @Qualifier(NotificationConfig.BeanQualifier.GLOBAL_POLICIES_MAP) + Map> globalPoliciesMap, + ErrorThesaurusProperties errors, TenantScopedEntityManager entityManager) { + this.queryFactory = queryFactory; + this.builderFactory = builderFactory; + this.authService = authService; + this.tenantConfigurationService = tenantConfigurationService; + this.globalPoliciesMap = globalPoliciesMap; + this.errors = errors; + this.entityManager = entityManager; + } + + @Override + public List persist(UserNotificationPreferencePersist model, FieldSet fieldSet) { + logger.debug(new MapLogEntry("persisting").And("model", model).And("fields", fieldSet)); + + this.authService.authorizeForce(Permission.EditUserNotificationPreference); + + Map> currentNotificationListPolicies; + NotifierListConfigurationDataContainer tenantNotifierListPolicies = this.tenantConfigurationService.collectTenantNotifierList(); + if (tenantNotifierListPolicies != null) + { + currentNotificationListPolicies = mergeNotifierPolicies(tenantNotifierListPolicies.getNotifiers(), this.globalPoliciesMap); + } + else + { + currentNotificationListPolicies = this.globalPoliciesMap; + } + + if (model.getNotificationPreferences().entrySet().stream().anyMatch(entry -> model.getNotificationPreferences().get(entry.getKey()).stream().anyMatch(contactType -> !currentNotificationListPolicies.get(entry.getKey()).contains(contactType)))) + { + throw new MyValidationException(this.errors.getOverlappingTenantConfigurationNotifierList().getCode(), this.errors.getOverlappingTenantConfigurationNotifierList().getMessage()); + } + + List datas = this.patchAndSave(List.of(model)); + return this.builderFactory.builder(UserNotificationPreferenceBuilder.class).build(fieldSet, datas); + } + + @Override + public NotifierListConfigurationDataContainer collectUserAvailableNotifierList(Set notificationTypes) { + Map> currentNotificationListPolicies; + NotifierListConfigurationDataContainer tenantNotifierListPolicies = this.tenantConfigurationService.collectTenantNotifierList(); + if (tenantNotifierListPolicies != null) + { + currentNotificationListPolicies = mergeNotifierPolicies(tenantNotifierListPolicies.getNotifiers(), this.globalPoliciesMap); + } + else + { + currentNotificationListPolicies = this.globalPoliciesMap; + } + + if (notificationTypes != null && !notificationTypes.isEmpty()) + { + return new NotifierListConfigurationDataContainer(currentNotificationListPolicies + .entrySet().stream() + .filter(x -> notificationTypes.contains(x.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + else return new NotifierListConfigurationDataContainer(currentNotificationListPolicies); + } + + @Override + public List collectUserNotificationPreferences(UUID id) { + Map> result = this.collectUserNotificationPreferences(List.of(id)); + if (result != null) { + return result.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); + } + logger.error("failed to collect user notification preferences for user " + id); + return new ArrayList<>(); + } + + @Override + public Map> collectUserNotificationPreferences(List ids) { + return this.builderFactory.builder(UserNotificationPreferenceBuilder.class) + .build(new BaseFieldSet(UserNotificationPreference.Field.USER_ID, UserNotificationPreference.Field.TYPE, + UserNotificationPreference.Field.CHANNEL, UserNotificationPreference.Field.ORDINAL), this.queryFactory + .query(UserNotificationPreferenceQuery.class) + .userId(ids).collect()).stream().collect(Collectors.groupingBy(UserNotificationPreference::getUserId)); //GK: Yep that exist on JAVA Streams + } + + private Map> mergeNotifierPolicies(Map> overrides, Map> bases) + { + Map> mergedPolicies = new HashMap<>(); + for(Map.Entry> policyEntry: bases.entrySet()) + { + if (overrides.containsKey(policyEntry.getKey())) + { + List notifierList = overrides.get(policyEntry.getKey()); + if(notifierList == null) throw new MyApplicationException(this.errors.getSystemError().getCode(), this.errors.getSystemError().getMessage()); + mergedPolicies.put(policyEntry.getKey(), notifierList); + } + else + { + mergedPolicies.put(policyEntry.getKey(), policyEntry.getValue()); + } + } + return mergedPolicies; + } + + private List patchAndSave(List models) + { + List datas = new ArrayList<>(); + for (UserNotificationPreferencePersist model: models) + { + for (Map.Entry> notificationPreference: model.getNotificationPreferences().entrySet()) + { + this.patchAndSave(model.getUserId(), notificationPreference.getKey(), notificationPreference.getValue()); + } + } + return datas; + } + + private List patchAndSave(UUID userId, UUID type, List contactTypes) + { + List preferences = null; + try { + preferences = this.queryFactory + .query(UserNotificationPreferenceQuery.class) + .type(type) + .userId(userId).collect(); + int ordinal = 0; + + List updatedPreferences = new ArrayList<>(); + for (NotificationContactType contactType : contactTypes) { + UserNotificationPreferenceEntity preference = preferences.stream().filter(x -> x.getChannel() == contactType).findFirst().orElse(null); + + if (preference != null) { + preference.setOrdinal(ordinal); + + } else { + preference = new UserNotificationPreferenceEntity(); + preference.setUserId(userId); + preference.setType(type); + preference.setOrdinal(ordinal); + preference.setChannel(contactType); + preference.setCreatedAt(Instant.now()); + } + this.entityManager.merge(preference); + this.entityManager.persist(preference); + updatedPreferences.add(preference); + ordinal++; + } + List toDelete = preferences.stream().filter(x -> !updatedPreferences.stream().map(y-> y.getChannel()).collect(Collectors.toList()).contains(x.getChannel())).collect(Collectors.toList()); + for (UserNotificationPreferenceEntity deletable: toDelete) { + this.entityManager.remove(deletable); + } + } catch (InvalidApplicationException e) { + logger.error(e.getMessage(), e); + } + return preferences; + } +} diff --git a/dmp-backend/notification-service/pom.xml b/dmp-backend/notification-service/pom.xml new file mode 100644 index 000000000..f96491917 --- /dev/null +++ b/dmp-backend/notification-service/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.4 + + + + gr.cite + notification-service-parent + Argos Notification Parent + 1.0.0 + pom + + + notification + notification-web + + + + 11 + 11 + + + + + 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 + + + + + cite-dev + + + cite-maven + CITE Maven Repository + https://crepo.cite.gr/repository/cite-maven/ + + + + true + + + + + \ No newline at end of file diff --git a/dmp-backend/notification-service/settings.xml b/dmp-backend/notification-service/settings.xml new file mode 100644 index 000000000..044f36492 --- /dev/null +++ b/dmp-backend/notification-service/settings.xml @@ -0,0 +1,12 @@ + + + org.sonarsource.scanner.maven + + + + cite-maven + ${server.username} + ${server.password} + + +