diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/AffiliatedAuthorizationHandler.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/AffiliatedAuthorizationHandler.java new file mode 100644 index 000000000..e1f93182b --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/AffiliatedAuthorizationHandler.java @@ -0,0 +1,56 @@ +package gr.cite.annotation.web.authorization; + +import gr.cite.annotation.authorization.AffiliatedAuthorizationRequirement; +import gr.cite.annotation.authorization.AffiliatedResource; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("affiliatedAuthorizationHandler") +public class AffiliatedAuthorizationHandler extends AuthorizationHandler { + + private final CustomPermissionAttributesConfiguration myConfiguration; + + @Autowired + public AffiliatedAuthorizationHandler(CustomPermissionAttributesConfiguration myConfiguration) { + this.myConfiguration = myConfiguration; + } + + @Override + public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) { + AffiliatedAuthorizationRequirement req = (AffiliatedAuthorizationRequirement) requirement; + if (req.getRequiredPermissions() == null) + return ACCESS_NOT_DETERMINED; + + AffiliatedResource rs = (AffiliatedResource) resource; + + boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated(); + if (!isAuthenticated) + return ACCESS_NOT_DETERMINED; + + if (myConfiguration.getMyPolicies() == null) + return ACCESS_NOT_DETERMINED; + + int hits = 0; + Boolean entityAffiliated = rs != null && rs.getAffiliated() != null ? rs.getAffiliated() : null; + + for (String permission : req.getRequiredPermissions()) { + CustomPermissionAttributesProperties.MyPermission policy = myConfiguration.getMyPolicies().get(permission); + boolean hasPermission = policy != null && policy.getEntityAffiliated() != null && policy.getEntityAffiliated() && entityAffiliated != null && entityAffiliated; + if (hasPermission) hits += 1; + } + if ((req.getMatchAll() && req.getRequiredPermissions().size() == hits) || (!req.getMatchAll() && hits > 0)) + return ACCESS_GRANTED; + + return ACCESS_NOT_DETERMINED; + } + + @Override + public Class supporting() { + return AffiliatedAuthorizationRequirement.class; + } + +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/CustomPermissionAttributesConfiguration.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/CustomPermissionAttributesConfiguration.java new file mode 100644 index 000000000..4767d4ea3 --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/CustomPermissionAttributesConfiguration.java @@ -0,0 +1,24 @@ +package gr.cite.annotation.web.authorization; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; + +@Configuration +@EnableConfigurationProperties(CustomPermissionAttributesProperties.class) +public class CustomPermissionAttributesConfiguration { + + private final CustomPermissionAttributesProperties properties; + + @Autowired + public CustomPermissionAttributesConfiguration(CustomPermissionAttributesProperties properties) { + this.properties = properties; + } + + public HashMap getMyPolicies() { + return properties.getPolicies(); + } + +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/CustomPermissionAttributesProperties.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/CustomPermissionAttributesProperties.java new file mode 100644 index 000000000..13d0614aa --- /dev/null +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/authorization/CustomPermissionAttributesProperties.java @@ -0,0 +1,38 @@ +package gr.cite.annotation.web.authorization; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +import java.util.HashMap; + +@ConfigurationProperties(prefix = "permissions") +@ConditionalOnProperty(prefix = "permissions", name = "enabled", havingValue = "true") +public class CustomPermissionAttributesProperties { + + private final HashMap policies; + + @ConstructorBinding + public CustomPermissionAttributesProperties(HashMap policies) { + this.policies = policies; + } + + public HashMap getPolicies() { + return policies; + } + + public static class MyPermission { + + private final Boolean entityAffiliated; + + @ConstructorBinding + public MyPermission(Boolean entityAffiliated) { + this.entityAffiliated = entityAffiliated; + } + + public Boolean getEntityAffiliated() { + return entityAffiliated; + } + } + +} diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/config/SecurityConfiguration.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/config/SecurityConfiguration.java index e51b096d2..761b144ca 100644 --- a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/config/SecurityConfiguration.java +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/config/SecurityConfiguration.java @@ -1,5 +1,8 @@ package gr.cite.annotation.web.config; +import gr.cite.annotation.authorization.AffiliatedAuthorizationRequirement; +import gr.cite.annotation.authorization.AffiliatedResource; +import gr.cite.annotation.web.authorization.AffiliatedAuthorizationHandler; import gr.cite.commons.web.authz.handler.AuthorizationHandler; import gr.cite.commons.web.authz.handler.PermissionClientAuthorizationHandler; import gr.cite.commons.web.authz.policy.AuthorizationRequirement; @@ -37,16 +40,19 @@ public class SecurityConfiguration { private final AuthenticationManagerResolver authenticationManagerResolver; private final Filter apiKeyFilter; private final OwnedAuthorizationHandler ownedAuthorizationHandler; + private final AffiliatedAuthorizationHandler affiliatedAuthorizationHandler; @Autowired public SecurityConfiguration(WebSecurityProperties webSecurityProperties, @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, @Qualifier("apiKeyFilter") Filter apiKeyFilter, - @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler) { + @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler, + @Qualifier("affiliatedAuthorizationHandler") AffiliatedAuthorizationHandler affiliatedAuthorizationHandler) { this.webSecurityProperties = webSecurityProperties; this.authenticationManagerResolver = authenticationManagerResolver; this.apiKeyFilter = apiKeyFilter; this.ownedAuthorizationHandler = ownedAuthorizationHandler; + this.affiliatedAuthorizationHandler = affiliatedAuthorizationHandler; } @Bean @@ -78,7 +84,7 @@ public class SecurityConfiguration { //If not set / set to null, only the default authorization handlers will be used @Override public List> addCustomHandlers() { - return List.of(ownedAuthorizationHandler); + return List.of(affiliatedAuthorizationHandler, ownedAuthorizationHandler); } //Here you can register your custom authorization requirements (if any) @@ -114,6 +120,9 @@ public class SecurityConfiguration { if (OwnedResource.class.equals(type)) { return new OwnedAuthorizationRequirement(); } + if (AffiliatedResource.class.equals(type)) { + return new AffiliatedAuthorizationRequirement(matchAll, permissions); + } throw new IllegalArgumentException("resource"); } }; diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java index f322246b3..83b503d4e 100644 --- a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java @@ -2,6 +2,7 @@ package gr.cite.annotation.web.controllers; import com.fasterxml.jackson.core.JsonProcessingException; import gr.cite.annotation.audit.AuditableAction; +import gr.cite.annotation.authorization.AuthorizationFlags; import gr.cite.annotation.data.AnnotationEntity; import gr.cite.annotation.model.Annotation; import gr.cite.annotation.model.builder.AnnotationBuilder; @@ -71,9 +72,9 @@ public class AnnotationController { this.censorFactory.censor(AnnotationCensor.class).censor(lookup.getProject(), null); - AnnotationQuery query = lookup.enrich(this.queryFactory); + AnnotationQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.OwnerOrPermissionAssociated); List data = query.collect(); - List models = this.builderFactory.builder(AnnotationBuilder.class).build(lookup.getProject(), data); + List models = this.builderFactory.builder(AnnotationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(lookup.getProject(), data); long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); this.auditService.track(AuditableAction.Annotation_Query, "lookup", lookup); @@ -87,8 +88,8 @@ public class AnnotationController { this.censorFactory.censor(AnnotationCensor.class).censor(fieldSet, null); - AnnotationQuery query = this.queryFactory.query(AnnotationQuery.class).ids(id); - Annotation model = this.builderFactory.builder(AnnotationBuilder.class).build(fieldSet, query.firstAs(fieldSet)); + AnnotationQuery query = this.queryFactory.query(AnnotationQuery.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).ids(id); + Annotation model = this.builderFactory.builder(AnnotationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(fieldSet, query.firstAs(fieldSet)); if (model == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Annotation.class.getSimpleName()}, LocaleContextHolder.getLocale())); diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/TenantConfigurationController.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/TenantConfigurationController.java index dcdff05c9..a8c6f2b18 100644 --- a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/TenantConfigurationController.java +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/TenantConfigurationController.java @@ -67,7 +67,7 @@ public class TenantConfigurationController { 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); + List models = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).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); @@ -83,7 +83,7 @@ public class TenantConfigurationController { 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)); + TenantConfiguration model = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(fieldSet, query.firstAs(fieldSet)); if (model == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, TenantConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale())); diff --git a/annotation-service/annotation-web/src/main/resources/config/cache.yml b/annotation-service/annotation-web/src/main/resources/config/cache.yml index c49b230c5..e1863d7e8 100644 --- a/annotation-service/annotation-web/src/main/resources/config/cache.yml +++ b/annotation-service/annotation-web/src/main/resources/config/cache.yml @@ -32,6 +32,12 @@ cache: maximumSize: 500 enableRecordStats: false expireAfterWriteSeconds: 320 + - names: [ "affiliation" ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 5000 + enableRecordStats: false + expireAfterWriteSeconds: 20 mapCaches: apiKey: name: apikey @@ -48,3 +54,6 @@ cache: userAllowedTenant: name: userAccessTenant keyPattern: user_access_tenant_$user_id$_$tenant_id$:v0 + affiliation: + name: affiliation + keyPattern: affiliation_$entity$_$user$_$type$:v0 diff --git a/annotation-service/annotation-web/src/main/resources/config/permissions.yml b/annotation-service/annotation-web/src/main/resources/config/permissions.yml index 7b7fb2ed1..89c8bb4c4 100644 --- a/annotation-service/annotation-web/src/main/resources/config/permissions.yml +++ b/annotation-service/annotation-web/src/main/resources/config/permissions.yml @@ -1,7 +1,14 @@ permissions: - extendedClaims: [ ] policies: - + DeferredAffiliation: + roles: + - Admin + - User + - Manager + - DescriptionTemplateEditor + clients: [ ] + allowAnonymous: false + allowAuthenticated: false # Tenants BrowseTenant: roles: @@ -49,30 +56,18 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false - # UserContactInfo - BrowseUserContactInfo: - roles: - - Admin - clients: [ ] - allowAnonymous: true - allowAuthenticated: false - EditUserContactInfo: - roles: - - Admin - clients: [ ] - allowAnonymous: false - allowAuthenticated: false - DeleteUserContactInfo: - roles: - - Admin - claims: [ ] - clients: [ ] - allowAnonymous: false - allowAuthenticated: false #Annotation BrowseAnnotation: roles: - Admin + entityAffiliated: true + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + NewAnnotation: + roles: + - Admin + entityAffiliated: true clients: [ ] allowAnonymous: true allowAuthenticated: false @@ -85,6 +80,7 @@ permissions: DeleteAnnotation: roles: - Admin + entityAffiliated: false clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -100,6 +96,4 @@ permissions: - Admin clients: [ ] allowAnonymous: false - allowAuthenticated: false - - # ViewPage Permissions \ No newline at end of file + allowAuthenticated: false \ No newline at end of file diff --git a/annotation-service/annotation-web/src/main/resources/config/queue.yml b/annotation-service/annotation-web/src/main/resources/config/queue.yml index 919162228..d7961d2ec 100644 --- a/annotation-service/annotation-web/src/main/resources/config/queue.yml +++ b/annotation-service/annotation-web/src/main/resources/config/queue.yml @@ -45,7 +45,8 @@ queue: tenant-touch-topic: tenant.touch user-removal-topic: user.remove user-touch-topic: user.touch - annotation-entity-touch-topic: annotation.entity.touch + annotation-entities-touch-topic: annotation.entities.touch + annotation-entities-removal-topic: annotation.entities.remove rabbitmq: enable: false interval-seconds: 30 diff --git a/annotation-service/annotation/pom.xml b/annotation-service/annotation/pom.xml index 5a116402f..aefe1aef0 100644 --- a/annotation-service/annotation/pom.xml +++ b/annotation-service/annotation/pom.xml @@ -62,7 +62,7 @@ gr.cite oidc-authn - 1.0.0 + 2.2.1 gr.cite @@ -72,7 +72,7 @@ gr.cite oidc-authz - 1.0.0 + 2.1.0 gr.cite @@ -82,7 +82,7 @@ gr.cite validation - 3.0.2 + 3.0.3 diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AffiliatedAuthorizationRequirement.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AffiliatedAuthorizationRequirement.java new file mode 100644 index 000000000..c15d5cff2 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AffiliatedAuthorizationRequirement.java @@ -0,0 +1,40 @@ +package gr.cite.annotation.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class AffiliatedAuthorizationRequirement implements AuthorizationRequirement { + private final Set requiredPermissions; + private final boolean matchAll; + + public AffiliatedAuthorizationRequirement(Set requiredPermissions) { + this(false, requiredPermissions); + } + + public AffiliatedAuthorizationRequirement(String... requiredPermissions) { + this(false, requiredPermissions); + + } + + public AffiliatedAuthorizationRequirement(boolean matchAll, Set requiredPermissions) { + this.matchAll = matchAll; + this.requiredPermissions = requiredPermissions; + } + + public AffiliatedAuthorizationRequirement(boolean matchAll, String... requiredPermissions) { + this.requiredPermissions = new HashSet<>(); + this.matchAll = matchAll; + this.requiredPermissions.addAll(Arrays.stream(requiredPermissions).distinct().toList()); + } + + public Set getRequiredPermissions() { + return requiredPermissions; + } + + public boolean getMatchAll() { + return matchAll; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AffiliatedResource.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AffiliatedResource.java new file mode 100644 index 000000000..dc5e6ede9 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AffiliatedResource.java @@ -0,0 +1,22 @@ +package gr.cite.annotation.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationResource; + +public class AffiliatedResource extends AuthorizationResource { + private Boolean isAffiliated; + + public AffiliatedResource() { + } + + public AffiliatedResource(Boolean isAffiliated) { + this.isAffiliated = isAffiliated; + } + + public Boolean getAffiliated() { + return isAffiliated; + } + + public void setAffiliated(Boolean affiliated) { + isAffiliated = affiliated; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AuthorizationFlags.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AuthorizationFlags.java index 6b0192388..33ce8fc17 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AuthorizationFlags.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/AuthorizationFlags.java @@ -3,6 +3,6 @@ package gr.cite.annotation.authorization; import java.util.EnumSet; public enum AuthorizationFlags { - None, Permission, Owner; - public static final EnumSet OwnerOrPermission = EnumSet.of(Owner, Permission); + None, Permission, Associated, Owner; + public static final EnumSet OwnerOrPermissionAssociated = EnumSet.of(Owner, Permission, Associated); } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/Permission.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/Permission.java index b28aa042b..665736cbe 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/Permission.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/Permission.java @@ -1,15 +1,12 @@ package gr.cite.annotation.authorization; public final class Permission { + public static String DeferredAffiliation = "DeferredAffiliation"; //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"; @@ -19,13 +16,12 @@ public final class Permission { //Annotation public static final String BrowseAnnotation = "BrowseAnnotation"; + public static String NewAnnotation = "NewAnnotation"; public static String EditAnnotation = "EditAnnotation"; public static String DeleteAnnotation = "DeleteAnnotation"; public static final String BrowseTenantConfiguration = "BrowseTenantConfiguration"; public static final String EditTenantConfiguration = "EditTenantConfiguration"; - // UI Pages - public static String ViewTenantConfigurationPage = "ViewTenantConfigurationPage"; } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/PermissionNameProvider.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/PermissionNameProvider.java new file mode 100644 index 000000000..cd1d92d8a --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/PermissionNameProvider.java @@ -0,0 +1,39 @@ +package gr.cite.annotation.authorization; + +import gr.cite.annotation.convention.ConventionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +@Service +@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) +public class PermissionNameProvider { + private static final Logger logger = LoggerFactory.getLogger(PermissionNameProvider.class); + private final List permissions; + + public PermissionNameProvider(ConventionService conventionService) { + this.permissions = new ArrayList<>(); + Class clazz = Permission.class; + for (Field f : clazz.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + try { + Object value = f.get(null); + if (value != null && !conventionService.isNullOrEmpty((String)value)) this.permissions.add((String)value); + } catch (Exception e) { + logger.error("Can not load permission " + f.getName() + " " + e.getMessage()); + } + } + } + } + + public List getPermissions() { + return permissions; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AffiliationCacheOptions.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AffiliationCacheOptions.java new file mode 100644 index 000000000..b39f5ee08 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AffiliationCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.annotation.authorization.authorizationcontentresolver; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.affiliation") +public class AffiliationCacheOptions extends CacheOptions { +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AffiliationCacheService.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AffiliationCacheService.java new file mode 100644 index 000000000..4d468b495 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AffiliationCacheService.java @@ -0,0 +1,95 @@ +package gr.cite.annotation.authorization.authorizationcontentresolver; + +import gr.cite.annotation.authorization.AffiliatedResource; +import gr.cite.annotation.convention.ConventionService; +import gr.cite.tools.cache.CacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Locale; +import java.util.UUID; + +@Service +public class AffiliationCacheService extends CacheService { + + public static class AffiliationCacheValue { + + public AffiliationCacheValue() { + } + + public AffiliationCacheValue(UUID userId, UUID entityId, String entityType, AffiliatedResource affiliatedResource) { + this.userId = userId; + this.entityId = entityId; + this.entityType = entityType; + this.affiliatedResource = affiliatedResource; + } + + private UUID userId; + private UUID entityId; + private String entityType; + private AffiliatedResource affiliatedResource; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public AffiliatedResource getAffiliatedResource() { + return affiliatedResource; + } + + public void setAffiliatedResource(AffiliatedResource affiliatedResource) { + this.affiliatedResource = affiliatedResource; + } + } + + private final ConventionService conventionService; + @Autowired + public AffiliationCacheService(AffiliationCacheOptions options, ConventionService conventionService) { + super(options); + this.conventionService = conventionService; + } + + @Override + protected Class valueClass() { + return AffiliationCacheValue.class; + } + + @Override + public String keyOf(AffiliationCacheValue value) { + return this.buildKey(value.getUserId(), value.getEntityId(), value.getEntityType()); + } + + + public String buildKey(UUID userId, UUID entityId, String entityType) { + if (userId == null) throw new IllegalArgumentException("userId id is required"); + if (entityId == null) throw new IllegalArgumentException("entityId id is required"); + if (this.conventionService.isNullOrEmpty(entityType)) throw new IllegalArgumentException("entityType id is required"); + + HashMap keyParts = new HashMap<>(); + keyParts.put("$user$", userId.toString().replace("-", "").toLowerCase(Locale.ROOT)); + keyParts.put("$entity$", entityId.toString().replace("-", "").toLowerCase(Locale.ROOT)); + keyParts.put("$type$", entityType); + return this.generateKey(keyParts); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AuthorizationContentResolver.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AuthorizationContentResolver.java new file mode 100644 index 000000000..ea3e8b408 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AuthorizationContentResolver.java @@ -0,0 +1,20 @@ +package gr.cite.annotation.authorization.authorizationcontentresolver; + + +import gr.cite.annotation.authorization.AffiliatedResource; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface AuthorizationContentResolver { + AffiliatedResource entityAffiliation(UUID id); + + Map entitiesAffiliation(List ids); + + List getPermissionNames(); + + AffiliatedResource annotationAffiliation(UUID id); + + Map annotationsAffiliation(List ids); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java new file mode 100644 index 000000000..f1a4d68e8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java @@ -0,0 +1,118 @@ +package gr.cite.annotation.authorization.authorizationcontentresolver; + +import gr.cite.annotation.authorization.AffiliatedResource; +import gr.cite.annotation.authorization.PermissionNameProvider; +import gr.cite.annotation.common.enums.IsActive; +import gr.cite.annotation.common.scope.user.UserScope; +import gr.cite.annotation.data.AnnotationEntity; +import gr.cite.annotation.data.EntityUserEntity; +import gr.cite.annotation.model.Annotation; +import gr.cite.annotation.model.EntityUser; +import gr.cite.annotation.query.AnnotationQuery; +import gr.cite.annotation.query.EntityUserQuery; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequestScope +public class AuthorizationContentResolverImpl implements AuthorizationContentResolver { + private final QueryFactory queryFactory; + private final UserScope userScope; + private final AffiliationCacheService affiliationCacheService; + private final PermissionNameProvider permissionNameProvider; + public AuthorizationContentResolverImpl(QueryFactory queryFactory, UserScope userScope, AffiliationCacheService affiliationCacheService, PermissionNameProvider permissionNameProvider) { + this.queryFactory = queryFactory; + this.userScope = userScope; + this.affiliationCacheService = affiliationCacheService; + this.permissionNameProvider = permissionNameProvider; + } + + @Override + public AffiliatedResource entityAffiliation(UUID id) { + return this.entitiesAffiliation(List.of(id)).getOrDefault(id, new AffiliatedResource()); + } + @Override + public Map entitiesAffiliation(List ids){ + UUID userId = this.userScope.getUserIdSafe(); + Map affiliatedResources = new HashMap<>(); + for (UUID id : ids){ + affiliatedResources.put(id, new AffiliatedResource()); + } + if (userId == null || !userScope.isSet()) return affiliatedResources; + + List idsToResolve = this.getAffiliatedFromCache(ids, userId, affiliatedResources, AnnotationEntity._entityId); + if (idsToResolve.isEmpty()) return affiliatedResources; + + List entityUsers = this.queryFactory.query(EntityUserQuery.class).entityIds(ids).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(EntityUser._id).ensure(EntityUser._entityId)); + + for (UUID entityId : entityUsers.stream().map(EntityUserEntity::getEntityId).distinct().toList()){ + affiliatedResources.get(entityId).setAffiliated(true); + } + + this.ensureAffiliatedInCache(idsToResolve, userId, affiliatedResources, AnnotationEntity._entityId); + return affiliatedResources; + } + + @Override + public List getPermissionNames() { + return permissionNameProvider.getPermissions(); + } + + @Override + public AffiliatedResource annotationAffiliation(UUID id) { + return this.annotationsAffiliation(List.of(id)).getOrDefault(id, new AffiliatedResource()); + } + @Override + public Map annotationsAffiliation(List ids){ + UUID userId = this.userScope.getUserIdSafe(); + Map affiliatedResources = new HashMap<>(); + for (UUID id : ids){ + affiliatedResources.put(id, new AffiliatedResource()); + } + if (userId == null || !userScope.isSet()) return affiliatedResources; + + List idsToResolve = this.getAffiliatedFromCache(ids, userId, affiliatedResources, AnnotationEntity.class.getSimpleName()); + if (idsToResolve.isEmpty()) return affiliatedResources; + + List annotationEntities = this.queryFactory.query(AnnotationQuery.class).ids(ids).collectAs(new BaseFieldSet().ensure(Annotation._id).ensure(Annotation._entityId).ensure(Annotation._id)); + List entityUsers = this.queryFactory.query(EntityUserQuery.class).entityIds(annotationEntities.stream().map(AnnotationEntity::getEntityId).distinct().toList()).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(EntityUser._id).ensure(EntityUser._entityId)); + Map> dmpUsersMap = entityUsers.stream().collect(Collectors.groupingBy(EntityUserEntity::getEntityId)); + + for (AnnotationEntity annotation : annotationEntities){ + List dmpDescriptionUsers = dmpUsersMap.getOrDefault(annotation.getEntityId(), new ArrayList<>()); + if (!dmpDescriptionUsers.isEmpty()) { + affiliatedResources.get(annotation.getId()).setAffiliated(true); + } + } + + this.ensureAffiliatedInCache(idsToResolve, userId, affiliatedResources, AnnotationEntity.class.getSimpleName()); + return affiliatedResources; + } + + + + private List getAffiliatedFromCache(List ids, UUID userId, Map affiliatedResources, String entityType){ + List idsToResolve = new ArrayList<>(); + for (UUID id : ids){ + AffiliationCacheService.AffiliationCacheValue cacheValue = this.affiliationCacheService.lookup(this.affiliationCacheService.buildKey(userId, id, entityType)); + if (cacheValue != null) affiliatedResources.put(id, cacheValue.getAffiliatedResource()); + else idsToResolve.add(id); + } + return idsToResolve; + } + + private void ensureAffiliatedInCache(List idsToResolve, UUID userId, Map affiliatedResources, String entityType){ + for (UUID id : idsToResolve){ + AffiliatedResource affiliatedResource = affiliatedResources.getOrDefault(id, null); + if (affiliatedResource != null) { + AffiliationCacheService.AffiliationCacheValue cacheValue = new AffiliationCacheService.AffiliationCacheValue(userId, id, entityType, affiliatedResource); + this.affiliationCacheService.put(cacheValue); + } + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/common/validation/UuidValidator.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/common/validation/UuidValidator.java index f0512728c..8d8ab1008 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/common/validation/UuidValidator.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/common/validation/UuidValidator.java @@ -2,7 +2,6 @@ package gr.cite.annotation.common.validation; import gr.cite.annotation.convention.ConventionService; import gr.cite.annotation.errorcode.ErrorThesaurusProperties; -import gr.cite.annotation.integrationevent.inbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEvent; import gr.cite.tools.validation.specification.Specification; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.MessageSource; diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueInboxEntity.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueInboxEntity.java index 3b16254b7..4847bc080 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueInboxEntity.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueInboxEntity.java @@ -2,6 +2,7 @@ package gr.cite.annotation.data; import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.data.conventers.IsActiveConverter; +import gr.cite.annotation.data.conventers.QueueInboxStatusConverter; import gr.cite.annotation.data.types.JsonSQLType; import gr.cite.queueinbox.entity.QueueInbox; import gr.cite.queueinbox.entity.QueueInboxStatus; @@ -21,17 +22,17 @@ public class QueueInboxEntity implements QueueInbox { public static final String _id = "id"; - @Column(name = "\"queue\"", nullable = false, length = 50) + @Column(name = "\"queue\"", nullable = false, length = 200) private String queue; public static final String _queue = "queue"; - @Column(name = "\"exchange\"", nullable = false, length = 50) + @Column(name = "\"exchange\"", nullable = false, length = 200) private String exchange; public static final String _exchange = "exchange"; - @Column(name = "\"route\"", nullable = false, length = 50) + @Column(name = "\"route\"", nullable = false, length = 200) private String route; public static final String _route = "route"; @@ -62,11 +63,11 @@ public class QueueInboxEntity implements QueueInbox { public static final String _tenantId = "tenantId"; - @Column(name = "\"status\"", length = 50, nullable = false) - @Enumerated(EnumType.STRING) + @Column(name = "\"status\"", nullable = false) + @Convert(converter = QueueInboxStatusConverter.class) private QueueInboxStatus status; + public final static String _status = "status"; - public static final String _status = "status"; @Column(name = "\"created_at\"", nullable = false) private Instant createdAt; @@ -79,7 +80,7 @@ public class QueueInboxEntity implements QueueInbox { public static final String _updatedAt = "updatedAt"; - @Column(name = "\"is_active\"", length = 20, nullable = false) + @Column(name = "\"is_active\"", nullable = false) @Convert(converter = IsActiveConverter.class) private IsActive isActive; diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueOutboxEntity.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueOutboxEntity.java index bef7a6c94..b25ca4edb 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueOutboxEntity.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/QueueOutboxEntity.java @@ -2,6 +2,7 @@ package gr.cite.annotation.data; import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.data.conventers.IsActiveConverter; +import gr.cite.annotation.data.conventers.QueueOutboxNotifyStatusConverter; import gr.cite.queueoutbox.entity.QueueOutbox; import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; import jakarta.persistence.*; @@ -19,12 +20,12 @@ public class QueueOutboxEntity implements QueueOutbox { public static final String _id = "id"; - @Column(name = "\"exchange\"", nullable = false, length = 50) + @Column(name = "\"exchange\"", nullable = false, length = 200) private String exchange; public static final String _exchange = "exchange"; - @Column(name = "\"route\"", length = 50) + @Column(name = "\"route\"", length = 200) private String route; public static final String _route = "route"; @@ -39,11 +40,10 @@ public class QueueOutboxEntity implements QueueOutbox { public static final String _message = "message"; - @Column(name = "\"notify_status\"", length = 20, nullable = false) - @Enumerated(EnumType.STRING) + @Column(name = "\"notify_status\"", nullable = false) + @Convert(converter = QueueOutboxNotifyStatusConverter.class) private QueueOutboxNotifyStatus notifyStatus; - - public static final String _notifyStatus = "notifyStatus"; + public final static String _notifyStatus = "notifyStatus"; @Column(name = "\"retry_count\"", nullable = false) private Integer retryCount; diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/data/conventers/QueueInboxStatusConverter.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/conventers/QueueInboxStatusConverter.java new file mode 100644 index 000000000..2089859d8 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/conventers/QueueInboxStatusConverter.java @@ -0,0 +1,19 @@ +package gr.cite.annotation.data.conventers; + +import gr.cite.queueinbox.entity.QueueInboxStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class QueueInboxStatusConverter implements AttributeConverter { + @Override + public Short convertToDatabaseColumn(QueueInboxStatus value) { + if (value == null) throw new IllegalArgumentException("Value could not be null for: " + this.getClass().getSimpleName()); + return value.getValue(); + } + + @Override + public QueueInboxStatus convertToEntityAttribute(Short dbData) { + return QueueInboxStatus.of(dbData); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/data/conventers/QueueOutboxNotifyStatusConverter.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/conventers/QueueOutboxNotifyStatusConverter.java new file mode 100644 index 000000000..a356181d1 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/data/conventers/QueueOutboxNotifyStatusConverter.java @@ -0,0 +1,19 @@ +package gr.cite.annotation.data.conventers; + +import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class QueueOutboxNotifyStatusConverter implements AttributeConverter { + @Override + public Short convertToDatabaseColumn(QueueOutboxNotifyStatus value) { + if (value == null) throw new IllegalArgumentException("Value could not be null for: " + this.getClass().getSimpleName()); + return value.getValue(); + } + + @Override + public QueueOutboxNotifyStatus convertToEntityAttribute(Short dbData) { + return QueueOutboxNotifyStatus.of(dbData); + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/AppRabbitConfigurer.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/AppRabbitConfigurer.java index a9390e3b0..b4ec3b7d2 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/AppRabbitConfigurer.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/AppRabbitConfigurer.java @@ -36,7 +36,8 @@ public class AppRabbitConfigurer extends RabbitConfigurer { bindingItems.addAll(this.inboxProperties.getTenantTouchTopic()); bindingItems.addAll(this.inboxProperties.getUserRemovalTopic()); bindingItems.addAll(this.inboxProperties.getUserTouchTopic()); - bindingItems.addAll(this.inboxProperties.getAnnotationEntityTouchTopic()); + bindingItems.addAll(this.inboxProperties.getAnnotationEntitiesTouchTopic()); + bindingItems.addAll(this.inboxProperties.getAnnotationEntitiesRemovalTopic()); return new InboxBindings(bindingItems); } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxProperties.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxProperties.java index a2b133711..b165a3e49 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxProperties.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxProperties.java @@ -17,21 +17,23 @@ public class InboxProperties { private final List userTouchTopic; - private final List annotationEntityTouchTopic; + private final List annotationEntitiesTouchTopic; + private final List annotationEntitiesRemovalTopic; public InboxProperties( - String exchange, - List tenantRemovalTopic, - List tenantTouchTopic, - List userRemovalTopic, - List userTouchTopic, - List annotationEntityTouchTopic) { + String exchange, + List tenantRemovalTopic, + List tenantTouchTopic, + List userRemovalTopic, + List userTouchTopic, + List annotationEntitiesTouchTopic, List annotationEntitiesRemovalTopic) { this.exchange = exchange; this.tenantRemovalTopic = tenantRemovalTopic; this.tenantTouchTopic = tenantTouchTopic; this.userRemovalTopic = userRemovalTopic; this.userTouchTopic = userTouchTopic; - this.annotationEntityTouchTopic = annotationEntityTouchTopic; + this.annotationEntitiesTouchTopic = annotationEntitiesTouchTopic; + this.annotationEntitiesRemovalTopic = annotationEntitiesRemovalTopic; } public List getTenantRemovalTopic() { @@ -50,8 +52,12 @@ public class InboxProperties { return userTouchTopic; } - public List getAnnotationEntityTouchTopic() { - return annotationEntityTouchTopic; + public List getAnnotationEntitiesTouchTopic() { + return annotationEntitiesTouchTopic; + } + + public List getAnnotationEntitiesRemovalTopic() { + return annotationEntitiesRemovalTopic; } public String getExchange() { diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxRepositoryImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxRepositoryImpl.java index b3822c478..d37870bc0 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxRepositoryImpl.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxRepositoryImpl.java @@ -5,7 +5,8 @@ import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.scope.fake.FakeRequestScope; import gr.cite.annotation.data.QueueInboxEntity; import gr.cite.annotation.integrationevent.TrackedEvent; -import gr.cite.annotation.integrationevent.inbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEventHandler; +import gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval.AnnotationEntitiesRemovalIntegrationEventHandler; +import gr.cite.annotation.integrationevent.inbox.annotationentitiestouch.AnnotationEntitiesTouchedIntegrationEventHandler; import gr.cite.annotation.integrationevent.inbox.tenantremoval.TenantRemovalIntegrationEventHandler; import gr.cite.annotation.integrationevent.inbox.tenanttouch.TenantTouchedIntegrationEventHandler; import gr.cite.annotation.integrationevent.inbox.userremoval.UserRemovalIntegrationEventHandler; @@ -332,8 +333,10 @@ public class InboxRepositoryImpl implements InboxRepository { handler = this.applicationContext.getBean(UserRemovalIntegrationEventHandler.class); else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserTouchTopic())) handler = this.applicationContext.getBean(UserTouchedIntegrationEventHandler.class); - else if (this.routingKeyMatched(routingKey, this.inboxProperties.getAnnotationEntityTouchTopic())) - handler = this.applicationContext.getBean(AnnotationEntityTouchedIntegrationEventHandler.class); + else if (this.routingKeyMatched(routingKey, this.inboxProperties.getAnnotationEntitiesTouchTopic())) + handler = this.applicationContext.getBean(AnnotationEntitiesTouchedIntegrationEventHandler.class); + else if (this.routingKeyMatched(routingKey, this.inboxProperties.getAnnotationEntitiesRemovalTopic())) + handler = this.applicationContext.getBean(AnnotationEntitiesRemovalIntegrationEventHandler.class); else { logger.error("No handler found for message routing key '{}'. Discarding.", routingKey); handler = null; diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEvent.java new file mode 100644 index 000000000..6ecafb668 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEvent.java @@ -0,0 +1,70 @@ +package gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval; + +import gr.cite.annotation.common.validation.BaseValidator; +import gr.cite.annotation.common.validation.UuidValidator; +import gr.cite.annotation.convention.ConventionService; +import gr.cite.annotation.errorcode.ErrorThesaurusProperties; +import gr.cite.annotation.integrationevent.TrackedEvent; +import gr.cite.tools.validation.ValidatorFactory; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class AnnotationEntitiesRemovalIntegrationEvent extends TrackedEvent { + + private List entityIds; + + public static final String _entityIds = "entityIds"; + + public List getEntityIds() { + return entityIds; + } + + public void setEntityIds(List entityIds) { + this.entityIds = entityIds; + } + + @Component(AnnotationEntitiesRemovalIntegrationEventValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class AnnotationEntitiesRemovalIntegrationEventValidator extends BaseValidator { + + public static final String ValidatorName = "AnnotationEntitiesRemovalIntegrationEventValidator"; + + private final MessageSource messageSource; + + private final ValidatorFactory validatorFactory; + + protected AnnotationEntitiesRemovalIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) { + super(conventionService, errors); + this.messageSource = messageSource; + this.validatorFactory = validatorFactory; + } + + @Override + protected Class modelClass() { + return AnnotationEntitiesRemovalIntegrationEvent.class; + } + + @Override + protected List specifications(AnnotationEntitiesRemovalIntegrationEvent item) { + return Arrays.asList( + this.spec() + .must(() -> !this.isListNullOrEmpty(item.getEntityIds())) + .failOn(AnnotationEntitiesRemovalIntegrationEvent._entityIds).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationEntitiesRemovalIntegrationEvent._entityIds}, LocaleContextHolder.getLocale())), + this.navSpec() + .iff(() -> !this.isListNullOrEmpty(item.getEntityIds())) + .on(AnnotationEntitiesRemovalIntegrationEvent._entityIds) + .over(item.getEntityIds()) + .using((i) -> this.validatorFactory.validator(UuidValidator.class)) + ); + } + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEventHandler.java new file mode 100644 index 000000000..3c1fa0864 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEventHandler.java @@ -0,0 +1,7 @@ +package gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval; + +import gr.cite.annotation.integrationevent.inbox.IntegrationEventHandler; + +public interface AnnotationEntitiesRemovalIntegrationEventHandler extends IntegrationEventHandler { + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEventHandlerImpl.java similarity index 58% rename from annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java rename to annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEventHandlerImpl.java index 034ea668e..46f94ffe4 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiesremoval/AnnotationEntitiesRemovalIntegrationEventHandlerImpl.java @@ -1,4 +1,4 @@ -package gr.cite.annotation.integrationevent.inbox.annotationentitytouch; +package gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval; import gr.cite.annotation.audit.AuditableAction; import gr.cite.annotation.common.JsonHandlingService; @@ -11,6 +11,7 @@ import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties; import gr.cite.annotation.query.EntityUserQuery; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.validation.ValidatorFactory; @@ -24,17 +25,13 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import java.time.Instant; -import java.util.AbstractMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) -public class AnnotationEntityTouchedIntegrationEventHandlerImpl implements AnnotationEntityTouchedIntegrationEventHandler { +public class AnnotationEntitiesRemovalIntegrationEventHandlerImpl implements AnnotationEntitiesRemovalIntegrationEventHandler { - private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationEntityTouchedIntegrationEventHandlerImpl.class)); + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationEntitiesRemovalIntegrationEventHandlerImpl.class)); private final JsonHandlingService jsonHandlingService; @@ -44,7 +41,7 @@ public class AnnotationEntityTouchedIntegrationEventHandlerImpl implements Annot private final QueryFactory queryFactory; - public AnnotationEntityTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, ApplicationContext applicationContext, QueryFactory queryFactory) { + public AnnotationEntitiesRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, ApplicationContext applicationContext, QueryFactory queryFactory) { this.jsonHandlingService = jsonHandlingService; this.validatorFactory = validatorFactory; this.applicationContext = applicationContext; @@ -53,66 +50,41 @@ public class AnnotationEntityTouchedIntegrationEventHandlerImpl implements Annot @Override public EventProcessingStatus handle(IntegrationEventProperties properties, String message) { - AnnotationEntityTouchedIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(AnnotationEntityTouchedIntegrationEvent.class, message); + AnnotationEntitiesRemovalIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(AnnotationEntitiesRemovalIntegrationEvent.class, message); if (event == null) return EventProcessingStatus.Error; - logger.debug("Handling {}", AnnotationEntityTouchedIntegrationEvent.class.getSimpleName()); + logger.debug("Handling {}", AnnotationEntitiesRemovalIntegrationEvent.class.getSimpleName()); - this.validatorFactory.validator(AnnotationEntityTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEventValidator.class).validateForce(event); + this.validatorFactory.validator(AnnotationEntitiesRemovalIntegrationEvent.AnnotationEntitiesRemovalIntegrationEventValidator.class).validateForce(event); EntityManager entityManager = null; EntityTransaction transaction = null; try (FakeRequestScope ignored = new FakeRequestScope()) { try { -// QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); -// TenantScope scope = this.applicationContext.getBean(TenantScope.class); -// if (scope.isMultitenant() && event.getTenant() != null) { -// TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(event.getTenant()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); -// if (tenant == null) { -// logger.error("missing tenant from event message"); -// return EventProcessingStatus.Error; -// } -// scope.setTenant(event.getTenant(), tenant.getCode()); -// } else if (scope.isMultitenant()) { -// logger.error("missing tenant from event message"); -// return EventProcessingStatus.Error; -// } -// -// ValidationService validator = this.applicationContext.getBean(ValidationService.class); -// validator.validateForce(model); - CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); currentPrincipalResolver.push(InboxPrincipal.build(properties)); EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); entityManager = entityManagerFactory.createEntityManager(); + DeleterFactory deleterFactory = this.applicationContext.getBean(DeleterFactory.class); + transaction = entityManager.getTransaction(); transaction.begin(); try { + EntityUserQuery entityUserQuery = this.queryFactory.query(EntityUserQuery.class); - List associatedUsersEntities = entityUserQuery - .entityIds(event.getEntityId()) + List items = entityUserQuery + .entityIds(event.getEntityIds()) .isActive(IsActive.Active) .collect(); - List associatedUsers = associatedUsersEntities.stream() - .map(EntityUserEntity::getUserId) - .toList(); - for (UUID user : event.getUserIds()) { - if (!associatedUsers.contains(user)) { - EntityUserEntity entityUserEntity = new EntityUserEntity(); - entityUserEntity.setId(UUID.randomUUID()); - entityUserEntity.setEntityId(event.getEntityId()); - entityUserEntity.setUserId(user); - entityUserEntity.setCreatedAt(Instant.now()); - entityUserEntity.setUpdatedAt(Instant.now()); - entityUserEntity.setIsActive(IsActive.Active); + - entityManager.persist(entityUserEntity); - } - } + deleterFactory.deleter(gr.cite.EntityUser.model.deleter.EntityUserDeleter.class).delete(items); + + entityManager.flush(); AuditService auditService = this.applicationContext.getBean(AuditService.class); diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEvent.java new file mode 100644 index 000000000..a8facce21 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEvent.java @@ -0,0 +1,138 @@ +package gr.cite.annotation.integrationevent.inbox.annotationentitiestouch; + +import gr.cite.annotation.common.validation.BaseValidator; +import gr.cite.annotation.common.validation.UuidValidator; +import gr.cite.annotation.convention.ConventionService; +import gr.cite.annotation.errorcode.ErrorThesaurusProperties; +import gr.cite.annotation.integrationevent.TrackedEvent; +import gr.cite.tools.validation.ValidatorFactory; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class AnnotationEntitiesTouchedIntegrationEvent extends TrackedEvent { + + private List events; + + public static final String _events = "events"; + + public List getEvents() { + return events; + } + + public void setEvents(List events) { + this.events = events; + } + + @Component(AnnotationEntitiesTouchedIntegrationEventValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class AnnotationEntitiesTouchedIntegrationEventValidator extends BaseValidator { + + public static final String ValidatorName = "AnnotationEntitiesTouchedIntegrationEventValidator"; + + private final MessageSource messageSource; + + private final ValidatorFactory validatorFactory; + + protected AnnotationEntitiesTouchedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) { + super(conventionService, errors); + this.messageSource = messageSource; + this.validatorFactory = validatorFactory; + } + + @Override + protected Class modelClass() { + return AnnotationEntitiesTouchedIntegrationEvent.class; + } + + @Override + protected List specifications(AnnotationEntitiesTouchedIntegrationEvent item) { + return Arrays.asList( + this.spec() + .must(() -> !this.isListNullOrEmpty(item.getEvents())) + .failOn(AnnotationEntitiesTouchedIntegrationEvent._events).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationEntitiesTouchedIntegrationEvent._events}, LocaleContextHolder.getLocale())), + this.navSpec() + .iff(() -> !this.isListNullOrEmpty(item.getEvents())) + .on(AnnotationEntitiesTouchedIntegrationEvent._events) + .over(item.getEvents()) + .using((i) -> this.validatorFactory.validator(AnnotationEntityTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEventValidator.class)) + ); + } + } + + public static class AnnotationEntityTouchedIntegrationEvent { + + private UUID entityId; + + public static final String _entityId = "entityId"; + + private List userIds; + + public static final String _userIds = "userIds"; + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + @Component(AnnotationEntityTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEventValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class AnnotationEntityTouchedIntegrationEventValidator extends BaseValidator { + + public static final String ValidatorName = "AnnotationEntityTouchedIntegrationEventValidator"; + + private final MessageSource messageSource; + + private final ValidatorFactory validatorFactory; + + protected AnnotationEntityTouchedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) { + super(conventionService, errors); + this.messageSource = messageSource; + this.validatorFactory = validatorFactory; + } + + @Override + protected Class modelClass() { + return AnnotationEntityTouchedIntegrationEvent.class; + } + + @Override + protected List specifications(AnnotationEntityTouchedIntegrationEvent item) { + return Arrays.asList( + this.spec() + .iff(() -> !this.isNull(item.getEntityId())) + .must(() -> this.isValidGuid(item.getEntityId())) + .failOn(AnnotationEntityTouchedIntegrationEvent._entityId).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationEntityTouchedIntegrationEvent._entityId}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isListNullOrEmpty(item.getUserIds())) + .failOn(AnnotationEntityTouchedIntegrationEvent._userIds).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationEntityTouchedIntegrationEvent._userIds}, LocaleContextHolder.getLocale())), + this.navSpec() + .iff(() -> !this.isListNullOrEmpty(item.getUserIds())) + .on(AnnotationEntityTouchedIntegrationEvent._userIds) + .over(item.getUserIds()) + .using((i) -> this.validatorFactory.validator(UuidValidator.class)) + ); + } + } + + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEventHandler.java similarity index 56% rename from annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java rename to annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEventHandler.java index 4f771def3..ef57c6e2c 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEventHandler.java @@ -1,7 +1,7 @@ -package gr.cite.annotation.integrationevent.inbox.annotationentitytouch; +package gr.cite.annotation.integrationevent.inbox.annotationentitiestouch; import gr.cite.annotation.integrationevent.inbox.IntegrationEventHandler; -public interface AnnotationEntityTouchedIntegrationEventHandler extends IntegrationEventHandler { +public interface AnnotationEntitiesTouchedIntegrationEventHandler extends IntegrationEventHandler { } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEventHandlerImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..b63d2db44 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitiestouch/AnnotationEntitiesTouchedIntegrationEventHandlerImpl.java @@ -0,0 +1,141 @@ +package gr.cite.annotation.integrationevent.inbox.annotationentitiestouch; + +import gr.cite.annotation.audit.AuditableAction; +import gr.cite.annotation.common.JsonHandlingService; +import gr.cite.annotation.common.enums.IsActive; +import gr.cite.annotation.common.scope.fake.FakeRequestScope; +import gr.cite.annotation.data.EntityUserEntity; +import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus; +import gr.cite.annotation.integrationevent.inbox.InboxPrincipal; +import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties; +import gr.cite.annotation.query.EntityUserQuery; +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.validation.ValidatorFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.OptimisticLockException; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements AnnotationEntitiesTouchedIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationEntitiesTouchedIntegrationEventHandlerImpl.class)); + + private final JsonHandlingService jsonHandlingService; + + private final ValidatorFactory validatorFactory; + + private final ApplicationContext applicationContext; + + private final QueryFactory queryFactory; + + public AnnotationEntitiesTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, ApplicationContext applicationContext, QueryFactory queryFactory) { + this.jsonHandlingService = jsonHandlingService; + this.validatorFactory = validatorFactory; + this.applicationContext = applicationContext; + this.queryFactory = queryFactory; + } + + @Override + public EventProcessingStatus handle(IntegrationEventProperties properties, String message) { + AnnotationEntitiesTouchedIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(AnnotationEntitiesTouchedIntegrationEvent.class, message); + if (event == null) + return EventProcessingStatus.Error; + + logger.debug("Handling {}", AnnotationEntitiesTouchedIntegrationEvent.class.getSimpleName()); + + this.validatorFactory.validator(AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntitiesTouchedIntegrationEventValidator.class).validateForce(event); + + EntityManager entityManager = null; + EntityTransaction transaction = null; + try (FakeRequestScope ignored = new FakeRequestScope()) { + try { + CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); + currentPrincipalResolver.push(InboxPrincipal.build(properties)); + + EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + DeleterFactory deleterFactory = this.applicationContext.getBean(DeleterFactory.class); + + transaction = entityManager.getTransaction(); + transaction.begin(); + + try { + for (AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent entityEvent : event.getEvents()) { + + EntityUserQuery entityUserQuery = this.queryFactory.query(EntityUserQuery.class); + List items = entityUserQuery + .entityIds(entityEvent.getEntityId()) + .isActive(IsActive.Active) + .collect(); + List updatedCreatedIds = new ArrayList<>(); + for (UUID user : entityEvent.getUserIds()) { + EntityUserEntity data = items.stream().filter(x -> x.getUserId().equals(user)).findFirst().orElse(null); + if (data == null) { + data = new EntityUserEntity(); + data.setId(UUID.randomUUID()); + data.setEntityId(entityEvent.getEntityId()); + data.setUserId(user); + data.setCreatedAt(Instant.now()); + data.setUpdatedAt(Instant.now()); + data.setIsActive(IsActive.Active); + + entityManager.persist(data); + } + updatedCreatedIds.add(data.getId()); + } + + List toDelete = items.stream().filter(x -> updatedCreatedIds.stream().noneMatch(y -> y.equals(x.getId()))).collect(Collectors.toList()); + deleterFactory.deleter(gr.cite.EntityUser.model.deleter.EntityUserDeleter.class).delete(toDelete); + + entityManager.flush(); + } + + AuditService auditService = this.applicationContext.getBean(AuditService.class); + + auditService.track(AuditableAction.User_Persist, Map.ofEntries( + new AbstractMap.SimpleEntry("model", event) + )); + + transaction.commit(); + } catch (Exception e) { + transaction.rollback(); + throw e; + } finally { + currentPrincipalResolver.pop(); + } + } catch (OptimisticLockException ex) { + // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working + logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); + if (transaction != null) + transaction.rollback(); + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + if (transaction != null) + transaction.rollback(); + } finally { + if (entityManager != null) + entityManager.close(); + } + } catch (Exception ex) { + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } + return EventProcessingStatus.Success; + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEvent.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEvent.java deleted file mode 100644 index f5b0de33c..000000000 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEvent.java +++ /dev/null @@ -1,86 +0,0 @@ -package gr.cite.annotation.integrationevent.inbox.annotationentitytouch; - -import gr.cite.annotation.common.validation.BaseValidator; -import gr.cite.annotation.common.validation.UuidValidator; -import gr.cite.annotation.convention.ConventionService; -import gr.cite.annotation.errorcode.ErrorThesaurusProperties; -import gr.cite.annotation.integrationevent.TrackedEvent; -import gr.cite.tools.validation.ValidatorFactory; -import gr.cite.tools.validation.specification.Specification; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.MessageSource; -import org.springframework.context.annotation.Scope; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.stereotype.Component; - -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -public class AnnotationEntityTouchedIntegrationEvent extends TrackedEvent { - - private UUID entityId; - - public static final String _entityId = "entityId"; - - private List userIds; - - public static final String _userIds = "userIds"; - - public UUID getEntityId() { - return entityId; - } - - public void setEntityId(UUID entityId) { - this.entityId = entityId; - } - - public List getUserIds() { - return userIds; - } - - public void setUserIds(List userIds) { - this.userIds = userIds; - } - - @Component(AnnotationEntityTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEventValidator.ValidatorName) - @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public static class AnnotationEntityTouchedIntegrationEventValidator extends BaseValidator { - - public static final String ValidatorName = "AnnotationEntityTouchedIntegrationEventValidator"; - - private final MessageSource messageSource; - - private final ValidatorFactory validatorFactory; - - protected AnnotationEntityTouchedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) { - super(conventionService, errors); - this.messageSource = messageSource; - this.validatorFactory = validatorFactory; - } - - @Override - protected Class modelClass() { - return AnnotationEntityTouchedIntegrationEvent.class; - } - - @Override - protected List specifications(AnnotationEntityTouchedIntegrationEvent item) { - return Arrays.asList( - this.spec() - .iff(() -> !this.isNull(item.getEntityId())) - .must(() -> this.isValidGuid(item.getEntityId())) - .failOn(AnnotationEntityTouchedIntegrationEvent._entityId).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationEntityTouchedIntegrationEvent._entityId}, LocaleContextHolder.getLocale())), - this.spec() - .must(() -> !this.isListNullOrEmpty(item.getUserIds())) - .failOn(AnnotationEntityTouchedIntegrationEvent._userIds).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationEntityTouchedIntegrationEvent._userIds}, LocaleContextHolder.getLocale())), - this.navSpec() - .iff(() -> !this.isListNullOrEmpty(item.getUserIds())) - .on(AnnotationEntityTouchedIntegrationEvent._userIds) - .over(item.getUserIds()) - .using((i) -> this.validatorFactory.validator(UuidValidator.class)) - ); - } - } - -} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/Annotation.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/Annotation.java index 96a87126a..363f8dfac 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/Annotation.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/Annotation.java @@ -7,6 +7,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import java.time.Instant; +import java.util.List; import java.util.UUID; public class Annotation { @@ -67,6 +68,13 @@ public class Annotation { public static final String _isActive = "isActive"; + private String hash; + + public static final String _hash = "hash"; + + private List authorizationFlags; + public static final String _authorizationFlags = "authorizationFlags"; + public UUID getId() { return id; } @@ -179,4 +187,19 @@ public class Annotation { this.isActive = isActive; } + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public List getAuthorizationFlags() { + return authorizationFlags; + } + + public void setAuthorizationFlags(List authorizationFlags) { + this.authorizationFlags = authorizationFlags; + } } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/EntityUser.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/EntityUser.java new file mode 100644 index 000000000..ff80f14de --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/EntityUser.java @@ -0,0 +1,94 @@ +package gr.cite.annotation.model; + +import gr.cite.annotation.common.enums.AnnotationProtectionType; +import gr.cite.annotation.common.enums.IsActive; + +import java.time.Instant; +import java.util.UUID; + +public class EntityUser { + + private UUID id; + + public static final String _id = "id"; + + private UUID entityId; + + public static final String _entityId = "entityId"; + + private User user; + + public static final String _user = "user"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + private Instant updatedAt; + + public static final String _updatedAt = "updatedAt"; + + private IsActive isActive; + + public static final String _isActive = "isActive"; + + private String hash; + + public static final String _hash = "hash"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public IsActive getIsActive() { + return isActive; + } + + public void setIsActive(IsActive isActive) { + this.isActive = isActive; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/AnnotationBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/AnnotationBuilder.java index de611d3ce..2e57b962f 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/AnnotationBuilder.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/AnnotationBuilder.java @@ -1,12 +1,15 @@ package gr.cite.annotation.model.builder; +import gr.cite.annotation.authorization.AffiliatedResource; import gr.cite.annotation.authorization.AuthorizationFlags; +import gr.cite.annotation.authorization.authorizationcontentresolver.AuthorizationContentResolver; import gr.cite.annotation.convention.ConventionService; import gr.cite.annotation.data.AnnotationEntity; import gr.cite.annotation.model.Annotation; import gr.cite.annotation.model.AnnotationAuthor; import gr.cite.annotation.model.User; import gr.cite.annotation.query.UserQuery; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; @@ -29,13 +32,17 @@ public class AnnotationBuilder extends BaseBuilder private final QueryFactory queryFactory; private final BuilderFactory builderFactory; + private final AuthorizationContentResolver authorizationContentResolver; + private final AuthorizationService authorizationService; private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); - public AnnotationBuilder(ConventionService conventionService, QueryFactory queryFactory, BuilderFactory builderFactory) { + public AnnotationBuilder(ConventionService conventionService, QueryFactory queryFactory, BuilderFactory builderFactory, AuthorizationContentResolver authorizationContentResolver, AuthorizationService authorizationService) { super(conventionService, new LoggerService(LoggerFactory.getLogger(AnnotationBuilder.class))); this.queryFactory = queryFactory; this.builderFactory = builderFactory; + this.authorizationContentResolver = authorizationContentResolver; + this.authorizationService = authorizationService; } public AnnotationBuilder authorize(EnumSet values) { @@ -51,42 +58,32 @@ public class AnnotationBuilder extends BaseBuilder return new ArrayList<>(); List models = new ArrayList<>(); + if (data == null) return models; FieldSet authorFields = fields.extractPrefixed(this.asPrefix(Annotation._author)); Map authorsMap = this.collectAuthors(authorFields, data); + Set authorizationFlags = this.extractAuthorizationFlags(fields, Annotation._authorizationFlags, this.authorizationContentResolver.getPermissionNames()); + Map affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.annotationsAffiliation(data.stream().map(AnnotationEntity::getId).toList()); + - if (data == null) - return models; for (AnnotationEntity d : data) { Annotation m = new Annotation(); - if (fields.hasField(this.asIndexer(Annotation._id))) - m.setId(d.getId()); - if (fields.hasField(this.asIndexer(Annotation._entityId))) - m.setEntityId(d.getEntityId()); - if (fields.hasField(this.asIndexer(Annotation._entityType))) - m.setEntityType(d.getEntityType()); - if (fields.hasField(this.asIndexer(Annotation._anchor))) - m.setAnchor(d.getAnchor()); - if (fields.hasField(this.asIndexer(Annotation._payload))) - m.setPayload(d.getPayload()); - if (fields.hasField(this.asIndexer(Annotation._subjectId))) - m.setSubjectId(d.getSubjectId()); - if (authorsMap != null && authorsMap.containsKey(d.getSubjectId())) - m.setAuthor(authorsMap.get(d.getSubjectId())); - if (fields.hasField(this.asIndexer(Annotation._threadId))) - m.setThreadId(d.getThreadId()); - if (fields.hasField(this.asIndexer(Annotation._parentId))) - m.setParentId(d.getParentId()); - if (fields.hasField(this.asIndexer(Annotation._protectionType))) - m.setProtectionType(d.getProtectionType()); - if (fields.hasField(this.asIndexer(Annotation._timeStamp))) - m.setTimeStamp(d.getTimeStamp()); - if (fields.hasField(this.asIndexer(Annotation._createdAt))) - m.setCreatedAt(d.getCreatedAt()); - if (fields.hasField(this.asIndexer(Annotation._updatedAt))) - m.setUpdatedAt(d.getUpdatedAt()); - if (fields.hasField(this.asIndexer(Annotation._isActive))) - m.setIsActive(d.getIsActive()); + if (fields.hasField(this.asIndexer(Annotation._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(Annotation._entityId))) m.setEntityId(d.getEntityId()); + if (fields.hasField(this.asIndexer(Annotation._entityType))) m.setEntityType(d.getEntityType()); + if (fields.hasField(this.asIndexer(Annotation._anchor))) m.setAnchor(d.getAnchor()); + if (fields.hasField(this.asIndexer(Annotation._payload))) m.setPayload(d.getPayload()); + if (fields.hasField(this.asIndexer(Annotation._subjectId))) m.setSubjectId(d.getSubjectId()); + if (fields.hasField(this.asIndexer(Annotation._threadId))) m.setThreadId(d.getThreadId()); + if (fields.hasField(this.asIndexer(Annotation._parentId))) m.setParentId(d.getParentId()); + if (fields.hasField(this.asIndexer(Annotation._protectionType))) m.setProtectionType(d.getProtectionType()); + if (fields.hasField(this.asIndexer(Annotation._timeStamp))) m.setTimeStamp(d.getTimeStamp()); + if (fields.hasField(this.asIndexer(Annotation._createdAt))) m.setCreatedAt(d.getCreatedAt()); + if (fields.hasField(this.asIndexer(Annotation._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); + if (fields.hasField(this.asIndexer(Annotation._isActive))) m.setIsActive(d.getIsActive()); + if (fields.hasField(this.asIndexer(Annotation._hash))) m.setHash(this.hashValue(d.getUpdatedAt())); + if (!authorFields.isEmpty() && authorsMap != null && authorsMap.containsKey(d.getSubjectId())) m.setAuthor(authorsMap.get(d.getSubjectId())); + if (affiliatedResourceMap != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null))); models.add(m); } this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); @@ -103,7 +100,7 @@ public class AnnotationBuilder extends BaseBuilder .map(AnnotationEntity::getSubjectId) .distinct() .collect(Collectors.toList()); - UserQuery query = this.queryFactory.query(UserQuery.class).authorize(this.authorize).ids(userIds); + UserQuery query = this.queryFactory.query(UserQuery.class).ids(userIds); Map> users = this.builderFactory.builder(UserBuilder.class).authorize(this.authorize).asMasterKey(query, clone, User::getId); users.forEach((key, val) -> { diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/BaseBuilder.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/BaseBuilder.java index a4b24163c..00f27ee18 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/BaseBuilder.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/builder/BaseBuilder.java @@ -1,6 +1,8 @@ package gr.cite.annotation.model.builder; +import gr.cite.annotation.authorization.AffiliatedResource; import gr.cite.annotation.convention.ConventionService; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.Builder; import gr.cite.tools.data.query.QueryBase; import gr.cite.tools.exception.MyApplicationException; @@ -92,4 +94,25 @@ public abstract class BaseBuilder implements Builder { return this.conventionService.asIndexer(names); } + protected Set extractAuthorizationFlags(FieldSet fields, String propertyName, List permissionNames){ + if (fields == null) return new HashSet<>(); + if (permissionNames == null) return new HashSet<>(); + + FieldSet authorizationFlags = fields.extractPrefixed(this.asPrefix(propertyName)); + List permissions = new ArrayList<>(); + for (String fieldValue : authorizationFlags.getFields()) permissions.addAll(permissionNames.stream().filter(x-> x.equalsIgnoreCase(fieldValue)).toList()); + return new HashSet<>(permissions); + } + + protected List evaluateAuthorizationFlags(AuthorizationService authorizationService, Set authorizationFlags, AffiliatedResource affiliatedResource) { + List allowed = new ArrayList<>(); + if (authorizationFlags == null) return allowed; + if (authorizationService == null) return allowed; + + for (String permission : authorizationFlags) { + Boolean isAllowed = affiliatedResource == null ? authorizationService.authorize(permission) : authorizationService.authorizeAtLeastOne(List.of(affiliatedResource), permission); + if (isAllowed) allowed.add(permission); + } + return allowed; + } } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationAuthorCensor.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationAuthorCensor.java new file mode 100644 index 000000000..27891f289 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationAuthorCensor.java @@ -0,0 +1,38 @@ +package gr.cite.annotation.model.censorship; + +import gr.cite.annotation.authorization.Permission; +import gr.cite.annotation.convention.ConventionService; +import gr.cite.annotation.model.Annotation; +import gr.cite.commons.web.authz.service.AuthorizationService; +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.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class AnnotationAuthorCensor extends BaseCensor{ + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationAuthorCensor.class)); + + protected final AuthorizationService authService; + + public AnnotationAuthorCensor(ConventionService conventionService, AuthorizationService authService) { + super(conventionService); + this.authService = authService; + } + + public void censor(FieldSet fields, UUID userId) { + logger.debug(new DataLogEntry("censoring fields", fields)); + if (fields == null || fields.isEmpty()) + return; + + this.authService.authorizeForce(Permission.BrowseAnnotation, Permission.DeferredAffiliation); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java index 538074557..c7ea75c0f 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java @@ -2,7 +2,9 @@ package gr.cite.annotation.model.censorship; import gr.cite.annotation.authorization.Permission; import gr.cite.annotation.convention.ConventionService; +import gr.cite.annotation.model.Annotation; import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.tools.data.censor.CensorFactory; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.DataLogEntry; import gr.cite.tools.logging.LoggerService; @@ -21,9 +23,12 @@ public class AnnotationCensor extends BaseCensor{ protected final AuthorizationService authService; - public AnnotationCensor(ConventionService conventionService, AuthorizationService authService) { + protected final CensorFactory censorFactory; + + public AnnotationCensor(ConventionService conventionService, AuthorizationService authService, CensorFactory censorFactory) { super(conventionService); this.authService = authService; + this.censorFactory = censorFactory; } public void censor(FieldSet fields, UUID userId) { @@ -31,7 +36,10 @@ public class AnnotationCensor extends BaseCensor{ if (fields == null || fields.isEmpty()) return; - this.authService.authorizeForce(Permission.BrowseAnnotation); + this.authService.authorizeForce(Permission.BrowseAnnotation, Permission.DeferredAffiliation); + + FieldSet authorFields = fields.extractPrefixed(this.asIndexerPrefix(Annotation._author)); + this.censorFactory.censor(AnnotationAuthorCensor.class).censor(authorFields, userId); } } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/deleter/EntityUserDeleter.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/deleter/EntityUserDeleter.java new file mode 100644 index 000000000..3da0db0bb --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/deleter/EntityUserDeleter.java @@ -0,0 +1,74 @@ +package gr.cite.EntityUser.model.deleter; + +import gr.cite.annotation.common.enums.IsActive; +import gr.cite.annotation.data.EntityUserEntity; +import gr.cite.annotation.data.TenantScopedEntityManager; +import gr.cite.annotation.query.EntityUserQuery; +import gr.cite.tools.data.deleter.Deleter; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class EntityUserDeleter implements Deleter { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(EntityUserDeleter.class)); + + private final TenantScopedEntityManager entityManager; + + private final QueryFactory queryFactory; + + @Autowired + public EntityUserDeleter( + TenantScopedEntityManager entityManager, + QueryFactory queryFactory + ) { + this.entityManager = entityManager; + this.queryFactory = queryFactory; + } + + public void deleteAndSaveByIds(List ids) throws InvalidApplicationException { + logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids)); + List data = this.queryFactory.query(EntityUserQuery.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 (EntityUserEntity item : data) { + logger.trace("deleting item {}", item); + item.setIsActive(IsActive.Inactive); + item.setUpdatedAt(now); + logger.trace("updating item"); + this.entityManager.merge(item); + logger.trace("updated item"); + } + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java index 8378c4f16..f217b462c 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java @@ -1,11 +1,16 @@ package gr.cite.annotation.query; import gr.cite.annotation.authorization.AuthorizationFlags; +import gr.cite.annotation.authorization.Permission; import gr.cite.annotation.common.enums.AnnotationProtectionType; import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.scope.user.UserScope; import gr.cite.annotation.data.AnnotationEntity; +import gr.cite.annotation.data.EntityUserEntity; import gr.cite.annotation.model.Annotation; +import gr.cite.annotation.model.EntityUser; +import gr.cite.annotation.query.utils.BuildSubQueryInput; +import gr.cite.annotation.query.utils.QueryUtilsService; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.query.FieldResolver; import gr.cite.tools.data.query.QueryBase; @@ -13,6 +18,7 @@ import gr.cite.tools.data.query.QueryContext; import jakarta.persistence.Tuple; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Subquery; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -44,11 +50,14 @@ public class AnnotationQuery extends QueryBase { private final UserScope userScope; + private final QueryUtilsService queryUtilsService; + private final AuthorizationService authService; - public AnnotationQuery(UserScope userScope, AuthorizationService authService) { + public AnnotationQuery(UserScope userScope, QueryUtilsService queryUtilsService, AuthorizationService authService) { this.userScope = userScope; - this.authService = authService; + this.queryUtilsService = queryUtilsService; + this.authService = authService; } public AnnotationQuery like(String value) { @@ -161,6 +170,11 @@ public class AnnotationQuery extends QueryBase { return this; } + public AnnotationQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + @Override protected Boolean isFalseQuery() { return this.isEmpty(this.ids) || this.isEmpty(this.excludedIds) || this.isEmpty(this.isActives) || this.isEmpty(this.entityIds); @@ -170,7 +184,44 @@ public class AnnotationQuery extends QueryBase { protected Class entityClass() { return AnnotationEntity.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.BrowseAnnotation)) return null; + UUID userId = null; + if (this.authorize.contains(AuthorizationFlags.Associated)) userId = this.userScope.getUserIdSafe(); + if (this.authorize.contains(AuthorizationFlags.Owner)) userId = this.userScope.getUserIdSafe(); + List predicates = new ArrayList<>(); + if (userId != null ) { + UUID finalUserId = userId; + Subquery subquery = this.queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(new BuildSubQueryInput.Builder<>(EntityUserEntity.class, UUID.class) + .query(queryContext.Query) + .criteriaBuilder(queryContext.CriteriaBuilder) + .keyPathFunc((subQueryRoot) -> subQueryRoot.get(EntityUserEntity._entityId)) + .filterFunc((subQueryRoot, cb) -> + cb.and( + cb.equal(subQueryRoot.get(EntityUserEntity._userId), finalUserId), + cb.equal(subQueryRoot.get(EntityUserEntity._isActive), IsActive.Active) + ) + ) + )); + predicates.add(queryContext.CriteriaBuilder.or( + queryContext.CriteriaBuilder.in(queryContext.Root.get(AnnotationEntity._subjectId)).value(userId), + queryContext.CriteriaBuilder.and( + queryContext.CriteriaBuilder.equal(queryContext.Root.get(AnnotationEntity._protectionType), AnnotationProtectionType.EntityAccessors), + queryContext.CriteriaBuilder.in(queryContext.Root.get(AnnotationEntity._entityId)).value(subquery) + ))); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } + @Override protected Predicate applyFilters(QueryContext queryContext) { List predicates = new ArrayList<>(); diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/EntityUserQuery.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/EntityUserQuery.java index 2c5c36673..6b309a7cc 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/EntityUserQuery.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/EntityUserQuery.java @@ -1,8 +1,16 @@ package gr.cite.annotation.query; import gr.cite.annotation.authorization.AuthorizationFlags; +import gr.cite.annotation.authorization.Permission; +import gr.cite.annotation.common.enums.AnnotationProtectionType; import gr.cite.annotation.common.enums.IsActive; +import gr.cite.annotation.common.scope.user.UserScope; +import gr.cite.annotation.data.AnnotationEntity; import gr.cite.annotation.data.EntityUserEntity; +import gr.cite.annotation.model.EntityUser; +import gr.cite.annotation.query.utils.BuildSubQueryInput; +import gr.cite.annotation.query.utils.QueryUtilsService; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.query.FieldResolver; import gr.cite.tools.data.query.QueryBase; import gr.cite.tools.data.query.QueryContext; @@ -12,6 +20,7 @@ import jakarta.persistence.criteria.Predicate; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; +import java.time.Instant; import java.util.*; @Component @@ -20,11 +29,20 @@ public class EntityUserQuery extends QueryBase { private Collection ids, entityIds, userIds; - private Collection isActives; + private Collection isActives;; + + private final AuthorizationService authService; + + private final UserScope userScope; private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); - public EntityUserQuery ids(UUID value) { + public EntityUserQuery(AuthorizationService authService, UserScope userScope) { + this.authService = authService; + this.userScope = userScope; + } + + public EntityUserQuery ids(UUID value) { this.ids = List.of(value); return this; } @@ -98,6 +116,26 @@ public class EntityUserQuery extends QueryBase { protected Class entityClass() { return EntityUserEntity.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.BrowseAnnotation)) return null; + UUID userId = null; + if (this.authorize.contains(AuthorizationFlags.Associated)) userId = this.userScope.getUserIdSafe(); + if (this.authorize.contains(AuthorizationFlags.Owner)) userId = this.userScope.getUserIdSafe(); + + List predicates = new ArrayList<>(); + if (userId != null ) { + predicates.add( queryContext.CriteriaBuilder.in(queryContext.Root.get(EntityUserEntity._userId)).value(userId)); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } + @Override protected Predicate applyFilters(QueryContext queryContext) { @@ -136,11 +174,35 @@ public class EntityUserQuery extends QueryBase { @Override protected String fieldNameOf(FieldResolver item) { - return null; + if (item.match(EntityUser._id)) + return EntityUserEntity._id; + else if (item.match(EntityUser._entityId)) + return EntityUserEntity._entityId; + else if (item.match(EntityUser._user)) + return EntityUserEntity._userId; + else if (item.prefix(EntityUser._user)) + return EntityUserEntity._userId; + else if (item.match(EntityUser._createdAt)) + return EntityUserEntity._createdAt; + else if (item.match(EntityUser._updatedAt)) + return EntityUserEntity._updatedAt; + else if (item.match(EntityUser._hash)) + return EntityUserEntity._updatedAt; + else if (item.match(EntityUser._isActive)) + return EntityUserEntity._isActive; + else + return null; } @Override protected EntityUserEntity convert(Tuple tuple, Set columns) { - return null; + EntityUserEntity item = new EntityUserEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, EntityUserEntity._id, UUID.class)); + item.setEntityId(QueryBase.convertSafe(tuple, columns, EntityUserEntity._entityId, UUID.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, EntityUserEntity._userId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, EntityUserEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, EntityUserEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, EntityUserEntity._isActive, IsActive.class)); + return item; } } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/BuildSubQueryInput.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/BuildSubQueryInput.java new file mode 100644 index 000000000..aab5c915b --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/BuildSubQueryInput.java @@ -0,0 +1,90 @@ +package gr.cite.annotation.query.utils; + +import gr.cite.tools.data.query.QueryContext; +import jakarta.persistence.criteria.*; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class BuildSubQueryInput { + private final AbstractQuery query; + private final CriteriaBuilder criteriaBuilder; + private final Class entityType; + private final Class keyType; + private final Function, Expression> keyPathFunc; + private final BiFunction, CriteriaBuilder, Predicate> filterFunc; + + public BuildSubQueryInput(Builder builder) { + query = builder.query; + criteriaBuilder = builder.criteriaBuilder; + entityType = builder.entityType; + keyType = builder.keyType; + keyPathFunc = builder.keyPathFunc; + filterFunc = builder.filterFunc; + } + + public AbstractQuery getQuery() { + return query; + } + + public CriteriaBuilder getCriteriaBuilder() { + return criteriaBuilder; + } + + public Class getEntityType() { + return entityType; + } + + public Class getKeyType() { + return keyType; + } + + public Function, Expression> getKeyPathFunc() { + return keyPathFunc; + } + + public BiFunction, CriteriaBuilder, Predicate> getFilterFunc() { + return filterFunc; + } + + public static class Builder { + private final Class entityType; + private final Class keyType; + private AbstractQuery query; + private CriteriaBuilder criteriaBuilder; + private Function, Expression> keyPathFunc; + private BiFunction, CriteriaBuilder, Predicate> filterFunc; + + public Builder(Class entityType, Class keyType) { + this.entityType = entityType; + this.keyType = keyType; + } + + public Builder(Class entityType, Class keyType, QueryContext queryContext) { + this.entityType = entityType; + this.keyType = keyType; + this.query = queryContext.Query; + this.criteriaBuilder = queryContext.CriteriaBuilder; + } + + public Builder query(AbstractQuery query) { + this.query = query; + return this; + } + + public Builder criteriaBuilder(CriteriaBuilder criteriaBuilder) { + this.criteriaBuilder = criteriaBuilder; + return this; + } + + public Builder keyPathFunc(Function, Expression> keyPathFunc) { + this.keyPathFunc = keyPathFunc; + return this; + } + + public Builder filterFunc(BiFunction, CriteriaBuilder, Predicate> filterFunc) { + this.filterFunc = filterFunc; + return this; + } + } +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/QueryUtilsService.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/QueryUtilsService.java new file mode 100644 index 000000000..a64a0277a --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/QueryUtilsService.java @@ -0,0 +1,11 @@ +package gr.cite.annotation.query.utils; + +import jakarta.persistence.criteria.AbstractQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Subquery; + +import java.util.UUID; + +public interface QueryUtilsService { + Subquery buildSubQuery(BuildSubQueryInput parameters); +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/QueryUtilsServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/QueryUtilsServiceImpl.java new file mode 100644 index 000000000..f668f5cea --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/utils/QueryUtilsServiceImpl.java @@ -0,0 +1,20 @@ +package gr.cite.annotation.query.utils; + +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +public class QueryUtilsServiceImpl implements QueryUtilsService { + @Override + public Subquery buildSubQuery(BuildSubQueryInput parameters){ + Subquery subQuery = parameters.getQuery().subquery(parameters.getKeyType()); + Root subQueryRoot = subQuery.from(parameters.getEntityType()); + subQuery.select(parameters.getKeyPathFunc().apply(subQueryRoot)).distinct(true); + subQuery.where(parameters.getFilterFunc().apply(subQueryRoot, parameters.getCriteriaBuilder())); + return subQuery; + } +} + diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java index 1ac337243..8dd30ef82 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java @@ -1,7 +1,9 @@ package gr.cite.annotation.service.annotation; import gr.cite.annotation.authorization.AuthorizationFlags; +import gr.cite.annotation.authorization.OwnedResource; import gr.cite.annotation.authorization.Permission; +import gr.cite.annotation.authorization.authorizationcontentresolver.AuthorizationContentResolver; import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.scope.user.UserScope; import gr.cite.annotation.data.AnnotationEntity; @@ -23,6 +25,8 @@ import gr.cite.tools.logging.MapLogEntry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; import javax.management.InvalidApplicationException; @@ -45,17 +49,23 @@ public class AnnotationServiceImpl implements AnnotationService { private final BuilderFactory builderFactory; private final UserScope userScope; + private final AuthorizationContentResolver authorizationContentResolver; + private final MessageSource messageSource; + + public AnnotationServiceImpl( - AuthorizationService authorizationService, - DeleterFactory deleterFactory, - EntityManager entityManager, - BuilderFactory builderFactory, UserScope userScope) { + AuthorizationService authorizationService, + DeleterFactory deleterFactory, + EntityManager entityManager, + BuilderFactory builderFactory, UserScope userScope, AuthorizationContentResolver authorizationContentResolver, MessageSource messageSource) { this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; this.entityManager = entityManager; this.builderFactory = builderFactory; this.userScope = userScope; + this.authorizationContentResolver = authorizationContentResolver; + this.messageSource = messageSource; } @Override @@ -63,7 +73,7 @@ public class AnnotationServiceImpl implements AnnotationService { public Annotation persist(AnnotationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException { logger.debug(new MapLogEntry("persisting annotation").And("model", model).And("fields", fields)); - this.authorizationService.authorizeForce(Permission.EditAnnotation); + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.entityAffiliation(model.getEntityId())), Permission.NewAnnotation); AnnotationEntity data = new AnnotationEntity(); data.setId(UUID.randomUUID()); @@ -91,7 +101,10 @@ public class AnnotationServiceImpl implements AnnotationService { public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { logger.debug("deleting Annotation: {}", id); - this.authorizationService.authorizeForce(Permission.DeleteAnnotation); + AnnotationEntity annotation = this.entityManager.find(AnnotationEntity.class, id); + if (annotation == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Annotation.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.authorizationService.authorizeAtLeastOneForce(annotation.getSubjectId() != null ? List.of(new OwnedResource(annotation.getSubjectId())) : null, Permission.DeleteAnnotation); this.deleterFactory.deleter(AnnotationDeleter.class).deleteAndSaveByIds(List.of(id)); } diff --git a/annotation-service/pom.xml b/annotation-service/pom.xml index c9662c360..47d35f3bb 100644 --- a/annotation-service/pom.xml +++ b/annotation-service/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.2 + 3.2.1 @@ -136,18 +136,18 @@ gr.cite rabbitmq-core - 2.1.1 + 2.1.2 gr.cite queue-inbox - 1.0.0 + 2.1.1 gr.cite queue-outbox - 1.0.0 + 2.1.1 diff --git a/dmp-backend/core/pom.xml b/dmp-backend/core/pom.xml index 9dcacf260..711c9c8de 100644 --- a/dmp-backend/core/pom.xml +++ b/dmp-backend/core/pom.xml @@ -65,12 +65,12 @@ gr.cite queue-inbox - 1.0.0 + 2.1.1 gr.cite queue-outbox - 1.0.0 + 2.1.1 diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java index cb982f855..1439ffb52 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java @@ -16,7 +16,7 @@ import java.util.List; @Service @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) public class PermissionNameProvider { - private static final Logger logger = LoggerFactory.getLogger(DepositServiceImpl.class); + private static final Logger logger = LoggerFactory.getLogger(PermissionNameProvider.class); private final List permissions; public PermissionNameProvider(ConventionService conventionService) { diff --git a/dmp-backend/core/src/main/java/eu/eudat/configurations/db/NamingStrategyProperties.java b/dmp-backend/core/src/main/java/eu/eudat/configurations/db/NamingStrategyProperties.java new file mode 100644 index 000000000..d13980dbb --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/configurations/db/NamingStrategyProperties.java @@ -0,0 +1,20 @@ +package eu.eudat.configurations.db; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "naming-strategy") +public class NamingStrategyProperties { + + private final String prefix; + + @ConstructorBinding + public NamingStrategyProperties(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/configurations/db/PrefixPhysicalNamingStrategy.java b/dmp-backend/core/src/main/java/eu/eudat/configurations/db/PrefixPhysicalNamingStrategy.java new file mode 100644 index 000000000..140062448 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/configurations/db/PrefixPhysicalNamingStrategy.java @@ -0,0 +1,30 @@ +package eu.eudat.configurations.db; + +import eu.eudat.convention.ConventionService; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@EnableConfigurationProperties({NamingStrategyProperties.class}) +public class PrefixPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl { + + private final NamingStrategyProperties namingStrategyProperties; + private final ConventionService conventionService; + + public PrefixPhysicalNamingStrategy(NamingStrategyProperties namingStrategyProperties, ConventionService conventionService) { + this.namingStrategyProperties = namingStrategyProperties; + this.conventionService = conventionService; + } + + @Override + public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment context) { + if (conventionService.isNullOrEmpty(namingStrategyProperties.getPrefix())) + return super.toPhysicalTableName(logicalName, context); + Identifier identifier = new Identifier(namingStrategyProperties.getPrefix() + logicalName.getText(), logicalName.isQuoted()); + return super.toPhysicalTableName(identifier, context); + } + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/QueueInboxEntity.java b/dmp-backend/core/src/main/java/eu/eudat/data/QueueInboxEntity.java index 43a44bbd6..29d5cb627 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/data/QueueInboxEntity.java +++ b/dmp-backend/core/src/main/java/eu/eudat/data/QueueInboxEntity.java @@ -2,6 +2,7 @@ package eu.eudat.data; import eu.eudat.commons.enums.IsActive; import eu.eudat.data.converters.enums.IsActiveConverter; +import eu.eudat.data.converters.enums.QueueInboxStatusConverter; import eu.eudat.data.types.JsonSQLType; import gr.cite.queueinbox.entity.QueueInbox; import gr.cite.queueinbox.entity.QueueInboxStatus; @@ -19,15 +20,15 @@ public class QueueInboxEntity implements QueueInbox { private UUID id; public final static String _id = "id"; - @Column(name = "\"queue\"", nullable = false, length = 50) + @Column(name = "\"queue\"", nullable = false, length = 200) private String queue; public final static String _queue = "queue"; - @Column(name = "\"exchange\"", nullable = false, length = 50) + @Column(name = "\"exchange\"", nullable = false, length = 200) private String exchange; public final static String _exchange = "exchange"; - @Column(name = "\"route\"", nullable = false, length = 50) + @Column(name = "\"route\"", nullable = false, length = 200) private String route; public final static String _route = "route"; @@ -52,14 +53,13 @@ public class QueueInboxEntity implements QueueInbox { private UUID tenantId; public final static String _tenantId = "tenantId"; - @Column(name = "\"is_active\"", length = 20, nullable = false) + @Column(name = "\"is_active\"", nullable = false) @Convert(converter = IsActiveConverter.class) private IsActive isActive; public final static String _isActive = "isActive"; - //TODO: as integer - @Column(name = "\"status\"", length = 50, nullable = false) - @Enumerated(EnumType.STRING) + @Column(name = "\"status\"", nullable = false) + @Convert(converter = QueueInboxStatusConverter.class) private QueueInboxStatus status; public final static String _status = "status"; diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/QueueOutboxEntity.java b/dmp-backend/core/src/main/java/eu/eudat/data/QueueOutboxEntity.java index 721d5c94d..daf463a5d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/data/QueueOutboxEntity.java +++ b/dmp-backend/core/src/main/java/eu/eudat/data/QueueOutboxEntity.java @@ -2,6 +2,7 @@ package eu.eudat.data; import eu.eudat.commons.enums.IsActive; import eu.eudat.data.converters.enums.IsActiveConverter; +import eu.eudat.data.converters.enums.QueueOutboxNotifyStatusConverter; import gr.cite.queueoutbox.entity.QueueOutbox; import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; @@ -17,11 +18,11 @@ public class QueueOutboxEntity implements QueueOutbox { private UUID id; public final static String _id = "id"; - @Column(name = "\"exchange\"", nullable = false, length = 50) + @Column(name = "\"exchange\"", nullable = false, length = 200) private String exchange; public final static String _exchange = "exchange"; - @Column(name = "\"route\"", length = 50) + @Column(name = "\"route\"", length = 200) private String route; public final static String _route = "route"; @@ -33,9 +34,8 @@ public class QueueOutboxEntity implements QueueOutbox { private String message; public final static String _message = "message"; - //TODO: as integer - @Column(name = "\"notify_status\"", length = 20, nullable = false) - @Enumerated(EnumType.STRING) + @Column(name = "\"notify_status\"", nullable = false) + @Convert(converter = QueueOutboxNotifyStatusConverter.class) private QueueOutboxNotifyStatus notifyStatus; public final static String _notifyStatus = "notifyStatus"; @@ -55,7 +55,7 @@ public class QueueOutboxEntity implements QueueOutbox { private UUID tenantId; public final static String _tenantId = "tenantId"; - @Column(name = "\"is_active\"", length = 20, nullable = false) + @Column(name = "\"is_active\"", nullable = false) @Convert(converter = IsActiveConverter.class) private IsActive isActive; public final static String _isActive = "isActive"; diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/QueueInboxStatusConverter.java b/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/QueueInboxStatusConverter.java new file mode 100644 index 000000000..387c57ad1 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/QueueInboxStatusConverter.java @@ -0,0 +1,19 @@ +package eu.eudat.data.converters.enums; + +import gr.cite.queueinbox.entity.QueueInboxStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class QueueInboxStatusConverter implements AttributeConverter { + @Override + public Short convertToDatabaseColumn(QueueInboxStatus value) { + if (value == null) throw new IllegalArgumentException("Value could not be null for: " + this.getClass().getSimpleName()); + return value.getValue(); + } + + @Override + public QueueInboxStatus convertToEntityAttribute(Short dbData) { + return QueueInboxStatus.of(dbData); + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/QueueOutboxNotifyStatusConverter.java b/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/QueueOutboxNotifyStatusConverter.java new file mode 100644 index 000000000..9e822930d --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/QueueOutboxNotifyStatusConverter.java @@ -0,0 +1,19 @@ +package eu.eudat.data.converters.enums; + +import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class QueueOutboxNotifyStatusConverter implements AttributeConverter { + @Override + public Short convertToDatabaseColumn(QueueOutboxNotifyStatus value) { + if (value == null) throw new IllegalArgumentException("Value could not be null for: " + this.getClass().getSimpleName()); + return value.getValue(); + } + + @Override + public QueueOutboxNotifyStatus convertToEntityAttribute(Short dbData) { + return QueueOutboxNotifyStatus.of(dbData); + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxIntegrationEvent.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxIntegrationEvent.java index 9c2db4fdc..30809dbbd 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxIntegrationEvent.java +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxIntegrationEvent.java @@ -28,6 +28,7 @@ public class OutboxIntegrationEvent extends IntegrationEvent { public static final String DESCRIPTION_TOUCH = "DESCRIPTION_TOUCH"; public static final String ANNOTATION_ENTITY_TOUCH = "ANNOTATION_ENTITY_TOUCH"; + public static final String ANNOTATION_ENTITY_REMOVE = "ANNOTATION_ENTITY_REMOVE"; public static final String WHAT_YOU_KNOW_ABOUT_ME_COMPLETED = "WHAT_YOU_KNOW_ABOUT_ME_COMPLETED"; diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxProperties.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxProperties.java index b9f771aa8..70fcef0c1 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxProperties.java +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxProperties.java @@ -23,7 +23,8 @@ public class OutboxProperties { private final String descriptionTouchTopic; - private final String annotationEntityTouchTopic; + private final String annotationEntitiesTouchTopic; + private final String annotationEntitiesRemovalTopic; private final String notifyTopic; @@ -34,19 +35,20 @@ public class OutboxProperties { private final String generateFileTopic; public OutboxProperties(String exchange, - String tenantTouchTopic, - String tenantRemovalTopic, - String tenantReactivationTopic, - String tenantUserInviteTopic, - String userRemovalTopic, - String userTouchTopic, - String dmpTouchTopic, - String descriptionTouchTopic, - String annotationEntityTouchTopic, - String notifyTopic, - String forgetMeCompletedTopic, - String whatYouKnowAboutMeCompletedTopic, - String generateFileTopic + String tenantTouchTopic, + String tenantRemovalTopic, + String tenantReactivationTopic, + String tenantUserInviteTopic, + String userRemovalTopic, + String userTouchTopic, + String dmpTouchTopic, + String descriptionTouchTopic, + String annotationEntitiesTouchTopic, + String annotationEntitiesRemovalTopic, + String notifyTopic, + String forgetMeCompletedTopic, + String whatYouKnowAboutMeCompletedTopic, + String generateFileTopic ) { this.exchange = exchange; this.tenantTouchTopic = tenantTouchTopic; @@ -57,8 +59,9 @@ public class OutboxProperties { this.userTouchTopic = userTouchTopic; this.dmpTouchTopic = dmpTouchTopic; this.descriptionTouchTopic = descriptionTouchTopic; - this.annotationEntityTouchTopic = annotationEntityTouchTopic; - this.notifyTopic = notifyTopic; + this.annotationEntitiesTouchTopic = annotationEntitiesTouchTopic; + this.annotationEntitiesRemovalTopic = annotationEntitiesRemovalTopic; + this.notifyTopic = notifyTopic; this.forgetMeCompletedTopic = forgetMeCompletedTopic; this.whatYouKnowAboutMeCompletedTopic = whatYouKnowAboutMeCompletedTopic; this.generateFileTopic = generateFileTopic; @@ -100,8 +103,12 @@ public class OutboxProperties { return descriptionTouchTopic; } - public String getAnnotationEntityTouchTopic() { - return annotationEntityTouchTopic; + public String getAnnotationEntitiesTouchTopic() { + return annotationEntitiesTouchTopic; + } + + public String getAnnotationEntitiesRemovalTopic() { + return annotationEntitiesRemovalTopic; } public String getNotifyTopic() { diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxRepositoryImpl.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxRepositoryImpl.java index 08f8a8eca..958a343de 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxRepositoryImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/OutboxRepositoryImpl.java @@ -416,7 +416,11 @@ public class OutboxRepositoryImpl implements OutboxRepository { break; } case OutboxIntegrationEvent.ANNOTATION_ENTITY_TOUCH: { - routingKey = this.outboxProperties.getAnnotationEntityTouchTopic(); + routingKey = this.outboxProperties.getAnnotationEntitiesTouchTopic(); + break; + } + case OutboxIntegrationEvent.ANNOTATION_ENTITY_REMOVE: { + routingKey = this.outboxProperties.getAnnotationEntitiesRemovalTopic(); break; } case OutboxIntegrationEvent.FORGET_ME_COMPLETED: { diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntitiesRemovalIntegrationEvent.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntitiesRemovalIntegrationEvent.java new file mode 100644 index 000000000..99e3e2f49 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntitiesRemovalIntegrationEvent.java @@ -0,0 +1,19 @@ +package eu.eudat.integrationevent.outbox.annotationentityremoval; + +import eu.eudat.integrationevent.TrackedEvent; + +import java.util.List; +import java.util.UUID; + +public class AnnotationEntitiesRemovalIntegrationEvent extends TrackedEvent { + + private List entityIds; + + public List getEntityIds() { + return entityIds; + } + + public void setEntityIds(List entityIds) { + this.entityIds = entityIds; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntityRemovalIntegrationEventHandler.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntityRemovalIntegrationEventHandler.java new file mode 100644 index 000000000..b5c82a3e0 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntityRemovalIntegrationEventHandler.java @@ -0,0 +1,9 @@ +package eu.eudat.integrationevent.outbox.annotationentityremoval; + +import java.util.UUID; + +public interface AnnotationEntityRemovalIntegrationEventHandler { + + void handleDescription(UUID descriptionId); + void handleDmp(UUID dmpId); +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntityRemovalIntegrationEventHandlerImpl.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntityRemovalIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..7b40dcc79 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentityremoval/AnnotationEntityRemovalIntegrationEventHandlerImpl.java @@ -0,0 +1,67 @@ +package eu.eudat.integrationevent.outbox.annotationentityremoval; + +import eu.eudat.commons.enums.IsActive; +import eu.eudat.data.DescriptionEntity; +import eu.eudat.data.DmpUserEntity; +import eu.eudat.integrationevent.outbox.OutboxIntegrationEvent; +import eu.eudat.integrationevent.outbox.OutboxService; +import eu.eudat.model.Description; +import eu.eudat.model.DmpUser; +import eu.eudat.query.DescriptionQuery; +import eu.eudat.query.DmpUserQuery; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class AnnotationEntityRemovalIntegrationEventHandlerImpl implements AnnotationEntityRemovalIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationEntityRemovalIntegrationEventHandlerImpl.class)); + + private final OutboxService outboxService; + + private final QueryFactory queryFactory; + + public AnnotationEntityRemovalIntegrationEventHandlerImpl(OutboxService outboxService, QueryFactory queryFactory) { + this.outboxService = outboxService; + this.queryFactory = queryFactory; + } + + private void handle(AnnotationEntitiesRemovalIntegrationEvent event) { + OutboxIntegrationEvent message = new OutboxIntegrationEvent(); + message.setMessageId(UUID.randomUUID()); + message.setType(OutboxIntegrationEvent.ANNOTATION_ENTITY_REMOVE); + message.setEvent(event); + this.outboxService.publish(message); + } + + @Override + public void handleDescription(UUID descriptionId) { + AnnotationEntitiesRemovalIntegrationEvent event = new AnnotationEntitiesRemovalIntegrationEvent(); + event.setEntityIds(List.of(descriptionId)); + + this.handle(event); + } + + @Override + public void handleDmp(UUID dmpId) { + List descriptionEntities = this.queryFactory.query(DescriptionQuery.class).dmpIds(dmpId).collectAs(new BaseFieldSet().ensure(Description._id)); + + AnnotationEntitiesRemovalIntegrationEvent event = new AnnotationEntitiesRemovalIntegrationEvent(); + event.setEntityIds(new ArrayList<>()); + event.getEntityIds().add(dmpId); + + for (DescriptionEntity description : descriptionEntities) event.getEntityIds().add(description.getId()); + + this.handle(event); + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntitiesTouchedIntegrationEvent.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntitiesTouchedIntegrationEvent.java new file mode 100644 index 000000000..35315b807 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntitiesTouchedIntegrationEvent.java @@ -0,0 +1,43 @@ +package eu.eudat.integrationevent.outbox.annotationentitytouch; + +import eu.eudat.integrationevent.TrackedEvent; + +import java.util.List; +import java.util.UUID; + +public class AnnotationEntitiesTouchedIntegrationEvent extends TrackedEvent { + + private List events; + + public List getEvents() { + return events; + } + + public void setEvents(List events) { + this.events = events; + } + + public static class AnnotationEntityTouchedIntegrationEvent { + + private UUID entityId; + + private List userIds; + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEvent.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEvent.java deleted file mode 100644 index 715d6d7f2..000000000 --- a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -package eu.eudat.integrationevent.outbox.annotationentitytouch; - -import eu.eudat.integrationevent.TrackedEvent; - -import java.util.List; -import java.util.UUID; - -public class AnnotationEntityTouchedIntegrationEvent extends TrackedEvent { - - private UUID entityId; - - private List userIds; - - public UUID getEntityId() { - return entityId; - } - - public void setEntityId(UUID entityId) { - this.entityId = entityId; - } - - public List getUserIds() { - return userIds; - } - - public void setUserIds(List userIds) { - this.userIds = userIds; - } - -} diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java index 856a4922a..592ea1c3c 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandler.java @@ -9,17 +9,6 @@ import java.util.UUID; public interface AnnotationEntityTouchedIntegrationEventHandler { - void handle(AnnotationEntityTouchedIntegrationEvent event); - - static AnnotationEntityTouchedIntegrationEvent buildEventFromPersistModel(DmpPersist persist) { - AnnotationEntityTouchedIntegrationEvent event = new AnnotationEntityTouchedIntegrationEvent(); - event.setEntityId(persist.getId()); - List users = new ArrayList<>(); - persist.getUsers().forEach(dmpUserPersist -> { - users.add(dmpUserPersist.getUser()); - }); - event.setUserIds(users); - return event; - } - + void handleDescription(UUID descriptionId); + void handleDmp(UUID dmpId); } diff --git a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java index 37a9541e4..6401ead08 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/integrationevent/outbox/annotationentitytouch/AnnotationEntityTouchedIntegrationEventHandlerImpl.java @@ -1,13 +1,24 @@ package eu.eudat.integrationevent.outbox.annotationentitytouch; +import eu.eudat.commons.enums.IsActive; +import eu.eudat.data.DescriptionEntity; +import eu.eudat.data.DmpUserEntity; import eu.eudat.integrationevent.outbox.OutboxIntegrationEvent; import eu.eudat.integrationevent.outbox.OutboxService; +import eu.eudat.model.Description; +import eu.eudat.model.DmpUser; +import eu.eudat.query.DescriptionQuery; +import eu.eudat.query.DmpUserQuery; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; 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; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Component @@ -18,12 +29,14 @@ public class AnnotationEntityTouchedIntegrationEventHandlerImpl implements Annot private final OutboxService outboxService; - public AnnotationEntityTouchedIntegrationEventHandlerImpl(OutboxService outboxService) { + private final QueryFactory queryFactory; + + public AnnotationEntityTouchedIntegrationEventHandlerImpl(OutboxService outboxService, QueryFactory queryFactory) { this.outboxService = outboxService; + this.queryFactory = queryFactory; } - @Override - public void handle(AnnotationEntityTouchedIntegrationEvent event) { + private void handle(AnnotationEntitiesTouchedIntegrationEvent event) { OutboxIntegrationEvent message = new OutboxIntegrationEvent(); message.setMessageId(UUID.randomUUID()); message.setType(OutboxIntegrationEvent.ANNOTATION_ENTITY_TOUCH); @@ -31,4 +44,40 @@ public class AnnotationEntityTouchedIntegrationEventHandlerImpl implements Annot this.outboxService.publish(message); } + @Override + public void handleDescription(UUID descriptionId) { + DescriptionEntity entity = this.queryFactory.query(DescriptionQuery.class).ids(descriptionId).firstAs(new BaseFieldSet().ensure(Description._dmp)); + if (entity == null) return; + List dmpUsers = this.queryFactory.query(DmpUserQuery.class).dmpIds(entity.getDmpId()).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(DmpUser._user)); + + AnnotationEntitiesTouchedIntegrationEvent event = new AnnotationEntitiesTouchedIntegrationEvent(); + event.setEvents(List.of(this.buildEventItem(descriptionId, dmpUsers))); + + this.handle(event); + } + + @Override + public void handleDmp(UUID dmpId) { + List descriptionEntities = this.queryFactory.query(DescriptionQuery.class).dmpIds(dmpId).collectAs(new BaseFieldSet().ensure(Description._id)); + List dmpUsers = this.queryFactory.query(DmpUserQuery.class).dmpIds(dmpId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(DmpUser._user)); + + AnnotationEntitiesTouchedIntegrationEvent event = new AnnotationEntitiesTouchedIntegrationEvent(); + event.setEvents(new ArrayList<>()); + event.getEvents().add(this.buildEventItem(dmpId, dmpUsers)); + + for (DescriptionEntity description : descriptionEntities) event.getEvents().add(this.buildEventItem(description.getId(), dmpUsers)); + + this.handle(event); + } + + private AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent buildEventItem(UUID entityId, List dmpUsers){ + AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent eventItem = new AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent(); + eventItem.setEntityId(entityId); + List users = new ArrayList<>(); + for (DmpUserEntity dmpUser : dmpUsers){ + users.add(dmpUser.getUserId()); + } + eventItem.setUserIds(users); + return eventItem; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/PublicDmp.java b/dmp-backend/core/src/main/java/eu/eudat/model/PublicDmp.java index 1ad8dbb90..9bdbb542a 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/PublicDmp.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/PublicDmp.java @@ -57,6 +57,9 @@ public class PublicDmp { private List descriptions; public static final String _descriptions = "descriptions"; + private List entityDois; + public static final String _entityDois = "entityDois"; + public UUID getId() { return id; } @@ -160,4 +163,12 @@ public class PublicDmp { public void setDescriptions(List descriptions) { this.descriptions = descriptions; } + + public List getEntityDois() { + return entityDois; + } + + public void setEntityDois(List entityDois) { + this.entityDois = entityDois; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/PublicEntityDoi.java b/dmp-backend/core/src/main/java/eu/eudat/model/PublicEntityDoi.java new file mode 100644 index 000000000..709027f2a --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/model/PublicEntityDoi.java @@ -0,0 +1,69 @@ +package eu.eudat.model; + +import eu.eudat.commons.enums.EntityType; + +import java.util.UUID; + +public class PublicEntityDoi { + + private UUID id; + + public static final String _id = "id"; + + private EntityType entityType; + + public static final String _entityType = "entityType"; + + private String repositoryId; + + public static final String _repositoryId = "repositoryId"; + + private String doi; + + public static final String _doi = "doi"; + + private UUID entityId; + + public static final String _entityId = "entityId"; + + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public EntityType getEntityType() { + return entityType; + } + + public void setEntityType(EntityType entityType) { + this.entityType = entityType; + } + + public String getRepositoryId() { + return repositoryId; + } + + public void setRepositoryId(String repositoryId) { + this.repositoryId = repositoryId; + } + + public String getDoi() { + return doi; + } + + public void setDoi(String doi) { + this.doi = doi; + } + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicDmpBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicDmpBuilder.java index 05d146e99..585393a8c 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicDmpBuilder.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicDmpBuilder.java @@ -1,13 +1,14 @@ package eu.eudat.model.builder; import eu.eudat.authorization.AuthorizationFlags; -import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.enums.EntityType; import eu.eudat.convention.ConventionService; import eu.eudat.data.DmpEntity; import eu.eudat.model.*; import eu.eudat.query.DescriptionQuery; import eu.eudat.query.DmpReferenceQuery; import eu.eudat.query.DmpUserQuery; +import eu.eudat.query.EntityDoiQuery; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; @@ -66,6 +67,9 @@ public class PublicDmpBuilder extends BaseBuilder { FieldSet descriptionsFields = fields.extractPrefixed(this.asPrefix(PublicDmp._descriptions)); Map> descriptionsMap = this.collectDmpDescriptions(descriptionsFields, data); + FieldSet entityDoisFields = fields.extractPrefixed(this.asPrefix(PublicDmp._entityDois)); + Map> entityDoisMap = this.collectEntityDois(entityDoisFields, data); + for (DmpEntity d : data) { PublicDmp m = new PublicDmp(); if (fields.hasField(this.asIndexer(PublicDmp._id))) m.setId(d.getId()); @@ -82,6 +86,7 @@ public class PublicDmpBuilder extends BaseBuilder { if (dmpReferenceMap != null && !dmpReferenceMap.isEmpty() && dmpReferenceMap.containsKey(d.getId())) m.setDmpReferences(dmpReferenceMap.get(d.getId())); if (dmpUsersMap != null && !dmpUsersMap.isEmpty() && dmpUsersMap.containsKey(d.getId())) m.setDmpUsers(dmpUsersMap.get(d.getId())); if (descriptionsMap != null && !descriptionsMap.isEmpty() && descriptionsMap.containsKey(d.getId())) m.setDescriptions(descriptionsMap.get(d.getId())); + if (entityDoisMap != null && !entityDoisMap.isEmpty() && entityDoisMap.containsKey(d.getId())) m.setEntityDois(entityDoisMap.get(d.getId())); models.add(m); } @@ -144,4 +149,22 @@ public class PublicDmpBuilder extends BaseBuilder { return itemMap; } + private Map> collectEntityDois(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", PublicEntityDoi.class.getSimpleName()); + + Map> itemMap; + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(this.asIndexer(PublicEntityDoi._entityId)); + EntityDoiQuery query = this.queryFactory.query(EntityDoiQuery.class).authorize(this.authorize).types(EntityType.DMP).entityIds(data.stream().map(DmpEntity::getId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(PublicEntityDoiBuilder.class).authorize(this.authorize).asMasterKey(query, clone, PublicEntityDoi::getEntityId); + + if (!fields.hasField(this.asIndexer(PublicEntityDoi._entityId))) { + itemMap.values().stream().flatMap(List::stream).filter(x -> x != null && x.getEntityId() != null).peek(x -> { + x.setEntityId(null); + }); + } + + return itemMap; + } + } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicEntityDoiBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicEntityDoiBuilder.java new file mode 100644 index 000000000..8ae936a29 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/model/builder/PublicEntityDoiBuilder.java @@ -0,0 +1,69 @@ +package eu.eudat.model.builder; + +import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.convention.ConventionService; +import eu.eudat.data.EntityDoiEntity; +import eu.eudat.model.*; +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.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class PublicEntityDoiBuilder extends BaseBuilder { + + private final QueryFactory queryFactory; + + private final BuilderFactory builderFactory; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + @Autowired + public PublicEntityDoiBuilder( + ConventionService conventionService, + QueryFactory queryFactory, + BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PublicEntityDoiBuilder.class))); + this.queryFactory = queryFactory; + this.builderFactory = builderFactory; + } + + public PublicEntityDoiBuilder 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 (EntityDoiEntity d : data) { + PublicEntityDoi m = new PublicEntityDoi(); + if (fields.hasField(this.asIndexer(PublicEntityDoi._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(PublicEntityDoi._entityType))) m.setEntityType(d.getEntityType()); + if (fields.hasField(this.asIndexer(PublicEntityDoi._repositoryId))) m.setRepositoryId(d.getRepositoryId()); + if (fields.hasField(this.asIndexer(PublicEntityDoi._doi))) m.setDoi(d.getDoi()); + if (fields.hasField(this.asIndexer(PublicEntityDoi._entityId))) m.setEntityId(d.getEntityId()); + + models.add(m); + } + + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + + return models; + } + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/CloneDmpPersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/CloneDmpPersist.java index 2994eac69..cbda577f6 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/persist/CloneDmpPersist.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/CloneDmpPersist.java @@ -4,13 +4,13 @@ import eu.eudat.commons.validation.BaseValidator; import gr.cite.tools.validation.specification.Specification; import eu.eudat.convention.ConventionService; import eu.eudat.errorcode.ErrorThesaurusProperties; -import org.apache.commons.compress.utils.Lists; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Scope; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -29,7 +29,7 @@ public class CloneDmpPersist { public static final String _description = "description"; - private List descriptions = Lists.newArrayList(); + private List descriptions = new ArrayList<>(); public static final String _descriptions = "descriptions"; diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/NewVersionDmpPersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/NewVersionDmpPersist.java index 5adc9b923..15953fd68 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/persist/NewVersionDmpPersist.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/NewVersionDmpPersist.java @@ -5,13 +5,13 @@ import gr.cite.tools.validation.specification.Specification; import eu.eudat.convention.ConventionService; import eu.eudat.data.DmpEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; -import org.apache.commons.compress.utils.Lists; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Scope; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -33,7 +33,7 @@ public class NewVersionDmpPersist { public static final String _blueprintId = "blueprintId"; - private List descriptions = Lists.newArrayList(); + private List descriptions = new ArrayList<>(); public static final String _descriptions = "descriptions"; diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java index 1f55d2cbc..a40643460 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java @@ -22,6 +22,8 @@ import eu.eudat.data.*; import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.event.DescriptionTouchedEvent; import eu.eudat.event.EventBroker; +import eu.eudat.integrationevent.outbox.annotationentityremoval.AnnotationEntityRemovalIntegrationEventHandler; +import eu.eudat.integrationevent.outbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.descriptiontouched.DescriptionTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler; @@ -99,6 +101,8 @@ public class DescriptionServiceImpl implements DescriptionService { private final StorageFileService storageFileService; private final DescriptionTouchedIntegrationEventHandler descriptionTouchedIntegrationEventHandler; private final AuthorizationContentResolver authorizationContentResolver; + private final AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler; + private final AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler; @Autowired public DescriptionServiceImpl( @@ -113,7 +117,7 @@ public class DescriptionServiceImpl implements DescriptionService { QueryFactory queryFactory, JsonHandlingService jsonHandlingService, UserScope userScope, - XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService, DescriptionTouchedIntegrationEventHandler descriptionTouchedIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver) { + XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService, DescriptionTouchedIntegrationEventHandler descriptionTouchedIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver, AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -135,6 +139,8 @@ public class DescriptionServiceImpl implements DescriptionService { this.storageFileService = storageFileService; this.descriptionTouchedIntegrationEventHandler = descriptionTouchedIntegrationEventHandler; this.authorizationContentResolver = authorizationContentResolver; + this.annotationEntityTouchedIntegrationEventHandler = annotationEntityTouchedIntegrationEventHandler; + this.annotationEntityRemovalIntegrationEventHandler = annotationEntityRemovalIntegrationEventHandler; } @Override @@ -230,6 +236,8 @@ public class DescriptionServiceImpl implements DescriptionService { this.descriptionTouchedIntegrationEventHandler.handle(DescriptionTouchedIntegrationEventHandler.buildEventFromPersistModel(model)); + this.annotationEntityTouchedIntegrationEventHandler.handleDescription(data.getId()); + this.elasticService.persistDescription(data); return this.builderFactory.builder(DescriptionBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Description._id), data); } @@ -294,71 +302,6 @@ public class DescriptionServiceImpl implements DescriptionService { return event; } -// public List getFieldById(String id){ -// List fieldEntities = new ArrayList<>(); -// if (id == null || id.isBlank()) return fieldEntities; -// if (this.getFieldSets() != null){ -// for (FieldSetEntity fieldSetEntity: this.getFieldSets()) { -// fieldEntities.addAll(fieldSetEntity.getFieldById(id)); -// } -// } -// if (this.getSections() != null){ -// for (SectionEntity sectionEntity: this.getSections()) { -// fieldEntities.addAll(sectionEntity.getFieldById(id)); -// } -// } -// return fieldEntities; -// } - - private void descriptionForce(DescriptionEntity description) throws Exception { - List datasetProfileValidators = new LinkedList<>(); - DescriptionTemplateEntity descriptionTemplateEntity = this.entityManager.find(DescriptionTemplateEntity.class, description.getDescriptionTemplateId()); - if (descriptionTemplateEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{description.getDescriptionTemplateId(), DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); - eu.eudat.commons.types.descriptiontemplate.DefinitionEntity descriptionTemplateDefinition = this.xmlHandlingService.fromXml(eu.eudat.commons.types.descriptiontemplate.DefinitionEntity.class, descriptionTemplateEntity.getDefinition()); - - - - - - // DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); -// DocumentBuilder builder = builderFactory.newDocumentBuilder(); -// Document xmlDocument = builder.parse(new ByteArrayInputStream(profile.getDefinition().getBytes())); -// -// XPath xPath = XPathFactory.newInstance().newXPath(); -// String expression = "//validation/@type[.=1]/ancestor::field/@id"; -// NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET); -// -// for (int i = 0; i < nodeList.getLength(); i++) { -// Node node = nodeList.item(i); -// datasetProfileValidators.add(node.getNodeValue()); -// } -// -// expression = "//validation/@type[.=1]/ancestor::fieldSet"; -// nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET); -// -// -// JSONObject obj = new JSONObject(description.getProperties()); -// VisibilityRuleService visibilityRuleService = new VisibilityRuleServiceImpl(); -// visibilityRuleService.setProperties(obj.toMap()); -// -// description.setProfile(profile); -// PagedDatasetProfile pagedDatasetProfile = this.getPagedProfile(new DatasetWizardModel(), description); -// visibilityRuleService.buildVisibilityContext(pagedDatasetProfile.getRules()); -// -// -// String failedField = null; -// -// for (String validator : datasetProfileValidators) { -// if (obj.has(validator) && isNullOrEmpty(obj.getString(validator)) && isElementVisible(nodeList, validator, visibilityRuleService)) { -// //throw new Exception("Field value of " + validator + " must be filled."); -// failedField = validator; -// break; -// } -// } - -// return failedField; - } - @Override public Description persistStatus(DescriptionStatusPersist model, FieldSet fields) throws IOException { logger.debug(new MapLogEntry("persisting data dmp").And("model", model).And("fields", fields)); @@ -385,6 +328,8 @@ public class DescriptionServiceImpl implements DescriptionService { this.elasticService.persistDescription(data); this.eventBroker.emit(new DescriptionTouchedEvent(data.getId())); + + this.annotationEntityTouchedIntegrationEventHandler.handleDescription(data.getId()); } return this.builderFactory.builder(DescriptionBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Description._id), data); } @@ -706,6 +651,8 @@ public class DescriptionServiceImpl implements DescriptionService { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(id)), Permission.DeleteDescription); this.deleterFactory.deleter(DescriptionDeleter.class).deleteAndSaveByIds(List.of(id), false); + + this.annotationEntityRemovalIntegrationEventHandler.handleDescription(id); } //endregion @@ -774,6 +721,8 @@ public class DescriptionServiceImpl implements DescriptionService { this.elasticService.persistDescription(newDescription); + this.annotationEntityTouchedIntegrationEventHandler.handleDescription(newDescription.getId()); + this.annotationEntityTouchedIntegrationEventHandler.handleDescription(existing.getId()); } //endregion diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/dmp/DmpServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/dmp/DmpServiceImpl.java index 46a7ee2de..25d5fd36a 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/dmp/DmpServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/dmp/DmpServiceImpl.java @@ -24,6 +24,7 @@ import eu.eudat.data.*; import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.event.DmpTouchedEvent; import eu.eudat.event.EventBroker; +import eu.eudat.integrationevent.outbox.annotationentityremoval.AnnotationEntityRemovalIntegrationEventHandler; import eu.eudat.integrationevent.outbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.dmptouched.DmpTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent; @@ -124,6 +125,7 @@ public class DmpServiceImpl implements DmpService { private final DmpTouchedIntegrationEventHandler dmpTouchedIntegrationEventHandler; private final AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler; + private final AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler; private final AuthorizationContentResolver authorizationContentResolver; @Autowired @@ -148,7 +150,7 @@ public class DmpServiceImpl implements DmpService { ValidatorFactory validatorFactory, ElasticService elasticService, DmpTouchedIntegrationEventHandler dmpTouchedIntegrationEventHandler, - AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver) { + AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -170,6 +172,7 @@ public class DmpServiceImpl implements DmpService { this.elasticService = elasticService; this.dmpTouchedIntegrationEventHandler = dmpTouchedIntegrationEventHandler; this.annotationEntityTouchedIntegrationEventHandler = annotationEntityTouchedIntegrationEventHandler; + this.annotationEntityRemovalIntegrationEventHandler = annotationEntityRemovalIntegrationEventHandler; this.authorizationContentResolver = authorizationContentResolver; } @@ -202,13 +205,13 @@ public class DmpServiceImpl implements DmpService { this.dmpTouchedIntegrationEventHandler.handle(DmpTouchedIntegrationEventHandler.buildEventFromPersistModel(model)); - this.annotationEntityTouchedIntegrationEventHandler.handle(AnnotationEntityTouchedIntegrationEventHandler.buildEventFromPersistModel(model)); - this.sendNotification(data); this.assignUsers(data.getId(), this.inviteUserOrAssignUsers(data.getId(), model.getUsers(), false), null, false); this.elasticService.persistDmp(data); + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(data.getId()); + return this.builderFactory.builder(DmpBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Dmp._id, Dmp._hash), data); } @@ -334,6 +337,8 @@ public class DmpServiceImpl implements DmpService { this.deleterFactory.deleter(DmpDeleter.class).deleteAndSaveByIds(List.of(id), false); if (previousFinalized != null) this.elasticService.persistDmp(previousFinalized); + + this.annotationEntityRemovalIntegrationEventHandler.handleDmp(data.getId()); } @Override @@ -445,6 +450,9 @@ public class DmpServiceImpl implements DmpService { this.elasticService.persistDmp(oldDmpEntity); this.elasticService.persistDmp(newDmp); + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(newDmp.getId()); + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(oldDmpEntity.getId()); + return this.builderFactory.builder(DmpBuilder.class).build(BaseFieldSet.build(fields, Dmp._id), newDmp); } @@ -565,9 +573,10 @@ public class DmpServiceImpl implements DmpService { this.entityManager.flush(); - this.elasticService.persistDmp(newDmp); + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(newDmp.getId()); + DmpEntity resultingDmpEntity = this.queryFactory.query(DmpQuery.class).ids(newDmp.getId()).firstAs(fields); return this.builderFactory.builder(DmpBuilder.class).build(fields, resultingDmpEntity); } @@ -615,6 +624,9 @@ public class DmpServiceImpl implements DmpService { .collect(); this.elasticService.persistDmp(dmpEntity); + + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(dmpEntity.getId()); + return this.builderFactory.builder(DmpUserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fieldSet, DmpUser._id, DmpUser._hash), persisted); } @@ -636,6 +648,8 @@ public class DmpServiceImpl implements DmpService { if (dmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getDmpId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.elasticService.persistDmp(dmpEntity); + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(dmpEntity.getId()); + return this.builderFactory.builder(DmpBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Dmp._id, Dmp._hash), data); } @@ -727,8 +741,6 @@ public class DmpServiceImpl implements DmpService { return data; } - - private @NotNull DmpBlueprintValueEntity buildDmpBlueprintValueEntity(DmpBlueprintValuePersist persist){ DmpBlueprintValueEntity data = new DmpBlueprintValueEntity(); if (persist == null) return data; @@ -738,8 +750,6 @@ public class DmpServiceImpl implements DmpService { return data; } - - private @NotNull List buildDmpReferencePersists(DmpPropertiesPersist persist){ List dmpReferencePersists = new ArrayList<>(); if (persist.getDmpBlueprintValues() != null && !persist.getDmpBlueprintValues().isEmpty()){ @@ -839,7 +849,6 @@ public class DmpServiceImpl implements DmpService { this.entityManager.flush(); } - private void patchAndSaveTemplates(UUID id, List models) throws InvalidApplicationException { if (models == null) models = new ArrayList<>(); List items = this.queryFactory.query(DmpDescriptionTemplateQuery.class).isActive(IsActive.Active).dmpIds(id).collect(); @@ -942,6 +951,8 @@ public class DmpServiceImpl implements DmpService { this.entityManager.flush(); this.elasticService.persistDmp(dmp); + + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(dmp.getId()); this.sendNotification(dmp); } @@ -964,12 +975,16 @@ public class DmpServiceImpl implements DmpService { this.updateVersionStatusAndSave(dmp, DmpStatus.Finalized, dmp.getStatus()); this.entityManager.flush(); + + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(dmp.getId()); this.sendNotification(dmp); } // invites public void inviteUserOrAssignUsers(UUID id, List users) throws InvalidApplicationException, JAXBException, IOException { this.inviteUserOrAssignUsers(id, users, true); + + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(id); } @@ -1074,7 +1089,6 @@ public class DmpServiceImpl implements DmpService { return persist.getToken(); } - public void dmpInvitationAccept(String token) { ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.DmpInvitation).isActive(IsActive.Active).first(); @@ -1102,6 +1116,8 @@ public class DmpServiceImpl implements DmpService { action.setStatus(ActionConfirmationStatus.Accepted); this.entityManager.merge(action); + + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(dmpInvitation.getDmpId()); } } diff --git a/dmp-backend/pom.xml b/dmp-backend/pom.xml index f8049f2c1..b3bf79193 100644 --- a/dmp-backend/pom.xml +++ b/dmp-backend/pom.xml @@ -10,7 +10,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.2 + 3.2.1 @@ -41,7 +41,7 @@ org.json json - 20230227 + 20240303 org.springframework @@ -92,7 +92,7 @@ org.elasticsearch.client elasticsearch-rest-high-level-client - 7.6.0 + 7.17.19 org.hibernate.orm @@ -109,7 +109,7 @@ com.jayway.jsonpath json-path - 2.4.0 + 2.9.0 @@ -153,24 +153,8 @@ 3.5 - - org.apache.poi - poi-ooxml - 4.1.2 - - - - org.apache.poi - poi - 4.1.2 - - - org.apache.xmlgraphics - fop - 2.3 - org.jsoup @@ -190,11 +174,6 @@ 1.0.6 - - fr.opensagres.xdocreport - fr.opensagres.xdocreport.itext.extension - 2.0.4 - commons-io @@ -356,18 +335,18 @@ gr.cite queue-inbox - 1.0.0 + 2.1.1 gr.cite queue-outbox - 1.0.0 + 2.1.1 gr.cite rabbitmq-core - 1.0.0 + 2.1.2 diff --git a/dmp-backend/web/src/main/resources/config/db.yml b/dmp-backend/web/src/main/resources/config/db.yml index 519502f2d..fcc2c426e 100644 --- a/dmp-backend/web/src/main/resources/config/db.yml +++ b/dmp-backend/web/src/main/resources/config/db.yml @@ -16,7 +16,7 @@ spring: dialect: org.hibernate.dialect.PostgreSQLDialect hibernate: naming: - physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + physical-strategy: eu.eudat.configurations.db.PrefixPhysicalNamingStrategy implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy datasource: url: ${DB_URL:} @@ -29,3 +29,6 @@ spring: maximum-pool-size: 10 idle-timeout: 600000 max-lifetime: 1800000 + +naming-strategy: + prefix: \ No newline at end of file diff --git a/dmp-backend/web/src/main/resources/config/queue.yml b/dmp-backend/web/src/main/resources/config/queue.yml index 54be62ec4..1de8f8db6 100644 --- a/dmp-backend/web/src/main/resources/config/queue.yml +++ b/dmp-backend/web/src/main/resources/config/queue.yml @@ -35,7 +35,8 @@ queue: user-removal-topic: user.remove dmp-touch-topic: dmp.touch description-touch-topic: description.touch - annotation-entity-touch-topic: annotation.entity.touch + annotation-entities-touch-topic: annotation.entities.touch + annotation-entities-removal-topic: annotation.entities.remove what-you-know-about-me-completed-topic: whatyouknowaboutme.completed generate-file-topic: generate.file rabbitmq: diff --git a/dmp-db-scema/updates/00.01.029_addQueueOutbox.sql b/dmp-db-scema/updates/00.01.029_addQueueOutbox.sql index 7a227c7c6..3409653d8 100644 --- a/dmp-db-scema/updates/00.01.029_addQueueOutbox.sql +++ b/dmp-db-scema/updates/00.01.029_addQueueOutbox.sql @@ -10,7 +10,7 @@ BEGIN exchange character varying(200) COLLATE pg_catalog."default" NOT NULL, route character varying(200) COLLATE pg_catalog."default" NOT NULL, message_id uuid NOT NULL, - notify_status character varying(100) COLLATE pg_catalog."default" NOT NULL, + notify_status smallint NOT NULL, retry_count integer NOT NULL, published_at timestamp without time zone, confirmed_at timestamp without time zone, diff --git a/dmp-db-scema/updates/00.01.030_addQueueInbox.sql b/dmp-db-scema/updates/00.01.030_addQueueInbox.sql index 13bdb9a53..e2296b850 100644 --- a/dmp-db-scema/updates/00.01.030_addQueueInbox.sql +++ b/dmp-db-scema/updates/00.01.030_addQueueInbox.sql @@ -7,14 +7,14 @@ BEGIN CREATE TABLE public."QueueInbox" ( id uuid NOT NULL, - queue character varying(50) COLLATE pg_catalog."default" NOT NULL, - exchange character varying(50) COLLATE pg_catalog."default" NOT NULL, - route character varying(50) COLLATE pg_catalog."default" NOT NULL, + queue character varying(200) COLLATE pg_catalog."default" NOT NULL, + exchange character varying(200) COLLATE pg_catalog."default" NOT NULL, + route character varying(200) COLLATE pg_catalog."default" NOT NULL, application_id character varying(100) COLLATE pg_catalog."default" NOT NULL, message_id uuid NOT NULL, message json NOT NULL, retry_count integer, - status character varying(50) COLLATE pg_catalog."default" NOT NULL, + status smallint NOT NULL, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, tenant uuid, diff --git a/dmp-db-scema/updates/00.01.037_add_ntf_QueueInbox.sql b/dmp-db-scema/updates/00.01.037_add_ntf_QueueInbox.sql index ffbf7d7e6..5aa40b82a 100644 --- a/dmp-db-scema/updates/00.01.037_add_ntf_QueueInbox.sql +++ b/dmp-db-scema/updates/00.01.037_add_ntf_QueueInbox.sql @@ -7,14 +7,14 @@ BEGIN CREATE TABLE public."ntf_QueueInbox" ( id uuid NOT NULL, - queue character varying(50) COLLATE pg_catalog."default" NOT NULL, - exchange character varying(50) COLLATE pg_catalog."default" NOT NULL, - route character varying(50) COLLATE pg_catalog."default" NOT NULL, + queue character varying(200) COLLATE pg_catalog."default" NOT NULL, + exchange character varying(200) COLLATE pg_catalog."default" NOT NULL, + route character varying(200) COLLATE pg_catalog."default" NOT NULL, application_id character varying(100) COLLATE pg_catalog."default" NOT NULL, message_id uuid NOT NULL, message json NOT NULL, retry_count integer, - status character varying(50) COLLATE pg_catalog."default" NOT NULL, + status smallint NOT NULL, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, tenant uuid, diff --git a/dmp-db-scema/updates/00.01.038_add_ntf_QueueOutbox.sql b/dmp-db-scema/updates/00.01.038_add_ntf_QueueOutbox.sql index 6720b85e2..53f79fce0 100644 --- a/dmp-db-scema/updates/00.01.038_add_ntf_QueueOutbox.sql +++ b/dmp-db-scema/updates/00.01.038_add_ntf_QueueOutbox.sql @@ -10,7 +10,7 @@ BEGIN exchange character varying(200) COLLATE pg_catalog."default" NOT NULL, route character varying(200) COLLATE pg_catalog."default" NOT NULL, message_id uuid NOT NULL, - notify_status character varying(100) COLLATE pg_catalog."default" NOT NULL, + notify_status smallint NOT NULL, retry_count integer NOT NULL, published_at timestamp without time zone, confirmed_at timestamp without time zone, diff --git a/dmp-db-scema/updates/00.01.049_Add_ant_QueueInbox.sql b/dmp-db-scema/updates/00.01.049_Add_ant_QueueInbox.sql index 48f593f0b..4fd7093bd 100644 --- a/dmp-db-scema/updates/00.01.049_Add_ant_QueueInbox.sql +++ b/dmp-db-scema/updates/00.01.049_Add_ant_QueueInbox.sql @@ -7,14 +7,14 @@ BEGIN CREATE TABLE public."ant_QueueInbox" ( id uuid NOT NULL, - queue character varying(50) COLLATE pg_catalog."default" NOT NULL, - exchange character varying(50) COLLATE pg_catalog."default" NOT NULL, - route character varying(50) COLLATE pg_catalog."default" NOT NULL, + queue character varying(200) COLLATE pg_catalog."default" NOT NULL, + exchange character varying(200) COLLATE pg_catalog."default" NOT NULL, + route character varying(200) COLLATE pg_catalog."default" NOT NULL, application_id character varying(100) COLLATE pg_catalog."default" NOT NULL, message_id uuid NOT NULL, message json NOT NULL, retry_count integer, - status character varying(50) COLLATE pg_catalog."default" NOT NULL, + status smallint NOT NULL, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, tenant uuid, diff --git a/dmp-db-scema/updates/00.01.050_Add_ant_QueueOutbox.sql b/dmp-db-scema/updates/00.01.050_Add_ant_QueueOutbox.sql index a90e251ae..7bcd5e500 100644 --- a/dmp-db-scema/updates/00.01.050_Add_ant_QueueOutbox.sql +++ b/dmp-db-scema/updates/00.01.050_Add_ant_QueueOutbox.sql @@ -10,7 +10,7 @@ BEGIN exchange character varying(200) COLLATE pg_catalog."default" NOT NULL, route character varying(200) COLLATE pg_catalog."default" NOT NULL, message_id uuid NOT NULL, - notify_status character varying(100) COLLATE pg_catalog."default" NOT NULL, + notify_status smallint NOT NULL, retry_count integer NOT NULL, published_at timestamp without time zone, confirmed_at timestamp without time zone, diff --git a/dmp-frontend/src/app/core/core-service.module.ts b/dmp-frontend/src/app/core/core-service.module.ts index b151c7d8f..004a2de11 100644 --- a/dmp-frontend/src/app/core/core-service.module.ts +++ b/dmp-frontend/src/app/core/core-service.module.ts @@ -47,6 +47,7 @@ import { InAppNotificationService } from './services/inapp-notification/inapp-no import { NotificationService } from './services/notification/notification-service'; import { SemanticsService } from './services/semantic/semantics.service'; import { PrefillingSourceService } from './services/prefilling-source/prefilling-source.service'; +import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service'; // // // This is shared module that provides all the services. Its imported only once on the AppModule. @@ -112,7 +113,8 @@ export class CoreServiceModule { InAppNotificationService, NotificationService, SemanticsService, - PrefillingSourceService + PrefillingSourceService, + VisibilityRulesService ], }; } diff --git a/dmp-frontend/src/app/core/model/dmp/dmp.ts b/dmp-frontend/src/app/core/model/dmp/dmp.ts index c05256f4f..d8fef3f2e 100644 --- a/dmp-frontend/src/app/core/model/dmp/dmp.ts +++ b/dmp-frontend/src/app/core/model/dmp/dmp.ts @@ -13,6 +13,7 @@ import { DmpAssociatedUser, User } from "../user/user"; import { DmpReference } from './dmp-reference'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { EntityType } from '@app/core/common/enum/entity-type'; export interface Dmp extends BaseEntity { label?: string; @@ -166,6 +167,7 @@ export interface PublicDmp extends BaseEntity { dmpReferences: PublicDmpReference[]; dmpUsers: PublicDmpUser[]; descriptions: PublicDescription[]; + entityDois: PublicEntityDoi[]; } export interface PublicDmpReference { @@ -201,3 +203,10 @@ export interface PublicUser { name: string; } +export interface PublicEntityDoi { + id: Guid; + entityType: EntityType; + entityId: Guid; + repositoryId: string; + doi: string; +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.html b/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.html index 43ad7519e..1565b3ffd 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.html +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.html @@ -1,7 +1,8 @@
{{i + 1}} - + + diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts index 4246446c5..89a1ed900 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/components/visibility-rule/description-template-editor-visibility-rule.component.ts @@ -39,6 +39,30 @@ export class DescriptionTemplateEditorRuleComponent implements OnInit { } + isTextType(type: DescriptionTemplateFieldType){ + return type == DescriptionTemplateFieldType.FREE_TEXT || type == DescriptionTemplateFieldType.CHECK_BOX || + type == DescriptionTemplateFieldType.TEXT_AREA || type == DescriptionTemplateFieldType.RICH_TEXT_AREA || + type == DescriptionTemplateFieldType.BOOLEAN_DECISION || type == DescriptionTemplateFieldType.RADIO_BOX || + type == DescriptionTemplateFieldType.CURRENCY || type == DescriptionTemplateFieldType.SELECT; + } + + isTextListType(type: DescriptionTemplateFieldType){ + return type == DescriptionTemplateFieldType.TAGS || type == DescriptionTemplateFieldType.INTERNAL_ENTRIES_DMPS || + type == DescriptionTemplateFieldType.INTERNAL_ENTRIES_DESCRIPTIONS; + } + + isDateType(type: DescriptionTemplateFieldType){ + return type == DescriptionTemplateFieldType.DATE_PICKER; + } + + isReferenceType(type: DescriptionTemplateFieldType){ + return type == DescriptionTemplateFieldType.REFERENCE_TYPES; + } + + isExternalIdentifierType(type: DescriptionTemplateFieldType){ + return type == DescriptionTemplateFieldType.VALIDATION || type == DescriptionTemplateFieldType.DATASET_IDENTIFIER;; + } + targetValidation() { //TODO } diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html index 05dcb1d71..065ede96c 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html @@ -139,9 +139,9 @@ - + {{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.GENERAL-INFO.DATASET-TEMPLATE-USERS' | translate}} - + {{'GENERAL.VALIDATION.REQUIRED' | translate}} diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts index 735a1ab04..baf3c2b52 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts @@ -951,7 +951,7 @@ export class DescriptionTemplateRuleEditorModel implements DescriptionTemplateRu validationErrorModel }); - ['target', 'value'].forEach(keyField => { + ['target', 'textValue', 'textListValue', 'dateValue'].forEach(keyField => { const control = formGroup?.get(keyField); control?.clearValidators(); control?.addValidators(context.getValidation(keyField).validators); diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.html b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.html index a6d8faf1a..2ca81178f 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.html +++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.html @@ -13,10 +13,10 @@
-
+
- +
{{field.label}}
@@ -48,9 +48,9 @@ - + - + {{fieldSetItemPropertiesControl.get('fields').get(field.id).get('value').getRawValue()}} diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service copy.ts b/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service copy.ts deleted file mode 100644 index 0e482fba5..000000000 --- a/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service copy.ts +++ /dev/null @@ -1,456 +0,0 @@ -// import { ApplicationRef, Injectable, NgZone } from '@angular/core'; -// import { AbstractControl, UntypedFormArray, UntypedFormGroup } from '@angular/forms'; -// import { isNumeric } from '@app/utilities/enhancers/utils'; -// import { Observable, Subject } from 'rxjs'; -// import { VisibilityRule } from './models/visibility-rule'; -// import { VisibilityRuleSource } from './models/visibility-rule-source'; -// import { VisibilityRulesContext } from './models/visibility-rules-context'; -// import { DescriptionTemplate, DescriptionTemplatePage, DescriptionTemplateSection } from '@app/core/model/description-template/description-template'; -// import { Rule } from './models/rule'; - -// @Injectable() -// export class VisibilityRulesService { - -// private readonly VISIBILITY_RULE_LOGIC: 'OR' | 'AND' = 'OR'; -// private readonly DEFAULTVISIBILITY = false; - -// private visibilityRuleContext: VisibilityRulesContext; -// private form: AbstractControl; - -// public isVisibleMap: { [key: string]: boolean } = {}; - -// private elementVisibilityMapSubject = new Subject<{ [key: string]: boolean }>(); -// private elementComputationalMap = new Map>(); /// keep saved the values of each form control validity value -// private _changeMade$ = new Subject(); - - -// // get isVisibleMap(): MapWithDefault { -// // // console.log('isVisibleMap'); -// // return this.elementVisibilityMap; - -// // }; /// keep saved the values of each form control validity value - -// constructor( -// public applicationReference: ApplicationRef, -// public ngZone: NgZone -// ) { - -// } - -// getElementVisibilityMapObservable(): Observable<{ [key: string]: boolean }> { -// // this.isVisibleMap -// // console.log('getElementVisibilityMapObservable: '); -// return this.elementVisibilityMapSubject.asObservable(); -// } - -// public checkElementVisibility(id: string): boolean { -// //console.log('checkElementVisibility: ' + id); -// return true; -// // if (this.visibilityRuleContext.rules.filter(item => item.targetControlId === id).length === 0) { return true; } -// // console.log(this.elementVisibilityMap.has(id) ? this.elementVisibilityMap.get(id) : false); -// // return this.elementVisibilityMap.has(id) ? this.elementVisibilityMap.get(id) : false; -// } - -// public buildVisibilityRules(item: Array, form: AbstractControl) { -// this.visibilityRuleContext = new VisibilityRulesContext(); -// this.visibilityRuleContext.buildVisibilityRuleContext(item || []); -// this.form = form; -// this.resetVisibilityRules(); -// } - -// public updateValueAndVisibility(id: string, value: any) { -// //console.log('updateValueAndVisibility: ' + id + ' value: ' + value); -// const visibilityRules = this.visibilityRuleContext.rules.filter(item => item.sourceVisibilityRules.filter(source => source.sourceControlId === id).length > 0); -// if (visibilityRules.length > 0) { -// visibilityRules.forEach(item => this.evaluateVisibility(item, value, id)); -// this.elementVisibilityMapSubject.next(this.isVisibleMap); -// } -// } - -// private evaluateVisibility(visibilityRule: VisibilityRule, value: any, sourceId: string) {// source controlId is the same -// //console.log('evaluateVisibility: ' + visibilityRule + ' value: ' + value + ' sourceId: ' + sourceId); - -// const targetId = visibilityRule.targetControlId; -// const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map(); - - -// if (value instanceof Array) { - -// const parsedSourceControlValues = visibilityRule.sourceVisibilityRules.map(e => this.parseValue(e.sourceControlValue)); -// const parsedValues = value.map(e => this.parseValue(e)); - -// const isVisible = parsedValues.map(v => parsedSourceControlValues.includes(v)).reduce((acc, current) => acc || current, false); - - -// // if(isVisible){ -// // this._emitChangesIfNeeded(visibilityRule.targetControlId, true); -// // this.elementVisibilityMap.set(visibilityRule.targetControlId, true); -// // return; -// // } -// visibilityMap.set(sourceId, isVisible); - -// } else { -// const visibilityDependencySource = visibilityRule.sourceVisibilityRules.filter(x => x.sourceControlId === sourceId); - -// const shouldBeVisible = visibilityDependencySource.reduce((isVisible, x) => { - -// const shouldBeHidden = value !== null && (this.parseValue(value) !== this.parseValue(x.sourceControlValue)); -// return this.VISIBILITY_RULE_LOGIC === 'OR' ? (isVisible || !shouldBeHidden) : (isVisible && !shouldBeHidden); -// // if(value !== null && ) -// }, this.VISIBILITY_RULE_LOGIC === 'AND'); -// visibilityMap.set(sourceId, shouldBeVisible); -// } - - -// this.elementComputationalMap.set(targetId, visibilityMap);// unnessecary - - -// const isVisible = this._computeVisibility(targetId); -// this._emitChangesIfNeeded(targetId, isVisible); -// const previousVisibility = this.isVisibleMap[targetId]; -// this.isVisibleMap[targetId] = isVisible; - -// if (!isVisible && previousVisibility !== isVisible) { -// this.resetControlWithId(this.form, targetId); -// } - - -// // for (let i = 0; i < visibilityRule.sourceVisibilityRules.length; i++) { -// // if (value != null && (this.parseValue(value) !== this.parseValue(visibilityRule.sourceVisibilityRules[i].sourceControlValue))) { -// // this._emitChangesIfNeeded(visibilityRule.targetControlId, false); -// // this.elementVisibilityMap.set(visibilityRule.targetControlId, false); -// // this.resetControlWithId(this.form, visibilityRule.targetControlId); -// // //this.updateValueAndVisibility(visibilityRule.targetControlId, null); -// // // this.clearValues(targetPathKey); -// // return; -// // } -// // } -// // this._emitChangesIfNeeded(visibilityRule.targetControlId, true); -// // this.elementVisibilityMap.set(visibilityRule.targetControlId, true); - -// // this.updateValueAndVisibility(visibilityRule.targetControlId, null); -// } - - -// private _computeVisibility(targetId: string): boolean { -// //console.log('_computeVisibility: ' + targetId); - -// const visibilityMap = this.elementComputationalMap.get(targetId); -// const values = visibilityMap.values(); -// let currentVal = values.next(); -// let visibilityValues: boolean[] = []; -// while (!currentVal.done) { -// visibilityValues.push(currentVal.value); -// currentVal = values.next(); -// } - - -// if (visibilityValues.length) { -// return visibilityValues.reduce((r, c) => { -// if (this.VISIBILITY_RULE_LOGIC === 'OR') { -// return r || c; -// } else { -// return r && c; -// } -// }, visibilityValues[0]); -// } - -// return this.DEFAULTVISIBILITY; -// } - -// private resetVisibilityRules() { -// //console.log('resetVisibilityRules: '); - -// this.isVisibleMap = {}; -// this.elementComputationalMap.clear(); -// this.elementComputationalMap = new Map>(); -// this._populateComputationMap(); /// !IMPORTANT FOR THE AND LOGIC -// this._changeMade$.next(); -// } - -// private _populateComputationMap(): void { -// //console.log('_populateComputationMap: '); -// // this.visibilityRuleContext.rules.forEach(rule => { -// // const targetId = rule.targetControlId; -// // const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map(); -// // rule.sourceVisibilityRules.forEach(vr => { -// // visibilityMap.set(vr.sourceControlId, this.DEFAULTVISIBILITY); -// // }); -// // this.elementComputationalMap.set(targetId, visibilityMap); -// // }); -// } - -// parseValue(value: any) { -// if (typeof value === 'string') { -// if (isNumeric(value)) { return value; } -// else if (value === 'true') { -// return true; -// } -// else if (value === 'false') { -// return false; -// } -// else { return this.translate(value); } -// } else { return value; } -// } - -// search(path, obj, target) { -// for (const k in obj) { -// if (obj.hasOwnProperty(k)) { -// if (obj[k] === target) { -// return path + '.' + k; -// } else if (typeof obj[k] === 'object') { -// const result = this.search(path + '.' + k, obj[k], target); -// if (result) { -// return result; -// } -// } -// } -// } -// return false; -// } - -// scanIfChildsOfCompositeFieldHasVisibleItems(compositeFieldParent: UntypedFormGroup): boolean { -// // console.log('scanIfChildsOfCompositeFieldHasVisibleItems: ' + compositeFieldParent); - -// //TODO: implement this -// return true; -// // let isVisible = false; -// // ((compositeFieldParent.get('fields'))).controls.forEach(element => { -// // if (this.checkElementVisibility(element.get('id').value)) { -// // isVisible = true; -// // } -// // }); -// // return isVisible; -// } - -// private translate(item: any) { -// try { -// return JSON.parse(item).value; -// } catch (error) { -// return item; -// } -// } - -// private resetControlWithId(formControl: AbstractControl, id: string) { -// //'resetControlWithId: ' + id); -// //TODO: implement this -// // if (formControl instanceof UntypedFormGroup) { -// // if ((formControl as UntypedFormGroup).contains('id') && (formControl as UntypedFormGroup).contains('value') && (formControl as UntypedFormGroup).get('id').value === id) { -// // this.resetFieldFormGroup(formControl); -// // } if ((formControl as UntypedFormGroup).contains('id') && (formControl as UntypedFormGroup).contains('fields') && (formControl as UntypedFormGroup).get('id').value === id) { -// // this.resetCompositeFieldFormGroup(formControl); -// // } else { -// // Object.keys(formControl.controls).forEach(item => { -// // const control = formControl.get(item); -// // this.resetControlWithId(control, id); -// // }); -// // } -// // } else if (formControl instanceof UntypedFormArray) { -// // formControl.controls.forEach(item => { -// // this.resetControlWithId(item, id); -// // }); -// // } -// } - -// private resetFieldFormGroup(formGroup: UntypedFormGroup) { -// //console.log('resetFieldFormGroup: ' + formGroup); -// //TODO: implement this -// // const renderStyle = formGroup.getRawValue().viewStyle.renderStyle; -// // if (renderStyle === DatasetProfileFieldViewStyle.Validation || renderStyle === DatasetProfileFieldViewStyle.DatasetIdentifier) { -// // formGroup.get('value').setValue({ identifier: '', type: '' }); -// // } else { -// // formGroup.get('value').setValue(formGroup.get('defaultValue').value ? this.parseValue(formGroup.get('defaultValue').value.value) : undefined); -// // } - -// } - -// private resetCompositeFieldFormGroup(formGroup: UntypedFormGroup) { -// //console.log('resetCompositeFieldFormGroup: ' + formGroup); -// //TODO: implement this -// // (formGroup.get('fields') as UntypedFormArray).controls.forEach((element: UntypedFormGroup) => { -// // this.resetFieldFormGroup(element); -// // }); -// // (formGroup.get('multiplicityItems') as UntypedFormArray).controls.splice(0); -// } -// private _emitChangesIfNeeded(id: string, valueToBeSet: boolean) { -// if (this.isVisibleMap[id]) { -// if (this.isVisibleMap[id] != valueToBeSet) { -// this._changeMade$.next(); -// } -// } else { -// this._changeMade$.next(); -// } -// } -// public get visibilityChange() { -// return this._changeMade$.asObservable(); -// } -// public getVisibilityDependency(targetId: string): VisibilityRuleSource[] | null { -// //console.log('getVisibilityDependency: ' + targetId); -// return this.visibilityRuleContext.rules.reduce((hasDependency, rule) => { -// if (hasDependency) return hasDependency; - -// if (rule.targetControlId === targetId) { -// return rule.sourceVisibilityRules; -// } - -// return null; -// }, null) as VisibilityRuleSource[]; -// } - -// public getVisibilityTargets(sourceId: string): string[] { -// console.log('getVisibilityTargets: ' + sourceId); -// return this.visibilityRuleContext.rules.filter(x => { -// const result = x.sourceVisibilityRules.filter(y => y.sourceControlId === sourceId); -// return result.length; -// }).map(x => x.targetControlId); -// } - -// // public appendVisibilityRule(rule: VisibilityRule): void{ - -// // const existingTargetRule = this.visibilityRuleContext.rules.find( r => r.targetControlId === rule.targetControlId); - -// // if(existingTargetRule){ -// // rule.sourceVisibilityRules.forEach(svr =>{ -// // existingTargetRule.sourceVisibilityRules.push(svr); -// // }); -// // }else{ -// // this.visibilityRuleContext.rules.push(rule); -// // } - - -// // } - - -// //removes rule that has the specific id either as a source either as a target -// public removeAllIdReferences(id: string): void { -// //console.log('removeAllIdReferences: ' + id); - -// // * Remove from visibility rues and visibility rules context - -// //remove as a target -// const temp = this.visibilityRuleContext.rules.map((x, i) => (x.targetControlId === id) ? i : null); -// const indexes = temp.filter(x => x !== null); -// indexes.reverse().forEach(index => this.visibilityRuleContext.rules.splice(index, 1)); -// this.isVisibleMap[id] = undefined; - - - -// //remove as a source -// const tbd = this.visibilityRuleContext.rules.reduce((to_be_deleted, rule, ruleIdx) => { -// const idxs = rule.sourceVisibilityRules.map((x, i) => (x.sourceControlId === id) ? i : null).filter(x => x !== null); -// idxs.reverse().forEach(index => rule.sourceVisibilityRules.splice(index, 1)); - -// if (!rule.sourceVisibilityRules.length) { -// to_be_deleted.push(ruleIdx); -// } -// return to_be_deleted -// }, []); - - -// //clean up empty -// tbd.reverse().forEach(index => { -// this.visibilityRuleContext.rules.splice(index, 1); -// }); - - - -// // * Remove from computational map - -// // as a target -// if (this.elementComputationalMap.get(id)) { -// this.elementComputationalMap.delete(id); -// } - - -// // as a source -// const keyIterator = this.elementComputationalMap.keys(); -// let currentKey = keyIterator.next(); - - -// while (!currentKey.done) { -// const currentVals = this.elementComputationalMap.get(currentKey.value); -// currentVals.delete(id); -// currentKey = keyIterator.next(); -// } -// } - - -// public addNewRule(rule: Rule, currentVisibility = this.DEFAULTVISIBILITY): void { -// //console.log('addNewRule: ' + rule + ' currentVisibility: ' + currentVisibility); - -// const targetId = rule.targetField; -// const sourceId = rule.sourceField; -// this.visibilityRuleContext.addToVisibilityRulesContext(rule); - - -// let visibilityMap = this.elementComputationalMap.get(targetId); - -// if (!visibilityMap) { -// visibilityMap = new Map(); -// this.elementComputationalMap.set(targetId, visibilityMap); -// } - -// visibilityMap.set(sourceId, currentVisibility); -// const isVisible = this._computeVisibility(targetId); - -// this._emitChangesIfNeeded(targetId, isVisible); -// this.isVisibleMap[targetId] = isVisible; -// this.elementVisibilityMapSubject.next(this.isVisibleMap); -// } - - -// /** -// * Check what sourceId hides or shows the target field -// * return true if no rule found -// */ -// public checkTargetVisibilityProvidedBySource(sourceId: string, targetId: string): boolean { -// //console.log('checkTargetVisibilityProvidedBySource: ' + sourceId + ' targetId: ' + targetId); - -// const computationalMap = this.elementComputationalMap.get(targetId); -// if (computationalMap) { -// return !!computationalMap.get(sourceId); -// } - -// return true; -// } - -// public getVisibilityRulesFromDescriptionTempalte(descriptionTemplate: DescriptionTemplate): Rule[] { -// //console.log('getVisibilityRulesFromDescriptionTempalte: ' + descriptionTemplate); -// const result: Rule[] = this.getVisibilityRulesFromDescriptionTempalteSections(descriptionTemplate?.definition?.pages); -// return result; -// } - -// public getVisibilityRulesFromDescriptionTempalteSections(pages: DescriptionTemplatePage[]): Rule[] { -// //console.log('getVisibilityRulesFromDescriptionTempalteSections: ' + sections); -// const result: Rule[] = []; -// pages.forEach(page => { -// page?.sections?.forEach(section => { -// if (section.sections != null) { result.push(...this.getVisibilityRulesFromDescriptionTempalteSections(section.sections)); }; -// section?.fieldSets?.forEach(fieldSet => { -// fieldSet?.fields?.forEach(field => { -// field.visibilityRules?.forEach(visibilityRule => { -// result.push({ -// sourceField: field.id.toString(), -// targetField: visibilityRule.target, -// dateValue: visibilityRule.dateValue, -// textValue: visibilityRule.textValue, -// textListValue: visibilityRule.textListValue -// }) -// }); -// }); -// }); -// }); -// }); -// return result; -// } -// } - -// class MapWithDefault extends Map { -// get(key) { -// //console.log('MapWithDefault'); -// if (!this.has(key)) { -// this.set(key, true); -// } -// return super.get(key); -// } -// } diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service.ts b/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service.ts index dd9cd7643..c1b43bdc1 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-form/visibility-rules/visibility-rules.service.ts @@ -263,7 +263,7 @@ export class VisibilityRulesService { const fieldType: DescriptionTemplateFieldType = rule.field != null && rule.field.data != null ? rule.field.data.fieldType : DescriptionTemplateFieldType.FREE_TEXT; if ([DescriptionTemplateFieldType.FREE_TEXT, DescriptionTemplateFieldType.CHECK_BOX, DescriptionTemplateFieldType.TEXT_AREA, DescriptionTemplateFieldType.RICH_TEXT_AREA, DescriptionTemplateFieldType.UPLOAD, DescriptionTemplateFieldType.BOOLEAN_DECISION, - DescriptionTemplateFieldType.RADIO_BOX, DescriptionTemplateFieldType.CURRENCY, DescriptionTemplateFieldType.SELECT].includes(fieldType) && field.textValue != null && field.textValue.length > 0) { + DescriptionTemplateFieldType.RADIO_BOX, DescriptionTemplateFieldType.CURRENCY].includes(fieldType) && field.textValue != null && field.textValue.length > 0) { if (DescriptionTemplateFieldType.UPLOAD == fieldType){ return false; //not apply visibility logic } else { diff --git a/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java b/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java index 519148da3..4ec5aee6b 100644 --- a/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java +++ b/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/InAppNotificationController.java @@ -71,10 +71,10 @@ public class InAppNotificationController { @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(); + + this.censorFactory.censor(InAppNotificationCensor.class).censor(lookup.getProject(), userId); + 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()); @@ -88,12 +88,13 @@ public class InAppNotificationController { @GetMapping("{id}") @Transactional - public InAppNotification Get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + public InAppNotification Get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { logger.debug(new MapLogEntry("retrieving" + InAppNotification.class.getSimpleName()).And("id", id).And("fields", fieldSet)); - this.censorFactory.censor(InAppNotificationCensor.class).censor(fieldSet); + UUID userId = this.userScope.getUserId(); + this.censorFactory.censor(InAppNotificationCensor.class).censor(fieldSet, userId); - InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).authorize(AuthorizationFlags.OwnerOrPermission).ids(id); + InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).authorize(AuthorizationFlags.OwnerOrPermission).userId(userId).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())); @@ -142,7 +143,7 @@ public class InAppNotificationController { 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)); + this.censorFactory.censor(InAppNotificationCensor.class).censor(new BaseFieldSet(InAppNotification.Field.ID), userId); InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).isActive(IsActive.Active).trackingState(NotificationInAppTracking.STORED).userId(userId); int count = Math.toIntExact(query.count()); diff --git a/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java b/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java index 8ff1323fe..d09539f67 100644 --- a/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java +++ b/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/UserNotificationPreferenceController.java @@ -66,7 +66,7 @@ public class UserNotificationPreferenceController { public QueryResult query(@RequestBody UserNotificationPreferenceLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", UserNotificationPreference.class.getSimpleName()); - this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(lookup.getProject()); + this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(lookup.getProject(), null); UserNotificationPreferenceQuery query = lookup.enrich(this.queryFactory); List data = query.collectAs(lookup.getProject()); @@ -83,7 +83,7 @@ public class UserNotificationPreferenceController { 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); + this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(fieldSet, userId); UserNotificationPreferenceQuery query = this.queryFactory.query(UserNotificationPreferenceQuery.class).userId(userId); UserNotificationPreference model = this.builderFactory.builder(UserNotificationPreferenceBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet)); diff --git a/notification-service/notification-web/src/main/resources/config/cache.yml b/notification-service/notification-web/src/main/resources/config/cache.yml index 69541500e..ec7370d42 100644 --- a/notification-service/notification-web/src/main/resources/config/cache.yml +++ b/notification-service/notification-web/src/main/resources/config/cache.yml @@ -39,9 +39,6 @@ cache: enableRecordStats: false expireAfterWriteSeconds: 60 mapCaches: - - names: [ cacheB ] - allowNullValues: true - storeByValue: true apiKey: name: apikey keyPattern: resolve_$keyhash$:v0 diff --git a/notification-service/notification-web/src/main/resources/config/db.yml b/notification-service/notification-web/src/main/resources/config/db.yml index aa3caed0b..0de4aa778 100644 --- a/notification-service/notification-web/src/main/resources/config/db.yml +++ b/notification-service/notification-web/src/main/resources/config/db.yml @@ -1,7 +1,11 @@ spring: jpa: properties: + org: + hibernate: + flushMode: MANUAL hibernate: + globally_quoted_identifiers: true ddl-auto: validate dialect: org.hibernate.dialect.PostgreSQLDialect hibernate: @@ -16,9 +20,10 @@ spring: hikari: connection-timeout: 30000 minimum-idle: 3 - maximum-pool-size: 5 + maximum-pool-size: 10 idle-timeout: 600000 max-lifetime: 1800000 + naming-strategy: prefix: ntf_ diff --git a/notification-service/notification-web/src/main/resources/config/email.yml b/notification-service/notification-web/src/main/resources/config/email.yml index fb7c94ba0..364788161 100644 --- a/notification-service/notification-web/src/main/resources/config/email.yml +++ b/notification-service/notification-web/src/main/resources/config/email.yml @@ -1,14 +1,14 @@ spring: mail: - host: ${MAIL_HOST:} - port: ${MAIL_PORT:} - username: ${MAIL_USERNAME:} - password: ${MAIL_PASSWORD:} + host: ${MAIL_HOST} + port: ${MAIL_PORT} + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} properties: mail: smtp: - auth: ${MAIL_AUTH:} + auth: ${MAIL_AUTH} starttls: - enable: ${MAIL_TLS:} + enable: ${MAIL_TLS} email: - address: ${MAIL_ADDRESS:} \ No newline at end of file + address: ${MAIL_ADDRESS} \ No newline at end of file diff --git a/notification-service/notification-web/src/main/resources/config/idpclaims.yml b/notification-service/notification-web/src/main/resources/config/idpclaims.yml index 3372e4ca7..97ff4a10e 100644 --- a/notification-service/notification-web/src/main/resources/config/idpclaims.yml +++ b/notification-service/notification-web/src/main/resources/config/idpclaims.yml @@ -37,3 +37,5 @@ idpclient: - type: azp Authorities: - type: authorities + ExternalProviderName: + - type: identity_provider \ No newline at end of file diff --git a/notification-service/notification-web/src/main/resources/config/permissions.yml b/notification-service/notification-web/src/main/resources/config/permissions.yml index d7247f8d8..a6d600777 100644 --- a/notification-service/notification-web/src/main/resources/config/permissions.yml +++ b/notification-service/notification-web/src/main/resources/config/permissions.yml @@ -5,26 +5,26 @@ permissions: # Tenants BrowseTenant: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false EditTenant: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteTenant: roles: - - ic-sti-superuser + - Admin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false AllowNoTenant: roles: - - ic-sti-superuser + - Admin claims: [ ] clients: [ ] allowAnonymous: false @@ -32,21 +32,19 @@ permissions: # Users BrowseUser: roles: - - ic-sti-superuser - - tenantadmin + - Admin clients: [ ] allowAnonymous: true allowAuthenticated: false EditUser: roles: - - admin - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteUser: roles: - - ic-sti-superuser + - Admin claims: [ ] clients: [ ] allowAnonymous: false @@ -54,20 +52,19 @@ permissions: # UserContactInfo BrowseUserContactInfo: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: true allowAuthenticated: false EditUserContactInfo: roles: - - ic-sti-superuser - - user + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteUserContactInfo: roles: - - ic-sti-superuser + - Admin claims: [ ] clients: [ ] allowAnonymous: false @@ -94,27 +91,26 @@ permissions: #Tenant Configuration BrowseTenantConfiguration: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false EditTenantConfiguration: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false #User Notification Preference BrowseUserNotificationPreference: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: true allowAuthenticated: false EditUserNotificationPreference: roles: - - ic-sti-superuser - - user + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -122,26 +118,25 @@ permissions: # ViewPage Permissions ViewNotificationPage: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false ViewNotificationEventRulePage: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false ViewInAppNotificationPage: roles: - - ic-sti-superuser - - tenantadmin + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false ViewNotificationTemplatePage: roles: - - ic-sti-superuser + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -170,7 +165,6 @@ permissions: BrowseInAppNotification: roles: - Admin - - User clients: [ ] allowAnonymous: false allowAuthenticated: false diff --git a/notification-service/notification-web/src/main/resources/config/security-devel.yml b/notification-service/notification-web/src/main/resources/config/security-devel.yml new file mode 100644 index 000000000..dbb097b19 --- /dev/null +++ b/notification-service/notification-web/src/main/resources/config/security-devel.yml @@ -0,0 +1,6 @@ +web: + security: + idp: + resource: + jwt: + audiences: [ "dmp_notification" ] \ No newline at end of file diff --git a/notification-service/notification-web/src/main/resources/config/security.yml b/notification-service/notification-web/src/main/resources/config/security.yml index 3024921aa..8f41034ac 100644 --- a/notification-service/notification-web/src/main/resources/config/security.yml +++ b/notification-service/notification-web/src/main/resources/config/security.yml @@ -2,19 +2,13 @@ web: security: enabled: true authorized-endpoints: [ api ] - allowed-endpoints: [ public, dataset, master-item, test ] + allowed-endpoints: [ public ] idp: api-key: - enabled: true - authorization-header: Authorization - client-id: ${IDP_APIKEY_CLIENT_ID} - client-secret: ${IDP_APIKEY_CLIENT_SECRET} - scope: ${IDP_APIKEY_SCOPE} + enabled: false resource: - token-type: JWT - opaque: - client-id: ${IDP_OPAQUE_CLIENT_ID} - client-secret: ${IDP_OPAQUE_CLIENT_SECRET} + token-type: JWT #| opaque jwt: claims: [ role, x-role ] - issuer-uri: ${IDP_ISSUER_URI:} \ No newline at end of file + issuer-uri: ${IDP_ISSUER_URI} + validIssuer: ${IDP_ISSUER_URI} \ No newline at end of file diff --git a/notification-service/notification-web/src/main/resources/config/server.yml b/notification-service/notification-web/src/main/resources/config/server.yml index bc9a15d9f..9a4a97cbc 100644 --- a/notification-service/notification-web/src/main/resources/config/server.yml +++ b/notification-service/notification-web/src/main/resources/config/server.yml @@ -1,3 +1,13 @@ server: port: ${WEB_PORT} - forward-headers-strategy: NONE \ No newline at end of file + forward-headers-strategy: NONE + tomcat: + threads: + max: 20 + max-connections: 10000 + + spring: + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB \ No newline at end of file diff --git a/notification-service/notification/pom.xml b/notification-service/notification/pom.xml index d820e9b6f..da75fb081 100644 --- a/notification-service/notification/pom.xml +++ b/notification-service/notification/pom.xml @@ -63,7 +63,7 @@ gr.cite oidc-authn - 1.0.0 + 2.2.1 gr.cite @@ -73,7 +73,7 @@ gr.cite oidc-authz - 1.0.0 + 2.1.0 gr.cite diff --git a/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java b/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java index c59bccb11..97126f92d 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/data/QueueInboxEntity.java @@ -2,6 +2,7 @@ package gr.cite.notification.data; import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.conventers.QueueInboxStatusConverter; import gr.cite.notification.data.types.JsonSQLType; import gr.cite.queueinbox.entity.QueueInbox; import gr.cite.queueinbox.entity.QueueInboxStatus; @@ -21,17 +22,17 @@ public class QueueInboxEntity implements QueueInbox { public static final String _id = "id"; - @Column(name = "\"queue\"", nullable = false, length = 50) + @Column(name = "\"queue\"", nullable = false, length = 200) private String queue; public static final String _queue = "queue"; - @Column(name = "\"exchange\"", nullable = false, length = 50) + @Column(name = "\"exchange\"", nullable = false, length = 200) private String exchange; public static final String _exchange = "exchange"; - @Column(name = "\"route\"", nullable = false, length = 50) + @Column(name = "\"route\"", nullable = false, length = 200) private String route; public static final String _route = "route"; @@ -62,11 +63,11 @@ public class QueueInboxEntity implements QueueInbox { public static final String _tenantId = "tenantId"; - @Column(name = "\"status\"", length = 50, nullable = false) - @Enumerated(EnumType.STRING) + @Column(name = "\"status\"", nullable = false) + @Convert(converter = QueueInboxStatusConverter.class) private QueueInboxStatus status; + public final static String _status = "status"; - public static final String _status = "status"; @Column(name = "\"created_at\"", nullable = false) private Instant createdAt; @@ -79,7 +80,7 @@ public class QueueInboxEntity implements QueueInbox { public static final String _updatedAt = "updatedAt"; - @Column(name = "\"is_active\"", length = 20, nullable = false) + @Column(name = "\"is_active\"", nullable = false) @Convert(converter = IsActiveConverter.class) private IsActive isActive; diff --git a/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java b/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java index e62f2ec4d..232aa169f 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/data/QueueOutboxEntity.java @@ -2,6 +2,7 @@ package gr.cite.notification.data; import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.data.conventers.IsActiveConverter; +import gr.cite.notification.data.conventers.QueueOutboxNotifyStatusConverter; import gr.cite.queueoutbox.entity.QueueOutbox; import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; import jakarta.persistence.*; @@ -19,12 +20,12 @@ public class QueueOutboxEntity implements QueueOutbox { public static final String _id = "id"; - @Column(name = "\"exchange\"", nullable = false, length = 50) + @Column(name = "\"exchange\"", nullable = false, length = 200) private String exchange; public static final String _exchange = "exchange"; - @Column(name = "\"route\"", length = 50) + @Column(name = "\"route\"", length = 200) private String route; public static final String _route = "route"; @@ -39,11 +40,10 @@ public class QueueOutboxEntity implements QueueOutbox { public static final String _message = "message"; - @Column(name = "\"notify_status\"", length = 20, nullable = false) - @Enumerated(EnumType.STRING) + @Column(name = "\"notify_status\"", nullable = false) + @Convert(converter = QueueOutboxNotifyStatusConverter.class) private QueueOutboxNotifyStatus notifyStatus; - - public static final String _notifyStatus = "notifyStatus"; + public final static String _notifyStatus = "notifyStatus"; @Column(name = "\"retry_count\"", nullable = false) private Integer retryCount; @@ -75,7 +75,7 @@ public class QueueOutboxEntity implements QueueOutbox { public static final String _updatedAt = "updatedAt"; - @Column(name = "\"is_active\"", length = 20, nullable = false) + @Column(name = "\"is_active\"", nullable = false) @Convert(converter = IsActiveConverter.class) private IsActive isActive; diff --git a/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/QueueInboxStatusConverter.java b/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/QueueInboxStatusConverter.java new file mode 100644 index 000000000..27e108488 --- /dev/null +++ b/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/QueueInboxStatusConverter.java @@ -0,0 +1,19 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.queueinbox.entity.QueueInboxStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class QueueInboxStatusConverter implements AttributeConverter { + @Override + public Short convertToDatabaseColumn(QueueInboxStatus value) { + if (value == null) throw new IllegalArgumentException("Value could not be null for: " + this.getClass().getSimpleName()); + return value.getValue(); + } + + @Override + public QueueInboxStatus convertToEntityAttribute(Short dbData) { + return QueueInboxStatus.of(dbData); + } +} diff --git a/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/QueueOutboxNotifyStatusConverter.java b/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/QueueOutboxNotifyStatusConverter.java new file mode 100644 index 000000000..e7c5bfb52 --- /dev/null +++ b/notification-service/notification/src/main/java/gr/cite/notification/data/conventers/QueueOutboxNotifyStatusConverter.java @@ -0,0 +1,19 @@ +package gr.cite.notification.data.conventers; + +import gr.cite.queueoutbox.entity.QueueOutboxNotifyStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class QueueOutboxNotifyStatusConverter implements AttributeConverter { + @Override + public Short convertToDatabaseColumn(QueueOutboxNotifyStatus value) { + if (value == null) throw new IllegalArgumentException("Value could not be null for: " + this.getClass().getSimpleName()); + return value.getValue(); + } + + @Override + public QueueOutboxNotifyStatus convertToEntityAttribute(Short dbData) { + return QueueOutboxNotifyStatus.of(dbData); + } +} diff --git a/notification-service/notification/src/main/java/gr/cite/notification/model/User.java b/notification-service/notification/src/main/java/gr/cite/notification/model/User.java index 3ea51b87c..7d3596491 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/model/User.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/model/User.java @@ -29,8 +29,6 @@ public class User { public static final String _hash = "hash"; private String hash; - public static final String _tenantUsers = "tenantUsers"; - public UUID getId() { return id; } diff --git a/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.java b/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.java index d20a14b4c..20b4ad8cf 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/InAppNotificationCensor.java @@ -1,6 +1,7 @@ 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.tools.fieldset.FieldSet; @@ -12,6 +13,9 @@ 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 InAppNotificationCensor extends BaseCensor { @@ -24,9 +28,9 @@ public class InAppNotificationCensor extends BaseCensor { this.authService = authService; } - public void censor(FieldSet fields) { + public void censor(FieldSet fields, UUID userId) { logger.debug(new DataLogEntry("censoring fields", fields)); if (this.isEmpty(fields)) return; - this.authService.authorizeForce(Permission.BrowseInAppNotification); + this.authService.authorizeAtLeastOneForce(userId != null ? List.of(new OwnedResource(userId)) : null, Permission.BrowseInAppNotification); } } diff --git a/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserCensor.java b/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserCensor.java index ffe0677b3..11afb5a08 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserCensor.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserCensor.java @@ -43,8 +43,5 @@ public class UserCensor extends BaseCensor { 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/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.java b/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.java index 99f0a7090..13847a527 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/model/censorship/UserNotificationPreferenceCensor.java @@ -1,6 +1,7 @@ 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.tools.fieldset.FieldSet; @@ -12,6 +13,9 @@ 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 UserNotificationPreferenceCensor extends BaseCensor { @@ -24,9 +28,9 @@ public class UserNotificationPreferenceCensor extends BaseCensor { this.authService = authService; } - public void censor(FieldSet fields) { + public void censor(FieldSet fields, UUID userId) { logger.debug(new DataLogEntry("censoring fields", fields)); if (this.isEmpty(fields)) return; - this.authService.authorizeForce(Permission.BrowseUserNotificationPreference); + this.authService.authorizeAtLeastOneForce(userId != null ? List.of(new OwnedResource(userId)) : null, Permission.BrowseUserNotificationPreference); } } diff --git a/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java b/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java index c6c349d6e..1c928e53a 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/service/inappnotification/InAppNotificationServiceImpl.java @@ -3,6 +3,7 @@ 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.common.scope.user.UserScope; import gr.cite.notification.data.InAppNotificationEntity; import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.model.deleter.InAppNotificationDeleter; @@ -36,13 +37,15 @@ public class InAppNotificationServiceImpl implements InAppNotificationService { private final AuthorizationService authService; private final DeleterFactory deleterFactory; + private final UserScope userScope; @Autowired - public InAppNotificationServiceImpl(QueryFactory queryFactory, TenantScopedEntityManager entityManager, AuthorizationService authService, DeleterFactory deleterFactory) { + public InAppNotificationServiceImpl(QueryFactory queryFactory, TenantScopedEntityManager entityManager, AuthorizationService authService, DeleterFactory deleterFactory, UserScope userScope) { this.queryFactory = queryFactory; this.entityManager = entityManager; this.authService = authService; this.deleterFactory = deleterFactory; + this.userScope = userScope; } public void markAsRead(UUID id) { @@ -52,9 +55,10 @@ public class InAppNotificationServiceImpl implements InAppNotificationService { public void markAsRead(List ids) { try { logger.debug(new DataLogEntry("marking as read in-app notifications", ids)); - + UUID userId = this.userScope.getUserId(); List items = this.queryFactory.query(InAppNotificationQuery.class) .ids(ids) + .userId(userId) .collect(); Instant now = Instant.now(); diff --git a/notification-service/pom.xml b/notification-service/pom.xml index a8e1700a0..ea8cdafce 100644 --- a/notification-service/pom.xml +++ b/notification-service/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.2 + 3.2.1 @@ -136,18 +136,18 @@ gr.cite rabbitmq-core - 2.1.1 + 2.1.2 gr.cite queue-inbox - 1.0.0 + 2.1.1 gr.cite queue-outbox - 1.0.0 + 2.1.1