From af5194132494201e64f42dd4d1c9d87214e15773 Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Fri, 13 Jun 2025 12:07:08 +0200 Subject: [PATCH 1/9] [NAE-2116] Frontend remote configuration - Refactor repositories and services to support pagination - Updated repository and service layer methods to utilize pagination where applicable, improving scalability and performance. - Replaced unpaged results with pageable implementations across multiple data queries and ensured method signatures reflect these changes. --- .gitignore | 1 + .../auth/service/RegistrationService.java | 8 ++- .../engine/auth/web/UserController.java | 2 +- .../ImpersonationAuthorizationService.java | 2 +- .../petrinet/service/PetriNetService.java | 3 +- .../startup/runner/DefaultFiltersRunner.java | 19 ++--- .../startup/runner/SuperCreatorRunner.java | 2 +- .../auth/service/AuthorityServiceImpl.java | 40 +++-------- .../engine/auth/service/GroupServiceImpl.java | 25 ++++--- .../engine/auth/service/UserServiceImpl.java | 9 +-- .../auth/repository/AuthorityRepository.java | 7 +- .../auth/repository/GroupRepository.java | 5 +- .../auth/repository/UserRepository.java | 69 +++++++++---------- .../engine/auth/service/AuthorityService.java | 14 ++-- .../engine/auth/service/GroupService.java | 8 +-- .../engine/auth/service/UserService.java | 8 +-- 16 files changed, 104 insertions(+), 118 deletions(-) diff --git a/.gitignore b/.gitignore index 450790f1fd..a20b5cf8c0 100644 --- a/.gitignore +++ b/.gitignore @@ -208,6 +208,7 @@ buildNumber.properties *.iml out gen +/storage/ ### Groovy template # .gitignore created from Groovy contributors in https://github.com/apache/groovy/blob/master/.gitignore diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java index 9d56ca0ce0..fa1baeb34f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java @@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @@ -59,7 +61,7 @@ public void removeExpiredUsers() { @Scheduled(cron = "0 0 1 * * *") public void resetExpiredToken() { log.info("Resetting expired user tokens"); - List users = userService.findAllByStateAndExpirationDateBefore(UserState.BLOCKED, LocalDateTime.now(), null); + Page users = userService.findAllByStateAndExpirationDateBefore(UserState.BLOCKED, LocalDateTime.now(), null, Pageable.unpaged()); if (users == null || users.isEmpty()) { log.info("There are none expired tokens. Everything is awesome."); return; @@ -69,8 +71,8 @@ public void resetExpiredToken() { user.setToken(null); user.setExpirationDate(null); }); - users = userService.saveUsers(users.stream().map(u -> (IUser) u).collect(Collectors.toList())); - log.info("Reset " + users.size() + " expired user tokens"); + userService.saveUsers(users.stream().map(u -> (IUser) u).collect(Collectors.toList())); + log.info("Reset " + users.getContent().size() + " expired user tokens"); } @Override diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java index d5dda0d26b..fc1ac2661c 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java @@ -260,7 +260,7 @@ public ResponseEntity assignNegativeRolesToUser(@PathVariable(" @ApiResponse(responseCode = "500", description = "Internal server error") }) public ResponseEntity> getAllAuthorities() { - return ResponseEntity.ok(authorityService.findAll()); + return ResponseEntity.ok(authorityService.findAll(Pageable.unpaged()).stream().toList()); } @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") diff --git a/application-engine/src/main/java/com/netgrif/application/engine/impersonation/service/ImpersonationAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/impersonation/service/ImpersonationAuthorizationService.java index 344897081a..9a996e420c 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/impersonation/service/ImpersonationAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/impersonation/service/ImpersonationAuthorizationService.java @@ -96,7 +96,7 @@ public List getAuthorities(List configs, IUser impersonated) { return new ArrayList<>(); } Set authIds = extractSetFromField(configs, "impersonated_authorities"); - return authorityService.findAllByIds(new ArrayList<>(authIds)).stream() + return authorityService.findAllByIds(new ArrayList<>(authIds), Pageable.unpaged()).stream() .filter(configAuth -> impersonated.getAuthorities().stream().anyMatch(userAuth -> userAuth.getStringId().equals(configAuth.getStringId()))) .collect(Collectors.toList()); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index e6f3702ce5..01c79619b2 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -484,7 +484,8 @@ public Page search(PetriNetSearch criteriaClass, LoggedUser u if (criteriaClass.getGroup().size() == 1) { this.addValueCriteria(query, queryTotal, Criteria.where("author.email").is(groupService.getGroupOwnerEmail(criteriaClass.getGroup().get(0)))); } else { - this.addValueCriteria(query, queryTotal, Criteria.where("author.email").in(groupService.getGroupsOwnerEmails(criteriaClass.getGroup()))); + // TODO: pagination? + this.addValueCriteria(query, queryTotal, Criteria.where("author.email").in(groupService.getGroupsOwnerEmails(criteriaClass.getGroup(), Pageable.unpaged()))); } } if (criteriaClass.getVersion() != null) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java index 16a16658e3..1d43324aa7 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java @@ -60,16 +60,17 @@ public void run(ApplicationArguments args) throws Exception { if (!createDefaultFilters) return; // All cases createCaseFilter("All cases", "assignment", FILTER_VISIBILITY_PUBLIC, "", new ArrayList(), - Map.of( + new HashMap<>(Map.of( "predicateMetadata", new ArrayList<>(), - "searchCategories", new ArrayList<>()), + "searchCategories", new ArrayList<>()) + ), Map.of( GERMAN_ISO_3166_CODE, "Alle Fälle", SLOVAK_ISO_3166_CODE, "Všetky prípady")); // My cases createCaseFilter("My cases", "assignment_ind", FILTER_VISIBILITY_PUBLIC, "(author:<>)", new ArrayList(), - Map.of( + new HashMap<>(Map.of( "predicateMetadata", List.of(List.of(Map.of( "category", "case_author", "configuration", Map.of("operator", "equals"), @@ -79,17 +80,18 @@ public void run(ApplicationArguments args) throws Exception { )) ))), "searchCategories", List.of("case_author") - ), Map.of( + )), + Map.of( GERMAN_ISO_3166_CODE, "Meine Fälle", SLOVAK_ISO_3166_CODE, "Moje prípady" )); // All tasks createTaskFilter("All tasks", "library_add_check", FILTER_VISIBILITY_PUBLIC, "", new ArrayList(), - Map.of( + new HashMap<>(Map.of( "predicateMetadata", List.of(), "searchCategories", List.of() - ), + )), Map.of( GERMAN_ISO_3166_CODE, "Alle Aufgaben", SLOVAK_ISO_3166_CODE, "Všetky úlohy" @@ -97,7 +99,7 @@ public void run(ApplicationArguments args) throws Exception { // My tasks createTaskFilter("My tasks", "account_box", FILTER_VISIBILITY_PUBLIC, "(userId:<>)", new ArrayList(), - Map.of( + new HashMap<>(Map.of( "predicateMetadata", List.of(List.of(Map.of( "category", "task_assignee", "configuration", Map.of("operator", "equals"), @@ -107,7 +109,8 @@ public void run(ApplicationArguments args) throws Exception { )) ))), "searchCategories", List.of("task_assignee") - ), Map.of( + )), + Map.of( GERMAN_ISO_3166_CODE, "Meine Aufgaben", SLOVAK_ISO_3166_CODE, "Moje úlohy" )); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java index 3aaa7a03b5..1a5560726f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java @@ -93,7 +93,7 @@ public void setAllProcessRoles() { } public void setAllAuthorities() { - superUser.setAuthorities(Set.copyOf(authorityService.findAll())); + superUser.setAuthorities(Set.copyOf(authorityService.findAll(Pageable.unpaged()).stream().toList())); superUser = userService.saveUser(superUser, null); } diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java index 528946c1f4..8d66e5007c 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java @@ -6,6 +6,8 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -23,39 +25,15 @@ public void setAuthorityRepository(AuthorityRepository authorityRepository) { } @Override - public List findAll() { - return authorityRepository.findAll(); + public Page findAll(Pageable pageable) { + return authorityRepository.findAll(pageable); } @Override @Transactional - public Authority getOrCreate(String s) { - Optional authority = authorityRepository.findById(s); - return authority.orElseGet(() -> authorityRepository.save(new AuthorityImpl(s))); - } - - @Override - @Transactional - public Authority getOrCreatePermission(String s) { - return getOrCreate(s); - } - - @Override - @Transactional - public Authority getOrCreateRole(String s) { - return getOrCreate(s); - } - - //TODO: this was never used - @Override - public List getAllPermissions() { - return List.of(); - } - - //TODO: this was never used - @Override - public List getAllRoles() { - return List.of(); + public Authority getOrCreate(String name) { + Optional authority = authorityRepository.findByName(name); + return authority.orElseGet(() -> authorityRepository.save(new AuthorityImpl(name))); } @Override @@ -64,7 +42,7 @@ public Authority getOne(String s) { } @Override - public List findAllByIds(List ids) { - return authorityRepository.findAllBy_idIn(ids.stream().map(ObjectId::new).collect(Collectors.toList())); + public Page findAllByIds(List ids, Pageable pageable) { + return authorityRepository.findAllBy_idIn(ids.stream().map(ObjectId::new).collect(Collectors.toList()), pageable); } } diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java index dcad18d724..86a8377e41 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.auth.service; +import com.netgrif.application.engine.adapter.spring.utils.PageableUtils; import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.repository.GroupRepository; import com.netgrif.application.engine.objects.auth.domain.Authority; @@ -75,7 +76,7 @@ public Group findById(String id) { } @Override - public Page findAllByIds(Set ids, Pageable pageable) { + public Page findAllByIds(Collection ids, Pageable pageable) { Page groups = groupRepository.findAllByIdIn(ids, pageable); groups.getContent().forEach(this::populateMembers); return groups; @@ -185,13 +186,15 @@ public void populateMembers(Group group) { } @Override - public Set getAllCoMembers(IUser user) { - Set userMembershipGroups = groupRepository.findAllByMemberIdsContains(user.getStringId()); + public Page getAllCoMembers(IUser user, Pageable pageable) { + Page userMembershipGroups = groupRepository.findAllByMemberIdsContains(user.getStringId(), pageable); IUser system = userService.getSystem(); - return userMembershipGroups.stream().map(Group::getMemberIds).flatMap(Set::stream) - .filter(id -> !id.equals(user.getStringId())) - .filter(id -> !id.equals(system.getStringId())) - .collect(Collectors.toSet()); + return PageableUtils.listToPage( + userMembershipGroups.stream().map(Group::getMemberIds).flatMap(Set::stream) + .filter(id -> !id.equals(user.getStringId()) && !id.equals(system.getStringId())) + .collect(Collectors.toList()), + userMembershipGroups.getPageable() + ) ; } @Override @@ -219,13 +222,13 @@ public Group assignAuthority(String groupId, String authorityId) { } @Override - public List findByIds(Collection ids) { - return groupRepository.findAllById(ids); + public Page findByIds(Collection ids, Pageable pageable) { + return groupRepository.findAllByIdIn(ids, pageable); } @Override - public Collection getGroupsOwnerEmails(Collection groupIds) { - return this.findByIds(groupIds).stream().map(this::getGroupOwnerEmail).toList(); + public Page getGroupsOwnerEmails(Collection groupIds, Pageable pageable) { + return this.findByIds(groupIds, pageable).map(this::getGroupOwnerEmail); } @Override diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java index 21e1334c01..842ac5a02f 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java @@ -201,7 +201,7 @@ public void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTi } @Override - public List findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds) { + public Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds, Pageable pageable) { Set collectionNames = collectionNameProvider.getCollectionNamesForRealms(realmIds); return userRepository.findAllByStateAndExpirationDateBefore(state, expirationDate, mongoTemplate, collectionNames); } @@ -301,7 +301,7 @@ public IUser findByEmail(String email, String realmId) { @Override public Page findAllCoMembers(com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { - Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser()); + Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); members.add(loggedUser.getSelfOrImpersonated().getId()); Set objMembers = members.stream().map(ObjectId::new).collect(Collectors.toSet()); Set collectionNames = collectionNameProvider.getCollectionNamesForAllRealm(); @@ -310,7 +310,8 @@ public Page findAllCoMembers(com.netgrif.application.engine.objects.auth. @Override public Page searchAllCoMembers(String query, com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { - Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser()); + // TODO: how to send pageable to group service???? + Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); members.add(loggedUser.getSelfOrImpersonated().getId()); Set collectionNames = collectionNameProvider.getCollectionNamesForAllRealm(); return changeType(userRepository.findAll(buildPredicate(members.stream().map(ObjectId::new) @@ -328,7 +329,7 @@ public Page searchAllCoMembers(String query, List role } - Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser()); + Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); members.add(loggedUser.getSelfOrImpersonated().getId()); BooleanExpression predicate = buildPredicate(members.stream().map(ObjectId::new).collect(Collectors.toSet()), query); if (!(roleIds == null || roleIds.isEmpty())) { diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java index 0f3d4916d3..79252267f1 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java @@ -2,15 +2,18 @@ import com.netgrif.application.engine.objects.auth.domain.Authority; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface AuthorityRepository extends MongoRepository { - Authority findByName(String name); + Optional findByName(String name); List findAllByNameStartsWith(String prefix); - List findAllBy_idIn(List ids); + Page findAllBy_idIn(List ids, Pageable pageable); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java index 17ec03be0e..ad5150fe87 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; +import java.util.Collection; import java.util.Optional; import java.util.Set; @@ -16,9 +17,9 @@ public interface GroupRepository extends MongoRepository, Queryds Optional findByIdentifier(String identifier); - Set findAllByMemberIdsContains(String memberId); + Page findAllByMemberIdsContains(String memberId, Pageable pageable); - Page findAllByIdIn(Set ids, Pageable pageable); + Page findAllByIdIn(Collection ids, Pageable pageable); Page findAllByRealmId(String realmId, Pageable pageable); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java index f5b39054dd..f885c8a422 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java @@ -43,14 +43,12 @@ default void deleteAll() { throw new UnsupportedOperationException("This method is not supported. Use 'UserRepository.deleteAll(MongoTemplate, Collection)' instead.'"); } - default Page findAll(Predicate predicate, Pageable pageable, MongoTemplate mongoTemplate, Collection collectionNames) { + default Page findAll(Predicate predicate, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Assert.notNull(predicate, "Predicate must not be null"); Assert.notNull(pageable, "Pageable must not be null"); - return PageableUtils.listToPage(collectionNames.stream().map(collection -> { - SpringDataMongodbQuery query = createQuery(predicate, mongoTemplate, collection); - return query.fetch().stream().map(User.class::cast).toList(); - }).flatMap(List::stream).toList(), pageable); + SpringDataMongodbQuery query = createQuery(predicate, mongoTemplate, collection); + return query.fetchPage(pageable).map(User.class::cast); } default Optional findById(ObjectId objectId, MongoTemplate mongoTemplate, String collectionName) { @@ -79,23 +77,17 @@ default void deleteAll(MongoTemplate mongoTemplate, Collection collectio collectionName.forEach(collection -> mongoTemplate.remove(new Query(), collection)); } - default Page findDistinctByStateAndProcessRoles__idIn(UserState state, List roleId, Pageable pageable, MongoTemplate mongoTemplate, Set collectionNames) { - Set resultUserSet = collectionNames.stream().map(collectionName -> - mongoTemplate.find( - Query.query( - Criteria.where("state").is(state) - .and("processRoles._id").in(roleId)) - .with(pageable), - com.netgrif.application.engine.adapter.spring.auth.domain.User.class, - collectionName) - ).flatMap(List::stream).collect(Collectors.toSet()); - return new PageImpl<>(resultUserSet.stream().toList(), pageable, resultUserSet.size()); + default Page findDistinctByStateAndProcessRoles__idIn(UserState state, List roleId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + Query query = Query.query( + Criteria.where("state").is(state) + .and("processRoles._id").in(roleId)); + return resolveUserPage(pageable, mongoTemplate, collection, query); } - default List findAllByProcessRoles__idIn(List rolesId, MongoTemplate mongoTemplate, Set collectionNames) { - return collectionNames.stream().map(collectionName -> - mongoTemplate.find(Query.query(Criteria.where("processRoles._id").in(rolesId)), com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName) - ).flatMap(List::stream).map(User.class::cast).toList(); + default Page findAllByProcessRoles__idIn(List rolesId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + Query query = Query.query( + Criteria.where("processRoles._id").in(rolesId)); + return resolveUserPage(pageable, mongoTemplate, collection, query); } default void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTime dateTime, MongoTemplate mongoTemplate, Set collectionNames) { @@ -104,26 +96,33 @@ default void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateT ); } - default List findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime dateTime, MongoTemplate mongoTemplate, Set collectionNames) { - return collectionNames.stream().map(collectionName -> - mongoTemplate.find(Query.query(Criteria.where("state").is(state).and("credentials.token.properties.expirationDate").lt(dateTime)), com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName) - ).flatMap(List::stream).map(User.class::cast).toList(); + default Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime dateTime, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + Query query = Query.query( + Criteria.where("state").is(state) + .and("credentials.token.properties.expirationDate").lt(dateTime)); + return resolveUserPage(pageable, mongoTemplate, collection, query); } - default Page findAllByIdInAndState(Set ids, UserState state, Pageable pageable, MongoTemplate mongoTemplate, Set collectionNames) { - Set resultUserSet = collectionNames.stream().map(collectionName -> - mongoTemplate.find( - Query.query( - Criteria.where("id").in(ids) - .and("state").is(state)) - .with(pageable), - com.netgrif.application.engine.adapter.spring.auth.domain.User.class, - collectionName) - ).flatMap(List::stream).collect(Collectors.toSet()); - return new PageImpl<>(resultUserSet.stream().toList(), pageable, resultUserSet.size()); + default Page findAllByIdInAndState(Set ids, UserState state, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + Query query = Query.query( + Criteria.where("id").in(ids) + .and("state").is(state)); + return resolveUserPage(pageable, mongoTemplate, collection, query); } private SpringDataMongodbQuery createQuery(Predicate predicate, MongoTemplate mongoTemplate, String collectionName) { return new SpringDataMongodbQuery<>(mongoTemplate, com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName).where(predicate); } + + private static PageImpl resolveUserPage(Pageable pageable, MongoTemplate mongoTemplate, String collection, Query query) { + List resultUserList = mongoTemplate.find( + query.with(pageable), + com.netgrif.application.engine.adapter.spring.auth.domain.User.class, + collection) + .stream() + .map(User.class::cast) + .collect(Collectors.toList()); + long total = mongoTemplate.count(query.limit(-1).skip(-1), com.netgrif.application.engine.adapter.spring.auth.domain.User.class); + return new PageImpl<>(resultUserList, pageable, total); + } } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java index 7080961470..13860f6ca1 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java @@ -1,24 +1,18 @@ package com.netgrif.application.engine.auth.service; import com.netgrif.application.engine.objects.auth.domain.Authority; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import java.util.List; public interface AuthorityService { - List findAll(); + Page findAll(Pageable pageable); Authority getOrCreate(String name); - Authority getOrCreatePermission(String name); - - Authority getOrCreateRole(String name); - - List getAllPermissions(); - - List getAllRoles(); - Authority getOne(String id); - List findAllByIds(List ids); + Page findAllByIds(List ids, Pageable pageable); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java index 8c89ce45f0..351f853b7c 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java @@ -26,9 +26,9 @@ public interface GroupService { Group findById(String id); - List findByIds(Collection ids); + Page findByIds(Collection ids, Pageable pageable); - Page findAllByIds(Set ids, Pageable pageable); + Page findAllByIds(Collection ids, Pageable pageable); Page findAll(Pageable pageable); @@ -48,13 +48,13 @@ public interface GroupService { void populateMembers(Group group); - Set getAllCoMembers(IUser user); + Page getAllCoMembers(IUser user, Pageable pageable); Page findByPredicate(Predicate predicate, Pageable pageable); Group assignAuthority(String groupId, String authorityId); - Collection getGroupsOwnerEmails(Collection groupIds); + Page getGroupsOwnerEmails(Collection groupIds, Pageable pageable); String getGroupOwnerEmail(String groupId); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java index 5a8509f4d0..eba9c73bbc 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java @@ -58,15 +58,15 @@ public interface UserService { Page findAllCoMembers(com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable); - List findAllByIds(Collection ids, String realmId); + Page findAllByIds(Collection ids, String realmId, Pageable pageable); Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable); Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable, Collection realmIds); - List findAllByProcessRoles(Set roleIds, Collection realmIds); + Page findAllByProcessRoles(Set roleIds, Collection realmIds, Pageable pageable); - List findAllByProcessRoles(Set roleIds); + Page findAllByProcessRoles(Set roleIds, Pageable pageable); void addDefaultAuthorities(IUser user); @@ -114,7 +114,7 @@ public interface UserService { void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds); - List findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds); + Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds, Pageable pageable); IUser transformToUser(Author author); From 292dff18bc229e575a7e1457d92e25026cce6d7f Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Wed, 25 Jun 2025 16:33:25 +0200 Subject: [PATCH 2/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Refactor pagination support across repositories and services - Updated methods to support `Pageable` for paginated data retrieval and processing. - Replaced bulk-loading calls with paginated queries to improve scalability and performance. --- .../engine/importer/service/Importer.java | 4 +- .../config/ProcessBeansConfiguration.java | 10 +- .../repositories/PetriNetRepository.java | 4 +- .../domain/roles/ProcessRoleRepository.java | 54 ++++--- .../petrinet/service/PetriNetService.java | 49 +++--- .../petrinet/service/ProcessRoleService.java | 107 ++++++++----- .../service/interfaces/IPetriNetService.java | 10 +- .../petrinet/web/PetriNetController.java | 9 +- .../startup/runner/AnonymousRoleRunner.java | 4 +- .../startup/runner/DefaultRoleRunner.java | 4 +- .../startup/runner/FunctionsCacheRunner.java | 4 +- .../startup/runner/SuperCreatorRunner.java | 24 ++- .../workflow/service/CaseSearchService.java | 4 +- .../service/ConfigurableMenuService.java | 3 +- .../service/ProcessRoleServiceTest.java | 18 ++- .../petrinet/service/ProcessRoleService.java | 14 +- .../spring/utils/PaginationProperties.java | 15 ++ .../engine/auth/service/GroupServiceImpl.java | 1 + .../engine/auth/service/UserServiceImpl.java | 141 +++++++++--------- .../auth/repository/UserRepository.java | 22 ++- .../engine/auth/service/UserService.java | 12 +- .../requestbodies/UserSearchRequestBody.java | 2 + 22 files changed, 320 insertions(+), 195 deletions(-) create mode 100644 nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java b/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java index e6cb08bb2d..44db5a9c1e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java @@ -1061,7 +1061,7 @@ protected void createRole(Role importRole) { if (shouldInitializeRole(importRole)) { role = initRole(importRole); } else { - role = new ArrayList<>(processRoleService.findAllByImportId(ProcessRole.GLOBAL + importRole.getId())).get(0); + role = processRoleService.findByImportId(ProcessRole.GLOBAL + importRole.getId()); } role.set_id(new ProcessResourceId(new ObjectId(net.getStringId()))); @@ -1071,7 +1071,7 @@ protected void createRole(Role importRole) { protected boolean shouldInitializeRole(Role importRole) { return importRole.isGlobal() == null || !importRole.isGlobal() || - (importRole.isGlobal() && processRoleService.findAllByImportId(ProcessRole.GLOBAL + importRole.getId()).isEmpty()); + (importRole.isGlobal() && processRoleService.findByImportId(ProcessRole.GLOBAL + importRole.getId()) == null); } protected ProcessRole initRole(Role importRole) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java index 185495800b..bd7f49907b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java @@ -1,7 +1,9 @@ package com.netgrif.application.engine.petrinet.config; import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.auth.service.RealmService; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.runner.RoleActionsRunner; import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRoleRepository; @@ -27,7 +29,9 @@ public ProcessRoleService processRoleService(ProcessRoleRepository processRoleRe @Lazy PetriNetService petriNetService, @Lazy UserService userService, ISecurityContextService securityContextService, - @Lazy GroupService groupService + @Lazy GroupService groupService, + @Lazy RealmService realmService, + @Lazy PaginationProperties paginationProperties ) { return new com.netgrif.application.engine.petrinet.service.ProcessRoleService( processRoleRepository, @@ -37,7 +41,9 @@ public ProcessRoleService processRoleService(ProcessRoleRepository processRoleRe petriNetService, userService, securityContextService, - groupService + groupService, + realmService, + paginationProperties ); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java index 3a133ee4bc..d4c81e0d46 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java @@ -16,15 +16,13 @@ public interface PetriNetRepository extends MongoRepository, Q PetriNet findByImportId(String id); - List findAllByIdentifier(String identifier); - PetriNet findByIdentifierAndVersion(String identifier, Version version); Page findByIdentifier(String identifier, Pageable pageable); Page findByIdentifierIn(List identifier, Pageable pageable); - List findAllByVersion(Version version); + Page findAllByVersion(Version version, Pageable pageable); List findAllByUriNodeId(String uri); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java index 0ba7793e71..5c2bb21fa7 100755 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java @@ -3,10 +3,13 @@ import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; +import org.springframework.data.util.Pair; import java.util.*; import java.util.stream.Collectors; @@ -14,30 +17,46 @@ @Repository public interface ProcessRoleRepository extends MongoRepository, QuerydslPredicateExecutor { - ProcessRole findByImportId(String importId); + Optional findByImportId(String importId); - Set findAllByProcessId(String netId); + Page findAllByProcessId(String netId, Pageable pageable); - Set findAllByImportIdIn(Set importIds); + List findAllByImportIdIn(Set importIds); - Set findAllByName_DefaultValue(String name); + Page findAllByName_DefaultValue(String name, Pageable pageable); - Set findAllByImportId(String importId); + Page findAllByImportId(String importId, Pageable pageable); - Set findAllByGlobalIsTrue(); + Page findAllByGlobalIsTrue(Pageable pageable); @Query("{ '_id.objectId': ?0 }") Optional findByIdObjectId(ObjectId objectId); - void deleteAllBy_idIn(Collection ids); + @Query("{ '_id.objectId': { $in: ?0 } }") + List findByObjectIds(Collection objectIds); + void deleteAllBy_idIn(Collection ids); - //TODO: It goes one at a time... make bulk - default Set findAllById(Set ids) { - return ids.stream() - .map(this::findByCompositeId) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); + default Set findAllByIdsSet(Collection ids) { + Map> partitionedIds = ids.stream() + .collect(Collectors.partitioningBy(id -> id.contains(ProcessResourceId.ID_SEPARATOR))); + + List forObjectIds = partitionedIds.get(false).stream() + .map(ObjectId::new) + .collect(Collectors.toList()); + + List forNetworkObjectIds = new ArrayList<>(); + List forNetworkNetworkIds = new ArrayList<>(); + partitionedIds.get(true).forEach(id -> { + String[] parts = id.split(ProcessResourceId.ID_SEPARATOR); + forNetworkNetworkIds.add(parts[0]); + forNetworkObjectIds.add(new ObjectId(parts[1])); + }); + + List processRoles = new ArrayList<>(); + processRoles.addAll(findByObjectIds(forObjectIds)); + processRoles.addAll(findByNetworkIdsAndObjectIds(forNetworkNetworkIds, forNetworkObjectIds)); + return new HashSet<>(processRoles); } default Optional findByCompositeId(String compositeId) { @@ -51,10 +70,9 @@ default Optional findByCompositeId(String compositeId) { } } - default List findAllByCompositeId(Collection compositeId) { - return compositeId.stream().map(this::findByCompositeId).map(optional -> optional.orElse(null)).filter(Objects::nonNull).collect(Collectors.toList()); - } - @Query("{ '_id.shortProcessId': ?0, '_id.objectId': ?1 }") Optional findByNetworkIdAndObjectId(String networkId, ObjectId objectId); -} + + @Query("{ $or: [ { '_id.shortProcessId': ?0, '_id.objectId': ?1 } ] }") + List findByNetworkIdsAndObjectIds(Collection networkIds, Collection objectIds); +} \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 01c79619b2..b93d360de3 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -295,8 +295,8 @@ public PetriNet getPetriNet(String identifier, Version version) { } @Override - public List getByIdentifier(String identifier) { - List nets = repository.findAllByIdentifier(identifier); + public Page getByIdentifier(String identifier, Pageable pageable) { + Page nets = repository.findByIdentifier(identifier, pageable); nets.forEach(PetriNet::initializeArcs); return nets; } @@ -360,8 +360,8 @@ public PetriNetImportReference getNetFromCase(String caseId) { } @Override - public List getAll() { - List nets = repository.findAll(); + public Page getAll(Pageable pageable) { + Page nets = repository.findAll(pageable); nets.forEach(PetriNet::initializeArcs); return nets; } @@ -380,35 +380,50 @@ public FileSystemResource getFile(String netId, String title) { } @Override - public List getReferences(LoggedUser user, Locale locale) { - return getAll().stream().map(net -> transformToReference(net, locale)).collect(Collectors.toList()); + public Page getReferences(LoggedUser user, Locale locale, Pageable pageable) { + return getAll(pageable).map(net -> transformToReference(net, locale)); } @Override - public List getReferencesByIdentifier(String identifier, LoggedUser user, Locale locale) { - return getByIdentifier(identifier).stream().map(net -> transformToReference(net, locale)).collect(Collectors.toList()); + public Page getReferencesByIdentifier(String identifier, LoggedUser user, Locale locale, Pageable pageable) { + return getByIdentifier(identifier, pageable).map(net -> transformToReference(net, locale)); } @Override - public List getReferencesByVersion(Version version, LoggedUser user, Locale locale) { - List references; - + public Page getReferencesByVersion(Version version, LoggedUser user, Locale locale, Pageable pageable) { + Page references; if (version == null) { GroupOperation groupByIdentifier = Aggregation.group("identifier").max("version").as("version"); - Aggregation aggregation = Aggregation.newAggregation(groupByIdentifier); - AggregationResults results = mongoTemplate.aggregate(aggregation, "petriNet", Document.class); - references = results.getMappedResults().stream() + Aggregation aggregation = Aggregation.newAggregation( + groupByIdentifier, + Aggregation.sort(pageable.getSort()), + Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()), + Aggregation.limit(pageable.getPageSize()) + ); + List results = mongoTemplate.aggregate(aggregation, "petriNet", Document.class).getMappedResults(); + List referenceList = results.stream() .map(doc -> { Document versionDoc = doc.get("version", Document.class); Version refVersion = new Version(versionDoc.getLong("major"), versionDoc.getLong("minor"), versionDoc.getLong("patch")); return getReference(doc.getString("_id"), refVersion, user, locale); }) .collect(Collectors.toList()); + Aggregation countAggregation = Aggregation.newAggregation( + groupByIdentifier, + Aggregation.count().as("total") + ); + AggregationResults countResults = mongoTemplate.aggregate( + countAggregation, + "your_collection_name", + Document.class + ); + long total = countResults.getUniqueMappedResult() != null + ? countResults.getUniqueMappedResult().getLong("total") + : 0L; + references = new PageImpl<>(referenceList, pageable, total); } else { - references = repository.findAllByVersion(version).stream() - .map(net -> transformToReference(net, locale)).collect(Collectors.toList()); + references = repository.findAllByVersion(version, pageable).map(net -> transformToReference(net, locale)); } - return references; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java index 77b7a583ef..b43fba519b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java @@ -1,10 +1,13 @@ package com.netgrif.application.engine.petrinet.service; +import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.auth.service.RealmService; import com.netgrif.application.engine.objects.auth.domain.Group; import com.netgrif.application.engine.objects.auth.domain.IUser; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.UserService; +import com.netgrif.application.engine.objects.auth.domain.Realm; import com.netgrif.application.engine.objects.event.events.user.UserRoleChangeEvent; import com.netgrif.application.engine.objects.importer.model.EventPhaseType; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; @@ -24,7 +27,9 @@ import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.util.*; import java.util.stream.Collectors; @@ -42,6 +47,8 @@ public class ProcessRoleService implements com.netgrif.application.engine.adapte private final IPetriNetService petriNetService; private final ISecurityContextService securityContextService; private final GroupService groupService; + private final RealmService realmService; + private final PaginationProperties paginationProperties; private ProcessRole defaultRole; private ProcessRole anonymousRole; @@ -49,7 +56,8 @@ public class ProcessRoleService implements com.netgrif.application.engine.adapte public ProcessRoleService(ProcessRoleRepository processRoleRepository, PetriNetRepository netRepository, ApplicationEventPublisher publisher, RoleActionsRunner roleActionsRunner, - @Lazy IPetriNetService petriNetService, @Lazy UserService userService, ISecurityContextService securityContextService, @Lazy GroupService groupService) { + @Lazy IPetriNetService petriNetService, @Lazy UserService userService, ISecurityContextService securityContextService, @Lazy GroupService groupService, + @Lazy RealmService realmService, @Lazy PaginationProperties paginationProperties) { this.processRoleRepository = processRoleRepository; this.netRepository = netRepository; this.publisher = publisher; @@ -58,6 +66,8 @@ public ProcessRoleService(ProcessRoleRepository processRoleRepository, this.userService = userService; this.securityContextService = securityContextService; this.groupService = groupService; + this.realmService = realmService; + this.paginationProperties = paginationProperties; } @Override @@ -66,13 +76,13 @@ public ProcessRole save(ProcessRole processRole) { } @Override - public List getAll() { - return processRoleRepository.findAll(); + public Page getAll(Pageable pageable) { + return processRoleRepository.findAll(pageable); } @Override - public List findAllByNetId(String s) { - return new ArrayList<>(processRoleRepository.findAllByProcessId(s)); + public Page findAllByNetId(String s, Pageable pageable) { + return processRoleRepository.findAllByProcessId(s, pageable); } @Override @@ -88,7 +98,7 @@ public void delete(String s) { @Override public void deleteAll(Collection collection) { - List processRoles = processRoleRepository.findAllByCompositeId(collection); + Set processRoles = processRoleRepository.findAllByIdsSet(collection); processRoleRepository.deleteAll(processRoles); } @@ -204,17 +214,17 @@ protected String getProcessIdFromFirstRole(Set newRoles) { @Override public ProcessRole getDefaultRole() { - return processRoleRepository.findByImportId(ProcessRole.DEFAULT_ROLE); + return processRoleRepository.findByImportId(ProcessRole.DEFAULT_ROLE).orElse(null); } @Override public ProcessRole getAnonymousRole() { - return processRoleRepository.findByImportId(ProcessRole.ANONYMOUS_ROLE); + return processRoleRepository.findByImportId(ProcessRole.ANONYMOUS_ROLE).orElse(null); } @Override public Collection findAllByIds(Collection collection) { - return processRoleRepository.findAllByCompositeId(collection.stream().map(ProcessResourceId::getStringId).collect(Collectors.toList())); + return processRoleRepository.findAllByIdsSet(collection.stream().map(ProcessResourceId::getStringId).collect(Collectors.toList())); } @Override @@ -225,7 +235,7 @@ public ProcessRole findById(ProcessResourceId processResourceId) { @Override public List saveAll(Iterable entities) { return StreamSupport.stream(entities.spliterator(), false).map(processRole -> { - if (!processRole.isGlobal() || processRoleRepository.findAllByImportId(processRole.getImportId()).isEmpty()) { + if (!processRole.isGlobal() || processRoleRepository.findByImportId(processRole.getImportId()) == null) { return processRoleRepository.save(processRole); } return null; @@ -234,7 +244,7 @@ public List saveAll(Iterable entities) { @Override public Set findByIds(Set ids) { - return new HashSet<>(processRoleRepository.findAllById(ids)); + return processRoleRepository.findAllByIdsSet(ids); } private Set updateRequestedRoles(IUser user, Set rolesNewToUser, Set rolesRemovedFromUser) { @@ -349,13 +359,13 @@ private void removeOldAndAssignNewRolesToUser(IUser user, Set reque } @Override - public List findAll() { - return processRoleRepository.findAll(); + public Page findAll(Pageable pageable) { + return processRoleRepository.findAll(pageable); } @Override - public Set findAllGlobalRoles() { - return processRoleRepository.findAllByGlobalIsTrue(); + public Page findAllGlobalRoles(Pageable pageable) { + return processRoleRepository.findAllByGlobalIsTrue(pageable); } @Override @@ -373,10 +383,10 @@ private List findAll(PetriNet net) { @Override public ProcessRole defaultRole() { if (defaultRole == null) { - Set roles = processRoleRepository.findAllByName_DefaultValue(ProcessRole.DEFAULT_ROLE); + Page roles = processRoleRepository.findAllByImportId(ProcessRole.DEFAULT_ROLE, Pageable.ofSize(2)); if (roles.isEmpty()) throw new IllegalStateException("No default process role has been found!"); - if (roles.size() > 1) + if (roles.getTotalElements() > 1) throw new IllegalStateException("More than 1 default process role exists!"); defaultRole = roles.stream().findFirst().orElse(null); } @@ -386,10 +396,10 @@ public ProcessRole defaultRole() { @Override public ProcessRole anonymousRole() { if (anonymousRole == null) { - Set roles = processRoleRepository.findAllByImportId(ProcessRole.ANONYMOUS_ROLE); + Page roles = processRoleRepository.findAllByImportId(ProcessRole.ANONYMOUS_ROLE, Pageable.ofSize(2)); if (roles.isEmpty()) throw new IllegalStateException("No anonymous process role has been found!"); - if (roles.size() > 1) + if (roles.getTotalElements() > 1) throw new IllegalStateException("More than 1 anonymous process role exists!"); anonymousRole = roles.stream().findFirst().orElse(null); } @@ -399,22 +409,22 @@ public ProcessRole anonymousRole() { /** * @param importId id from a process of a role * @return a process role object - * @deprecated use {@link ProcessRoleService#findAllByImportId(String)} instead + * @deprecated use {@link ProcessRoleService#findAllByImportId(String, Pageable)} instead */ @Deprecated(forRemoval = true, since = "6.2.0") @Override public ProcessRole findByImportId(String importId) { - return processRoleRepository.findAllByImportId(importId).stream().findFirst().orElse(null); + return processRoleRepository.findByImportId(importId).orElse(null); } @Override - public Set findAllByImportId(String importId) { - return processRoleRepository.findAllByImportId(importId); + public Page findAllByImportId(String importId, Pageable pageable) { + return processRoleRepository.findAllByImportId(importId, pageable); } @Override - public Set findAllByDefaultName(String name) { - return processRoleRepository.findAllByName_DefaultValue(name); + public Page findAllByDefaultName(String name, Pageable pageable) { + return processRoleRepository.findAllByName_DefaultValue(name, pageable); } @Override @@ -429,20 +439,37 @@ public void deleteRolesOfNet(PetriNet net, LoggedUser loggedUser) { List deletedRoleIds = this.findAll(net.getStringId()).stream().filter(processRole -> processRole.getProcessId() != null).map(ProcessRole::get_id).collect(Collectors.toList()); Set deletedRoleStringIds = deletedRoleIds.stream().map(ProcessResourceId::toString).collect(Collectors.toSet()); - List usersWithRemovedRoles = this.userService.findAllByProcessRoles(new HashSet<>(deletedRoleIds)); - for (IUser user : usersWithRemovedRoles) { - log.info("[" + net.getStringId() + "]: Removing deleted roles of Petri net " + net.getIdentifier() + " version " + net.getVersion().toString() + " from user " + user.getFullName() + " with id " + user.getStringId()); - - if (user.getProcessRoles().isEmpty()) { - continue; - } - - Set newRoles = user.getProcessRoles().stream() - .filter(role -> !deletedRoleStringIds.contains(role.getStringId())) - .map(ProcessRole::get_id) - .collect(Collectors.toSet()); - this.assignRolesToUser(user, newRoles, loggedUser); - } + Pageable realmPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page realms; + do { + realms = realmService.getSmallRealm(realmPageable); + + realms.forEach(realm -> { + Pageable usersPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page users; + do { + users = this.userService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), realm.getId(), usersPageable); + + for (IUser user : users) { + log.info("[" + net.getStringId() + "]: Removing deleted roles of Petri net " + net.getIdentifier() + " version " + net.getVersion().toString() + " from user " + user.getFullName() + " with id " + user.getStringId()); + + if (user.getProcessRoles().isEmpty()) { + continue; + } + + Set newRoles = user.getProcessRoles().stream() + .filter(role -> !deletedRoleStringIds.contains(role.getStringId())) + .map(ProcessRole::get_id) + .collect(Collectors.toSet()); + this.assignRolesToUser(user, newRoles, loggedUser); + } + + usersPageable = usersPageable.next(); + } while (users.hasNext()); + }); + + realmPageable = realmPageable.next(); + } while (realms.hasNext()); log.info("[" + net.getStringId() + "]: Deleting all roles of Petri net " + net.getIdentifier() + " version " + net.getVersion().toString()); this.processRoleRepository.deleteAllBy_idIn(deletedRoleIds); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index c11ff71c84..930d8c61a5 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -61,7 +61,7 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti PetriNet getPetriNet(String identifier, Version version); - List getByIdentifier(String identifier); + Page getByIdentifier(String identifier, Pageable pageable); List findAllByUriNodeId(String uriNodeId); @@ -69,15 +69,15 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti PetriNet getNewestVersionByIdentifier(String identifier); - List getAll(); + Page getAll(Pageable pageable); FileSystemResource getFile(String netId, String title); - List getReferences(LoggedUser user, Locale locale); + Page getReferences(LoggedUser user, Locale locale, Pageable pageable); - List getReferencesByIdentifier(String identifier, LoggedUser user, Locale locale); + Page getReferencesByIdentifier(String identifier, LoggedUser user, Locale locale, Pageable pageable); - List getReferencesByVersion(Version version, LoggedUser user, Locale locale); + Page getReferencesByVersion(Version version, LoggedUser user, Locale locale, Pageable pageable); List getReferencesByUsersProcessRoles(LoggedUser user, Locale locale); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java index 46e23c8a33..847278f34d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java @@ -127,13 +127,14 @@ public EntityModel importPetriNet( public ResponseEntity> getAll(@RequestParam(value = "indentifier", required = false) String identifier, @RequestParam(value = "version", required = false) String version, Pageable pageable, Authentication auth, Locale locale) { LoggedUser user = (LoggedUser) auth.getPrincipal(); if (identifier != null && version == null) { - return ResponseEntity.ok(new PageImpl<>(service.getReferencesByIdentifier(identifier, user, locale), pageable, 0)); + return ResponseEntity.ok(service.getReferencesByIdentifier(identifier, user, locale, pageable)); } else if (identifier == null && version != null) { - return ResponseEntity.ok(new PageImpl<>(service.getReferencesByVersion(converter.convert(version), user, locale), pageable, 0)); + return ResponseEntity.ok(service.getReferencesByVersion(converter.convert(version), user, locale, pageable)); } else if (identifier != null) { - return ResponseEntity.ok(new PageImpl<>(Collections.singletonList(service.getReference(identifier, converter.convert(version), user, locale)), pageable, 0)); + PetriNetReference reference = service.getReference(identifier, converter.convert(version), user, locale); + return ResponseEntity.ok(new PageImpl<>(Collections.singletonList(reference), pageable, reference.getIdentifier().isEmpty() ? 0 : 1)); } else { - return ResponseEntity.ok(new PageImpl<>(service.getReferences(user, locale), pageable, 0)); + return ResponseEntity.ok(service.getReferences(user, locale, pageable)); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AnonymousRoleRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AnonymousRoleRunner.java index c6be15a8dc..fe462f806e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AnonymousRoleRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AnonymousRoleRunner.java @@ -27,8 +27,8 @@ public class AnonymousRoleRunner implements ApplicationEngineStartupRunner { @Override public void run(ApplicationArguments args) throws Exception { log.info("Creating anonymous process role"); - Set role = processRoleService.findAllByImportId(ProcessRole.ANONYMOUS_ROLE); - if (role != null && !role.isEmpty()) { + ProcessRole role = processRoleService.findByImportId(ProcessRole.ANONYMOUS_ROLE); + if (role != null) { log.info("Anonymous role already exists"); return; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRoleRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRoleRunner.java index b9e1cd5add..95909eb6fb 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRoleRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRoleRunner.java @@ -29,8 +29,8 @@ public class DefaultRoleRunner implements ApplicationEngineStartupRunner { @Override public void run(ApplicationArguments args) throws Exception { log.info("Creating default process role"); - Set role = (Set) processRoleService.findAllByDefaultName(ProcessRole.DEFAULT_ROLE); - if (role != null && !role.isEmpty()) { + ProcessRole role = processRoleService.findByImportId(ProcessRole.DEFAULT_ROLE); + if (role != null) { log.info("Default role already exists"); return; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/FunctionsCacheRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/FunctionsCacheRunner.java index f1c6a60a75..358b90e1c4 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/FunctionsCacheRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/FunctionsCacheRunner.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationArguments; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; @Slf4j @@ -21,7 +22,8 @@ public class FunctionsCacheRunner implements ApplicationEngineStartupRunner { @Override public void run(ApplicationArguments args) throws Exception { log.info("Namespace function caching started"); - petriNetService.getAll().forEach(cacheService::cachePetriNetFunctions); + // TODO JOFO: unpaged necessary? + petriNetService.getAll(Pageable.unpaged()).forEach(cacheService::cachePetriNetFunctions); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java index 1a5560726f..ad3ec6d87c 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java @@ -1,10 +1,12 @@ package com.netgrif.application.engine.startup.runner; +import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.service.AuthorityService; import com.netgrif.application.engine.objects.auth.domain.*; import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.auth.service.GroupService; import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.startup.ApplicationEngineStartupRunner; import com.netgrif.application.engine.startup.annotation.RunnerOrder; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; @@ -14,6 +16,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; @@ -38,6 +42,7 @@ public class SuperCreatorRunner implements ApplicationEngineStartupRunner { private final UserService userService; private final GroupService groupService; private final ProcessRoleService processRoleService; + private final PaginationProperties paginationProperties; @Getter private IUser superUser; @@ -65,7 +70,15 @@ private IUser createSuperUser() { user.setPassword(superAdminPassword); user.setState(UserState.ACTIVE); user.setAuthorities(authorities); - user.setProcessRoles(new HashSet<>(processRoleService.findAll())); + + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page processRoles; + do { + processRoles = processRoleService.findAll(pageable); + user.getProcessRoles().addAll(processRoles.getContent()); + pageable = pageable.next(); + } while (processRoles.hasNext()); + this.superUser = userService.createUser(user, null); log.info("Super user created"); } else { @@ -88,7 +101,14 @@ public void setAllGroups() { } public void setAllProcessRoles() { - superUser.setProcessRoles(Set.copyOf(processRoleService.findAll())); + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page processRoles; + do { + processRoles = processRoleService.findAll(pageable); + superUser.getProcessRoles().addAll(processRoles.getContent()); + pageable = pageable.next(); + } while (processRoles.hasNext()); + superUser = userService.saveUser(superUser, null); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java index d9d4abfe4a..a22d8222da 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java @@ -23,6 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.time.LocalDate; @@ -267,7 +268,8 @@ public Predicate fullText(Object petriNetQuery, String searchPhrase) { List petriNets; if (processes.isEmpty()) { - petriNets = petriNetService.getAll(); + // TODO JOFO: unpaged necessary? + petriNets = petriNetService.getAll(Pageable.unpaged()).getContent(); } else { petriNets = processes.stream().map(process -> petriNetService.getNewestVersionByIdentifier(process)).collect(Collectors.toList()); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/ConfigurableMenuService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/ConfigurableMenuService.java index 6ac3a4e7ca..2de3e2e1db 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/ConfigurableMenuService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/ConfigurableMenuService.java @@ -18,6 +18,7 @@ import com.netgrif.application.engine.utils.FullPageRequest; import com.netgrif.application.engine.workflow.service.interfaces.IConfigurableMenuService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.*; @@ -94,7 +95,7 @@ public Map getNetsByAuthorAsMapOptions(IUser author, Locale public Map getAvailableRolesFromNet(EnumerationMapField processField, MultichoiceMapField permittedRoles, MultichoiceMapField bannedRoles) { if (GLOBAL_ROLE.equals(processField.getValue())) { - return processRoleService.findAllGlobalRoles().stream() + return processRoleService.findAllGlobalRoles(Pageable.unpaged()).stream() .filter(role -> !permittedRoles.getOptions().containsKey(role.getImportId() + ":" + GLOBAL_ROLE) && !bannedRoles.getOptions().containsKey(role.getImportId() + ":" + GLOBAL_ROLE)) .collect(Collectors.toMap( diff --git a/application-engine/src/test/java/com/netgrif/application/engine/petrinet/service/ProcessRoleServiceTest.java b/application-engine/src/test/java/com/netgrif/application/engine/petrinet/service/ProcessRoleServiceTest.java index 6ed4609530..9a93297b72 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/petrinet/service/ProcessRoleServiceTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/petrinet/service/ProcessRoleServiceTest.java @@ -13,6 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -53,14 +55,14 @@ public void before() { @Test void shouldFindAllProcessRoles() throws IOException, MissingPetriNetMetaDataException { - List roles = processRoleService.findAll(); - int originalRoles = roles.size(); + Page roles = processRoleService.findAll(Pageable.unpaged()); + long originalRoles = roles.getTotalElements(); petriNetService.importPetriNet(new FileInputStream("src/test/resources/all_data.xml"), VersionType.MAJOR, superCreator.getLoggedSuper()); petriNetService.importPetriNet(new FileInputStream("src/test/resources/role_all_data.xml"), VersionType.MAJOR, superCreator.getLoggedSuper()); - roles = processRoleService.findAll(); + roles = processRoleService.findAll(Pageable.unpaged()); assertNotNull(roles); assertFalse(roles.isEmpty()); - assertEquals(originalRoles + 3, roles.size()); // + 2 roles from all_data and 1 role from role_all_data + assertEquals(originalRoles + 3, roles.getTotalElements()); // + 2 roles from all_data and 1 role from role_all_data } @Test @@ -93,20 +95,20 @@ void shouldGetAnonymousRole() { @Test void shouldFindAllProcessRolesByImportId() throws IOException, MissingPetriNetMetaDataException { petriNetService.importPetriNet(new FileInputStream("src/test/resources/all_data.xml"), VersionType.MAJOR, superCreator.getLoggedSuper()); - Set roles = processRoleService.findAllByImportId(ROLE_IMPORT_ID); + Page roles = processRoleService.findAllByImportId(ROLE_IMPORT_ID, Pageable.unpaged()); assertNotNull(roles); assertFalse(roles.isEmpty()); - assertEquals(1, roles.size()); + assertEquals(1, roles.getTotalElements()); assertEquals(ROLE_IMPORT_ID, roles.stream().findFirst().get().getImportId()); } @Test void shouldFindAllProcessRolesByName() throws IOException, MissingPetriNetMetaDataException { petriNetService.importPetriNet(new FileInputStream("src/test/resources/all_data.xml"), VersionType.MAJOR, superCreator.getLoggedSuper()); - Collection roles = processRoleService.findAllByDefaultName("Process role"); + Page roles = processRoleService.findAllByDefaultName("Process role", Pageable.unpaged()); assertNotNull(roles); assertFalse(roles.isEmpty()); - assertEquals(1, roles.size()); + assertEquals(1, roles.getTotalElements()); assertEquals(ROLE_IMPORT_ID, roles.stream().findFirst().get().getImportId()); } } diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java index 5c6a448428..9736a0edba 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java @@ -6,14 +6,16 @@ import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import java.util.*; public interface ProcessRoleService { ProcessRole save(ProcessRole processRole); List saveAll(Iterable processRoles); - List getAll(); - List findAllByNetId(String netId); + Page getAll(Pageable pageable); + Page findAllByNetId(String netId, Pageable pageable); Optional get(ProcessResourceId id); void delete(String id); void deleteAll(Collection ids); @@ -28,13 +30,13 @@ public interface ProcessRoleService { ProcessRole getAnonymousRole(); Collection findAllByIds(Collection roleIds); ProcessRole findById(ProcessResourceId id); - Collection findAllByDefaultName(String name); - Set findAllByImportId(String importId); + Page findAllByDefaultName(String name, Pageable pageable); + Page findAllByImportId(String importId, Pageable pageable); ProcessRole findById(String id); Set findByIds(Set ids); ProcessRole findByImportId(String importId); - List findAll(); - Set findAllGlobalRoles(); + Page findAll(Pageable pageable); + Page findAllGlobalRoles(Pageable pageable); List findAll(String netId); ProcessRole defaultRole(); ProcessRole anonymousRole(); diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java new file mode 100644 index 0000000000..b39bb79382 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java @@ -0,0 +1,15 @@ +package com.netgrif.application.engine.adapter.spring.utils; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "netgrif.pagination") +public class PaginationProperties { + + private int backendPageSize = 100; + + private int frontendPageSize = 20; +} diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java index 86a8377e41..5fb939a42f 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java @@ -187,6 +187,7 @@ public void populateMembers(Group group) { @Override public Page getAllCoMembers(IUser user, Pageable pageable) { + // TODO JOFO: rework for pagination Page userMembershipGroups = groupRepository.findAllByMemberIdsContains(user.getStringId(), pageable); IUser system = userService.getSystem(); return PageableUtils.listToPage( diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java index 842ac5a02f..f76d369aa3 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java @@ -2,6 +2,7 @@ import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.adapter.spring.workflow.service.FilterImportExportService; import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.provider.CollectionNameProvider; @@ -19,11 +20,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.support.PageableExecutionUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -56,6 +56,8 @@ public class UserServiceImpl implements UserService { private IUser systemUser; + private PaginationProperties paginationProperties; + @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; @@ -103,6 +105,12 @@ public void setGroupService(GroupService groupService) { this.groupService = groupService; } + @Lazy + @Autowired + public void setPaginationProperties(PaginationProperties paginationProperties) { + this.paginationProperties = paginationProperties; + } + @Override public IUser saveUser(IUser user, String realmId) { user.setRealmId(realmId); @@ -140,13 +148,9 @@ public Optional findUserByUsername(String username, String realmId) { public Page findAllUsers(String realmName, Pageable pageable) { log.trace("Retrieving all users in realm [{}]", realmName); String collectionName = collectionNameProvider.getCollectionNameForRealm(realmName); - Page page = PageableExecutionUtils.getPage( - mongoTemplate.findAll(IUser.class, collectionName), - pageable, - () -> mongoTemplate.count(new Query().with(pageable), IUser.class, collectionName) - ); - log.debug("Found [{}] users in realm [{}]", page.getContent().size(), realmName); - return page; + Page users = userRepository.findAllByQuery(new Query(), pageable, mongoTemplate, collectionName); + log.debug("Found [{}] users in realm [{}]", users.getContent().size(), realmName); + return changeType(users); } @Override @@ -201,9 +205,9 @@ public void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTi } @Override - public Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds, Pageable pageable) { - Set collectionNames = collectionNameProvider.getCollectionNamesForRealms(realmIds); - return userRepository.findAllByStateAndExpirationDateBefore(state, expirationDate, mongoTemplate, collectionNames); + public Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, String realmId, Pageable pageable) { + String collection = collectionNameProvider.getCollectionNameForRealm(realmId); + return userRepository.findAllByStateAndExpirationDateBefore(state, expirationDate, pageable, mongoTemplate, collection); } @Override @@ -246,7 +250,15 @@ public void addAllRolesToAdminByUsername(String username) { throw new IllegalArgumentException("Admin user with username [%s] cannot be found.".formatted(username)); } IUser user = userOptional.get(); - user.setProcessRoles(new HashSet<>(processRoleService.findAll())); + + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page processRoles; + do { + processRoles = processRoleService.findAll(pageable); + user.getProcessRoles().addAll(processRoles.getContent()); + pageable = pageable.next(); + } while (processRoles.hasNext()); + saveUser(user, user.getRealmId()); } @@ -301,26 +313,28 @@ public IUser findByEmail(String email, String realmId) { @Override public Page findAllCoMembers(com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { + // TODO JOFO: rework for pagination Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); members.add(loggedUser.getSelfOrImpersonated().getId()); Set objMembers = members.stream().map(ObjectId::new).collect(Collectors.toSet()); - Set collectionNames = collectionNameProvider.getCollectionNamesForAllRealm(); - return changeType(userRepository.findAllByIdInAndState(objMembers, UserState.ACTIVE, pageable, mongoTemplate, collectionNames), pageable); + String collectionName = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); + return changeType(userRepository.findAllByIdInAndState(objMembers, UserState.ACTIVE, pageable, mongoTemplate, collectionName)); } @Override public Page searchAllCoMembers(String query, com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { - // TODO: how to send pageable to group service???? - Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); - members.add(loggedUser.getSelfOrImpersonated().getId()); - Set collectionNames = collectionNameProvider.getCollectionNamesForAllRealm(); - return changeType(userRepository.findAll(buildPredicate(members.stream().map(ObjectId::new) - .collect(Collectors.toSet()), query), pageable, mongoTemplate, collectionNames), pageable); + // TODO JOFO: rework for pagination + Page members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()); + members.getContent().add(loggedUser.getSelfOrImpersonated().getId()); + String collectionName = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); + return changeType(userRepository.findAllByQuery(buildPredicate(members.stream().map(ObjectId::new) + .collect(Collectors.toSet()), query), pageable, mongoTemplate, collectionName)); } @Override public Page searchAllCoMembers(String query, List roleIds, List negateRoleIds, com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { + // TODO JOFO: rework for pagination!!! if ((roleIds == null || roleIds.isEmpty()) && (negateRoleIds == null || negateRoleIds.isEmpty())) return searchAllCoMembers(query, loggedUser, pageable); @@ -329,61 +343,42 @@ public Page searchAllCoMembers(String query, List role } - Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); - members.add(loggedUser.getSelfOrImpersonated().getId()); + Page members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()); + members.getContent().add(loggedUser.getSelfOrImpersonated().getId()); BooleanExpression predicate = buildPredicate(members.stream().map(ObjectId::new).collect(Collectors.toSet()), query); if (!(roleIds == null || roleIds.isEmpty())) { predicate = predicate.and(QUser.user.processRoles.any()._id.in(roleIds)); } predicate = predicate.and(QUser.user.processRoles.any()._id.in(negateRoleIds).not()); - Set collectionNames = collectionNameProvider.getCollectionNamesForAllRealm(); - Page users = userRepository.findAll(predicate, pageable, mongoTemplate, collectionNames); + String collection = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); + Page users = userRepository.findAllByQuery(predicate, pageable, mongoTemplate, collection); - return changeType(users, pageable); + return changeType(users); } @Override - public List findAllByIds(Collection ids, String realmId) { + public Page findAllByIds(Collection ids, String realmId, Pageable pageable) { log.debug("Finding users by collection of IDs [{}]", ids); - return ids.stream().map(id -> findById(id, realmId)).toList(); - } - - @Override - public Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable) { - return this.findAllActiveByProcessRoles(roleIds, pageable, List.of(getLoggedUser().getRealmId())); - } - - @Override - public Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable, Collection realmIds) { - Set collectionNames = collectionNameProvider.getCollectionNamesForRealms(realmIds); - Page users = userRepository.findDistinctByStateAndProcessRoles__idIn( - UserState.ACTIVE, - new ArrayList<>(roleIds), - pageable, - mongoTemplate, - collectionNames - ); - return changeType(users, pageable); + String collection = collectionNameProvider.getCollectionNameForRealm(realmId); + Page users = userRepository.findAllByIds(ids.stream().map(ObjectId::new).toList(), pageable, mongoTemplate, collection); + return changeType(users); } @Override - public List findAllByProcessRoles(Set roleIds, Collection realmIds) { - Set collectionNames = collectionNameProvider.getCollectionNamesForRealms(realmIds); - return searchUsersByRoleIds(roleIds, collectionNames); + public Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable, String realmId) { + String collection = collectionNameProvider.getCollectionNameForRealm(realmId); + Page users = userRepository.findDistinctByStateAndProcessRoles__idIn( UserState.ACTIVE, roleIds, pageable, mongoTemplate, collection); + return changeType(users); } @Override - public List findAllByProcessRoles(Set roleIds) { - Set collectionNames = collectionNameProvider.getCollectionNamesForAllRealm(); - return searchUsersByRoleIds(roleIds, collectionNames); + public Page findAllByProcessRoles(Set roleIds, String realmId, Pageable pageable) { + String collectionName = collectionNameProvider.getCollectionNameForRealm(realmId); + return searchUsersByRoleIds(roleIds, collectionName, pageable); } - protected List searchUsersByRoleIds(Set roleIds, Set collectionNames) { - List users = userRepository.findAllByProcessRoles__idIn( - new ArrayList<>(roleIds), - mongoTemplate, - collectionNames - ); + protected Page searchUsersByRoleIds(Set roleIds, String collectionName, Pageable pageable) { + Page users = userRepository.findAllByProcessRoles__idIn(roleIds, pageable, mongoTemplate, collectionName); return changeType(users); } @@ -427,7 +422,14 @@ public IUser getSystem() { if (systemUser == null) { systemUser = createSystemUser(); } - systemUser.setProcessRoles(new HashSet<>(processRoleService.getAll())); + + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page processRoles; + do { + processRoles = processRoleService.findAll(pageable); + systemUser.getProcessRoles().addAll(processRoles.getContent()); + pageable = pageable.next(); + } while (processRoles.hasNext()); return systemUser; } @@ -497,9 +499,16 @@ public IUser removeNegativeProcessRole(IUser user, String roleId) { @Override public void removeRoleOfDeletedPetriNet(PetriNet petriNet, Collection realmIds) { Set collectionNames = collectionNameProvider.getCollectionNamesForRealms(realmIds); - List users = findAllByProcessRoles(petriNet.getRoles().values().stream().map(ProcessRole::get_id).collect(Collectors.toSet()), collectionNames); - users.forEach(u -> { - petriNet.getRoles().forEach((k, role) -> removeRole(u, role.get_id())); + collectionNames.forEach(collection -> { + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page users; + do { + users = findAllByProcessRoles(petriNet.getRoles().values().stream().map(ProcessRole::get_id).collect(Collectors.toSet()), collection, pageable); + users.forEach(u -> { + petriNet.getRoles().forEach((k, role) -> removeRole(u, role.get_id())); + }); + pageable = pageable.next(); + } while (users.hasNext()); }); } @@ -617,12 +626,8 @@ protected void setDisablePassword(User user) { log.debug("Password N/A set for user [{}]", user.getUsername()); } - private Page changeType(Page users, Pageable pageable) { - return new PageImpl<>(changeType(users.getContent()), pageable, users.getTotalElements()); - } - - private List changeType(List users) { - return users.stream().map(IUser.class::cast).collect(Collectors.toList()); + private Page changeType(Page users) { + return users.map(IUser.class::cast); } private BooleanExpression buildPredicate(Set members, String query) { diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java index f885c8a422..3f557f0240 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java @@ -1,6 +1,5 @@ package com.netgrif.application.engine.auth.repository; -import com.netgrif.application.engine.adapter.spring.utils.PageableUtils; import com.netgrif.application.engine.objects.auth.domain.User; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; @@ -43,7 +42,7 @@ default void deleteAll() { throw new UnsupportedOperationException("This method is not supported. Use 'UserRepository.deleteAll(MongoTemplate, Collection)' instead.'"); } - default Page findAll(Predicate predicate, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + default Page findAllByQuery(Predicate predicate, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Assert.notNull(predicate, "Predicate must not be null"); Assert.notNull(pageable, "Pageable must not be null"); @@ -77,14 +76,21 @@ default void deleteAll(MongoTemplate mongoTemplate, Collection collectio collectionName.forEach(collection -> mongoTemplate.remove(new Query(), collection)); } - default Page findDistinctByStateAndProcessRoles__idIn(UserState state, List roleId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + default Page findAllByQuery(Query query, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + if (query == null) { + query = new Query(); + } + return resolveUserPage(pageable, mongoTemplate, collection, query); + } + + default Page findDistinctByStateAndProcessRoles__idIn(UserState state, Collection roleId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("state").is(state) .and("processRoles._id").in(roleId)); return resolveUserPage(pageable, mongoTemplate, collection, query); } - default Page findAllByProcessRoles__idIn(List rolesId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + default Page findAllByProcessRoles__idIn(Collection rolesId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("processRoles._id").in(rolesId)); return resolveUserPage(pageable, mongoTemplate, collection, query); @@ -103,7 +109,13 @@ default Page findAllByStateAndExpirationDateBefore(UserState state, LocalD return resolveUserPage(pageable, mongoTemplate, collection, query); } - default Page findAllByIdInAndState(Set ids, UserState state, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + default Page findAllByIds(Collection ids, Pageable pageable, MongoTemplate mongoTemplate, String collection) { + Query query = Query.query( + Criteria.where("id").in(ids)); + return resolveUserPage(pageable, mongoTemplate, collection, query); + } + + default Page findAllByIdInAndState(Collection ids, UserState state, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("id").in(ids) .and("state").is(state)); diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java index eba9c73bbc..02e0924fcb 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java @@ -60,13 +60,9 @@ public interface UserService { Page findAllByIds(Collection ids, String realmId, Pageable pageable); - Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable); + Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable, String realmId); - Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable, Collection realmIds); - - Page findAllByProcessRoles(Set roleIds, Collection realmIds, Pageable pageable); - - Page findAllByProcessRoles(Set roleIds, Pageable pageable); + Page findAllByProcessRoles(Set roleIds, String realmId, Pageable pageable); void addDefaultAuthorities(IUser user); @@ -112,9 +108,9 @@ public interface UserService { LoggedUserImpl transformToLoggedUser(IUser user); - void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds); + void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds); - Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, Collection realmIds, Pageable pageable); + Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime expirationDate, String realmIds, Pageable pageable); IUser transformToUser(Author author); diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java index 1716dfdac2..f24856fb4e 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java @@ -7,6 +7,8 @@ @Data public class UserSearchRequestBody { + private String realmId; + private String fulltext; private List roles; From 26ed9dbdee1ded748ad0eaecbaf8b3d0ea35a57b Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Wed, 2 Jul 2025 12:41:58 +0200 Subject: [PATCH 3/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Refactor co-member search logic for better pagination support - Replaced outdated co-member retrieval methods with a more efficient approach using predicates for filtering. - Removed unused methods and redundant code from GroupService and GroupServiceImpl, simplifying the logic. --- .../engine/auth/service/GroupServiceImpl.java | 15 ------ .../engine/auth/service/UserServiceImpl.java | 51 +++++++++---------- .../engine/auth/service/GroupService.java | 2 - 3 files changed, 23 insertions(+), 45 deletions(-) diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java index 5fb939a42f..efdba39a8b 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java @@ -1,6 +1,5 @@ package com.netgrif.application.engine.auth.service; -import com.netgrif.application.engine.adapter.spring.utils.PageableUtils; import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.repository.GroupRepository; import com.netgrif.application.engine.objects.auth.domain.Authority; @@ -16,7 +15,6 @@ import org.springframework.data.domain.Pageable; import java.util.*; -import java.util.stream.Collectors; @Slf4j @Getter @@ -185,19 +183,6 @@ public void populateMembers(Group group) { }); } - @Override - public Page getAllCoMembers(IUser user, Pageable pageable) { - // TODO JOFO: rework for pagination - Page userMembershipGroups = groupRepository.findAllByMemberIdsContains(user.getStringId(), pageable); - IUser system = userService.getSystem(); - return PageableUtils.listToPage( - userMembershipGroups.stream().map(Group::getMemberIds).flatMap(Set::stream) - .filter(id -> !id.equals(user.getStringId()) && !id.equals(system.getStringId())) - .collect(Collectors.toList()), - userMembershipGroups.getPageable() - ) ; - } - @Override public Page findByPredicate(Predicate predicate, Pageable pageable) { return groupRepository.findAll(predicate, pageable); diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java index f76d369aa3..63dfa7bcb2 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.MongoTemplate; @@ -313,46 +314,35 @@ public IUser findByEmail(String email, String realmId) { @Override public Page findAllCoMembers(com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { - // TODO JOFO: rework for pagination - Set members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()).stream().collect(Collectors.toSet()); - members.add(loggedUser.getSelfOrImpersonated().getId()); - Set objMembers = members.stream().map(ObjectId::new).collect(Collectors.toSet()); - String collectionName = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); - return changeType(userRepository.findAllByIdInAndState(objMembers, UserState.ACTIVE, pageable, mongoTemplate, collectionName)); + return this.searchAllCoMembers(null, loggedUser, pageable); } @Override public Page searchAllCoMembers(String query, com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { - // TODO JOFO: rework for pagination - Page members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()); - members.getContent().add(loggedUser.getSelfOrImpersonated().getId()); + IUser user = this.findById(loggedUser.getSelfOrImpersonated().getId(), loggedUser.getSelfOrImpersonated().getRealmId()); + BooleanExpression predicate = buildPredicate(user, query); String collectionName = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); - return changeType(userRepository.findAllByQuery(buildPredicate(members.stream().map(ObjectId::new) - .collect(Collectors.toSet()), query), pageable, mongoTemplate, collectionName)); - + Page users = userRepository.findAllByQuery(predicate, pageable, mongoTemplate, collectionName); + return changeType(users); } @Override public Page searchAllCoMembers(String query, List roleIds, List negateRoleIds, com.netgrif.application.engine.objects.auth.domain.LoggedUser loggedUser, Pageable pageable) { - // TODO JOFO: rework for pagination!!! - if ((roleIds == null || roleIds.isEmpty()) && (negateRoleIds == null || negateRoleIds.isEmpty())) + if ((roleIds == null || roleIds.isEmpty()) && (negateRoleIds == null || negateRoleIds.isEmpty())) { return searchAllCoMembers(query, loggedUser, pageable); - - if (negateRoleIds == null) { - negateRoleIds = new ArrayList<>(); } - - Page members = groupService.getAllCoMembers(loggedUser.getSelfOrImpersonated().transformToUser(), Pageable.unpaged()); - members.getContent().add(loggedUser.getSelfOrImpersonated().getId()); - BooleanExpression predicate = buildPredicate(members.stream().map(ObjectId::new).collect(Collectors.toSet()), query); - if (!(roleIds == null || roleIds.isEmpty())) { + IUser user = this.findById(loggedUser.getSelfOrImpersonated().getId(), loggedUser.getSelfOrImpersonated().getRealmId()); + BooleanExpression predicate = buildPredicate(user, query); + if (roleIds != null && !roleIds.isEmpty()) { predicate = predicate.and(QUser.user.processRoles.any()._id.in(roleIds)); } - predicate = predicate.and(QUser.user.processRoles.any()._id.in(negateRoleIds).not()); - String collection = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); - Page users = userRepository.findAllByQuery(predicate, pageable, mongoTemplate, collection); + if (negateRoleIds != null && !negateRoleIds.isEmpty()) { + predicate = predicate.and(QUser.user.processRoles.any()._id.in(negateRoleIds).not()); + } + String collectionName = collectionNameProvider.getCollectionNameForRealm(loggedUser.getRealmId()); + Page users = userRepository.findAllByQuery(predicate, pageable, mongoTemplate, collectionName); return changeType(users); } @@ -367,7 +357,7 @@ public Page findAllByIds(Collection ids, String realmId, Pageable @Override public Page findAllActiveByProcessRoles(Set roleIds, Pageable pageable, String realmId) { String collection = collectionNameProvider.getCollectionNameForRealm(realmId); - Page users = userRepository.findDistinctByStateAndProcessRoles__idIn( UserState.ACTIVE, roleIds, pageable, mongoTemplate, collection); + Page users = userRepository.findDistinctByStateAndProcessRoles__idIn(UserState.ACTIVE, roleIds, pageable, mongoTemplate, collection); return changeType(users); } @@ -630,10 +620,15 @@ private Page changeType(Page users) { return users.map(IUser.class::cast); } - private BooleanExpression buildPredicate(Set members, String query) { + private BooleanExpression buildPredicate(IUser user, String query) { + IUser system = this.getSystem(); BooleanExpression predicate = QUser.user - .id.in(members) + .groupIds.any().in(user.getGroupIds()) + .and(QUser.user.id.ne(new ObjectId(system.getStringId()))) .and(QUser.user.state.eq(UserState.ACTIVE)); + if (query == null) { + return predicate; + } for (String word : query.split(" ")) { predicate = predicate .andAnyOf(QUser.user.email.containsIgnoreCase(word), diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java index 351f853b7c..3bbbe61a11 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java @@ -48,8 +48,6 @@ public interface GroupService { void populateMembers(Group group); - Page getAllCoMembers(IUser user, Pageable pageable); - Page findByPredicate(Predicate predicate, Pageable pageable); Group assignAuthority(String groupId, String authorityId); From 25a21db366aeb5be8060f82692790ab03126f8f3 Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Wed, 2 Jul 2025 12:56:51 +0200 Subject: [PATCH 4/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Fixed pagination response for WorkflowController where pageSize and pageNumber were flipped --- .../engine/workflow/web/WorkflowController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java index 61a9b9c9e4..25c83ca385 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java @@ -102,7 +102,7 @@ public PagedModel getAll(Pageable pageable, PagedResourcesAssemble .getAll(pageable, assembler)).withRel("all"); PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageNumber(), pageable.getPageSize(), cases.getTotalElements())); + return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } @Operation(summary = "Generic case search with QueryDSL predicate", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -113,7 +113,7 @@ public PagedModel search2(@QuerydslPredicate(root = Case.class) Pr .search2(predicate, pageable, assembler)).withRel("search2"); PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageNumber(), pageable.getPageSize(), cases.getTotalElements())); + return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } @Operation(summary = "Generic case search on Elasticsearch database", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -126,7 +126,7 @@ public PagedModel search(@RequestBody SingleCaseSearchRequestAsLis PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); ResourceLinkAssembler.addLinks(resources, ElasticCase.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageNumber(), pageable.getPageSize(), cases.getTotalElements())); + return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } @Operation(summary = "Generic case search on Mongo database", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -137,7 +137,7 @@ public PagedModel searchMongo(@RequestBody Map sea .searchMongo(searchBody, pageable, auth, assembler, locale)).withRel("search"); PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageNumber(), pageable.getPageSize(), cases.getTotalElements())); + return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } @@ -165,7 +165,7 @@ public PagedModel findAllByAuthor(@PathVariable("id") String autho .findAllByAuthor(authorId, petriNet, assembler, pageable)).withRel("author"); PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageNumber(), pageable.getPageSize(), cases.getTotalElements())); + return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") From df117d02bdd7cee6c2442c07d595c1669ea49aca Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Thu, 3 Jul 2025 12:17:10 +0200 Subject: [PATCH 5/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Fix serialization issue with Case Icon object --- .../application/engine/objects/petrinet/domain/Icon.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/Icon.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/Icon.java index 5c371cf87c..3cc0068b48 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/Icon.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/Icon.java @@ -3,9 +3,16 @@ import lombok.Getter; import lombok.Setter; +import java.io.Serial; +import java.io.Serializable; + @Setter @Getter -public class Icon { +public class Icon implements Serializable { + + @Serial + private static final long serialVersionUID = -140211037056216078L; + private String key; private String value; From c685b2c6dbe3a567a41b8b9f57348dfcda54ccd9 Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Thu, 3 Jul 2025 15:34:33 +0200 Subject: [PATCH 6/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Add comprehensive documentation and pagination to repositories - Enhanced Javadoc documentation across repositories and services for clarity. - Added methods supporting pagination in key repositories like UserRepository, AuthorityRepository, ProcessRoleRepository, and GroupRepository. - Removed unused methods in ElasticPetriNetService and related interfaces for cleaner implementation. --- .../domain/ElasticPetriNetRepository.java | 19 +- .../service/ElasticPetriNetService.java | 37 +-- .../interfaces/IElasticPetriNetService.java | 8 - .../repositories/PetriNetRepository.java | 47 +++- .../domain/roles/ProcessRoleRepository.java | 95 ++++++- .../service/interfaces/IPetriNetService.java | 240 +++++++++++++++++- .../service/interfaces/IWorkflowService.java | 8 - .../workflow/web/WorkflowController.java | 10 +- .../engine-processes/preference_item.xml | 6 +- .../spring/utils/PaginationProperties.java | 16 +- .../engine/auth/service/GroupServiceImpl.java | 30 ++- .../auth/repository/AuthorityRepository.java | 23 +- .../auth/repository/GroupRepository.java | 39 ++- .../auth/repository/UserRepository.java | 157 +++++++++++- .../engine/auth/service/AuthorityService.java | 30 ++- .../requestbodies/UserSearchRequestBody.java | 19 ++ 16 files changed, 679 insertions(+), 105 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNetRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNetRepository.java index 9a5da428d8..7f86ffa915 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNetRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNetRepository.java @@ -6,12 +6,25 @@ import java.util.List; +/** + * Repository interface for managing {@link ElasticPetriNet} entities in Elasticsearch. + * Extends {@link ElasticsearchRepository} to provide CRUD operations and additional query methods. + */ @Repository public interface ElasticPetriNetRepository extends ElasticsearchRepository { + /** + * Finds an {@link ElasticPetriNet} entity by its string ID. + * + * @param stringId the string ID of the {@link ElasticPetriNet} to find + * @return the {@link ElasticPetriNet} entity with the given string ID, or {@code null} if none found + */ ElasticPetriNet findByStringId(String stringId); - List findAllByUriNodeId(String uriNodeId); - + /** + * Deletes all {@link ElasticPetriNet} entities with the given string ID. + * + * @param id the string ID of the {@link ElasticPetriNet} entities to delete + */ void deleteAllByStringId(String id); -} +} \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java index d9fc4b0c08..d5443d7e29 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java @@ -1,20 +1,13 @@ package com.netgrif.application.engine.elastic.service; -import com.netgrif.application.engine.objects.elastic.domain.ElasticPetriNet; import com.netgrif.application.engine.elastic.domain.ElasticPetriNetRepository; import com.netgrif.application.engine.elastic.service.executors.Executor; import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetService; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.objects.elastic.domain.ElasticPetriNet; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.stream.Collectors; - @Service @Slf4j public class ElasticPetriNetService implements IElasticPetriNetService { @@ -23,19 +16,11 @@ public class ElasticPetriNetService implements IElasticPetriNetService { private final Executor executors; - private IPetriNetService petriNetService; - public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors) { this.repository = repository; this.executors = executors; } - @Lazy - @Autowired - public void setPetriNetService(IPetriNetService petriNetService) { - this.petriNetService = petriNetService; - } - @Override public void index(ElasticPetriNet net) { executors.execute(net.getStringId(), () -> { @@ -69,24 +54,4 @@ public void remove(String id) { log.info("[" + id + "]: PetriNet \"" + id + "\" deleted"); }); } - - @Override - public String findUriNodeId(PetriNet net) { - if (net == null) { - return null; - } - ElasticPetriNet elasticPetriNet = repository.findByStringId(net.getStringId()); - if (elasticPetriNet == null) { - log.warn("[" + net.getStringId() + "] PetriNet with id [" + net.getStringId() + "] is not indexed."); - return null; - } - - return elasticPetriNet.getUriNodeId(); - } - - @Override - public List findAllByUriNodeId(String uriNodeId) { - List elasticPetriNets = repository.findAllByUriNodeId(uriNodeId); - return petriNetService.findAllById(elasticPetriNets.stream().map(ElasticPetriNet::getStringId).collect(Collectors.toList())); - } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java index 02a36e50a7..7cd005f6c6 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java @@ -1,11 +1,8 @@ package com.netgrif.application.engine.elastic.service.interfaces; import com.netgrif.application.engine.objects.elastic.domain.ElasticPetriNet; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import org.springframework.scheduling.annotation.Async; -import java.util.List; - public interface IElasticPetriNetService { @Async @@ -14,9 +11,4 @@ public interface IElasticPetriNetService { void indexNow(ElasticPetriNet net); void remove(String id); - - String findUriNodeId(PetriNet net); - - List findAllByUriNodeId(String uriNodeId); - } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java index d4c81e0d46..641a0c50af 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java @@ -8,23 +8,52 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; -import java.util.List; - +/** + * Repository interface for handling operations on {@link PetriNet} entities. + * Extends {@link MongoRepository} for standard CRUD operations and {@link QuerydslPredicateExecutor} + * to support dynamic queries. + */ public interface PetriNetRepository extends MongoRepository, QuerydslPredicateExecutor { - List findByTitle_DefaultValue(String title); - + /** + * Finds a {@link PetriNet} entity by its import identifier. + * + * @param id the import identifier of the desired PetriNet. + * @return the {@link PetriNet} entity matching the given import identifier, or {@code null} if none found. + */ PetriNet findByImportId(String id); + /** + * Finds a {@link PetriNet} entity by its identifier and version. + * + * @param identifier the unique identifier of the PetriNet. + * @param version the version of the PetriNet. + * @return the {@link PetriNet} entity matching the given identifier and version, or {@code null} if none found. + */ PetriNet findByIdentifierAndVersion(String identifier, Version version); + /** + * Finds a paginated list of {@link PetriNet} entities by their identifier. + * + * @param identifier the unique identifier of the PetriNet. + * @param pageable the pagination details. + * @return a {@link Page} containing the list of matching {@link PetriNet} entities. + */ Page findByIdentifier(String identifier, Pageable pageable); - Page findByIdentifierIn(List identifier, Pageable pageable); - + /** + * Finds a paginated list of {@link PetriNet} entities by their version. + * + * @param version the version of the PetriNet. + * @param pageable the pagination details. + * @return a {@link Page} containing the list of matching {@link PetriNet} entities. + */ Page findAllByVersion(Version version, Pageable pageable); - List findAllByUriNodeId(String uri); - + /** + * Deletes a {@link PetriNet} entity by its unique object ID. + * + * @param id the unique ID of the PetriNet to delete. + */ void deleteBy_id(ObjectId id); -} +} \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java index 5c2bb21fa7..78b4835ab5 100755 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java @@ -9,34 +9,94 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; -import org.springframework.data.util.Pair; import java.util.*; import java.util.stream.Collectors; +/** + * Repository interface for managing {@link ProcessRole} entities in a MongoDB database. + * Extends {@link MongoRepository} for standard CRUD operations and {@link QuerydslPredicateExecutor} + * to support dynamic queries. + */ @Repository public interface ProcessRoleRepository extends MongoRepository, QuerydslPredicateExecutor { + /** + * Finds a {@link ProcessRole} by its import ID. + * + * @param importId the import ID to search for + * @return an {@link Optional} containing the found {@link ProcessRole}, if any + */ Optional findByImportId(String importId); - Page findAllByProcessId(String netId, Pageable pageable); - List findAllByImportIdIn(Set importIds); + /** + * Finds a paginated list of all {@link ProcessRole} entities associated with a specific process ID. + * + * @param netId the process ID + * @param pageable the pageable object for pagination settings + * @return a {@link Page} of {@link ProcessRole} entities + */ + Page findAllByProcessId(String netId, Pageable pageable); + /** + * Finds a paginated list of all {@link ProcessRole} entities by their default name value. + * + * @param name the default name value + * @param pageable the pageable object for pagination settings + * @return a {@link Page} of {@link ProcessRole} entities + */ Page findAllByName_DefaultValue(String name, Pageable pageable); + /** + * Finds a paginated list of all {@link ProcessRole} entities by their import ID with pagination. + * + * @param importId the import ID to filter + * @param pageable the pageable object for pagination settings + * @return a {@link Page} of {@link ProcessRole} entities + */ Page findAllByImportId(String importId, Pageable pageable); + /** + * Finds a paginated list of all global {@link ProcessRole} entities where the global flag is set to true. + * + * @param pageable the pageable object for pagination settings + * @return a {@link Page} of {@link ProcessRole} entities + */ Page findAllByGlobalIsTrue(Pageable pageable); + /** + * Finds a {@link ProcessRole} by its object ID. + * + * @param objectId the object ID to search for + * @return an {@link Optional} containing the found {@link ProcessRole}, if any + */ @Query("{ '_id.objectId': ?0 }") Optional findByIdObjectId(ObjectId objectId); + /** + * Finds all {@link ProcessRole} entities by their object IDs. + * + * @param objectIds a {@link Collection} of object IDs + * @return a {@link List} of {@link ProcessRole} entities + */ @Query("{ '_id.objectId': { $in: ?0 } }") List findByObjectIds(Collection objectIds); + /** + * Deletes all {@link ProcessRole} entities with the provided collection of resource IDs. + * + * @param ids a collection of {@link ProcessResourceId} entities + */ void deleteAllBy_idIn(Collection ids); + /** + * Finds all {@link ProcessRole} entities by a collection of IDs, where the IDs may + * represent either object IDs or composite IDs (containing a network ID and an object ID). + * + * @param ids a collection of process role IDs to filter + * @return a {@link Set} of {@link ProcessRole} entities + */ default Set findAllByIdsSet(Collection ids) { Map> partitionedIds = ids.stream() .collect(Collectors.partitioningBy(id -> id.contains(ProcessResourceId.ID_SEPARATOR))); @@ -48,10 +108,10 @@ default Set findAllByIdsSet(Collection ids) { List forNetworkObjectIds = new ArrayList<>(); List forNetworkNetworkIds = new ArrayList<>(); partitionedIds.get(true).forEach(id -> { - String[] parts = id.split(ProcessResourceId.ID_SEPARATOR); - forNetworkNetworkIds.add(parts[0]); - forNetworkObjectIds.add(new ObjectId(parts[1])); - }); + String[] parts = id.split(ProcessResourceId.ID_SEPARATOR); + forNetworkNetworkIds.add(parts[0]); + forNetworkObjectIds.add(new ObjectId(parts[1])); + }); List processRoles = new ArrayList<>(); processRoles.addAll(findByObjectIds(forObjectIds)); @@ -59,6 +119,13 @@ default Set findAllByIdsSet(Collection ids) { return new HashSet<>(processRoles); } + /** + * Finds a {@link ProcessRole} by its composite ID. The composite ID may represent either a single + * object ID or a combination of network ID and object ID. + * + * @param compositeId the composite ID to search for + * @return an {@link Optional} containing the found {@link ProcessRole}, if any + */ default Optional findByCompositeId(String compositeId) { String[] parts = compositeId.split(ProcessResourceId.ID_SEPARATOR); if (parts.length == 2) { @@ -70,9 +137,23 @@ default Optional findByCompositeId(String compositeId) { } } + /** + * Finds a {@link ProcessRole} by a network ID and object ID. + * + * @param networkId the short process ID + * @param objectId the object ID + * @return an {@link Optional} containing the found {@link ProcessRole}, if any + */ @Query("{ '_id.shortProcessId': ?0, '_id.objectId': ?1 }") Optional findByNetworkIdAndObjectId(String networkId, ObjectId objectId); + /** + * Finds all {@link ProcessRole} entities matching the specified network IDs and object IDs. + * + * @param networkIds a collection of short process IDs + * @param objectIds a collection of object IDs + * @return a {@link List} of {@link ProcessRole} entities + */ @Query("{ $or: [ { '_id.shortProcessId': ?0, '_id.objectId': ?1 } ] }") List findByNetworkIdsAndObjectIds(Collection networkIds, Collection objectIds); } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index c9cbf50bd9..73921f1e2f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -24,79 +24,315 @@ import java.io.InputStream; import java.util.*; +/** + * Interface defining methods for managing {@link PetriNet} objects. + */ public interface IPetriNetService { + /** + * Transforms a {@link PetriNet} into a {@link PetriNetReference}. + * + * @param net the PetriNet to transform + * @param locale the locale to use for translations + * @return a {@link PetriNetReference} representing the given PetriNet + */ static PetriNetReference transformToReference(PetriNet net, Locale locale) { - //return new PetriNetReference(net.getStringId(), net.getIdentifier(), net.getVersion(), net.getTitle().getTranslation(locale), net.getInitials(), net.getTranslatedDefaultCaseName(locale)); return new PetriNetReference(net, locale); } + /** + * Transforms a {@link Transition} into a {@link TransitionReference}. + * + * @param net the {@link PetriNet} containing the transition + * @param transition the transition to transform + * @param locale the locale to use for translations + * @return a {@link TransitionReference} for the given transition + */ static TransitionReference transformToReference(PetriNet net, Transition transition, Locale locale) { List list = new ArrayList<>(); transition.getImmediateData().forEach(fieldId -> list.add(new com.netgrif.application.engine.workflow.web.responsebodies.DataFieldReference(net.getDataSet().get(fieldId), locale))); return new TransitionReference(transition.getStringId(), transition.getTitle().getTranslation(locale), net.getStringId(), list); } + /** + * Transforms a {@link Field} into a {@link DataFieldReference}. + * + * @param net the {@link PetriNet} containing the field + * @param transition the {@link Transition} containing the field + * @param field the field to transform + * @param locale the locale to use for translations + * @return a {@link DataFieldReference} for the given field + */ static DataFieldReference transformToReference(PetriNet net, Transition transition, Field field, Locale locale) { return new DataFieldReference(field.getStringId(), field.getName().getTranslation(locale), net.getStringId(), transition.getStringId()); } + /** + * Imports a PetriNet from XML input. + * + * @param xmlFile the input stream of the XML file + * @param releaseType the type of release + * @param user the user performing the import + * @return an {@link ImportPetriNetEventOutcome} representing the result + * @throws IOException if an I/O error occurs + * @throws MissingPetriNetMetaDataException if metadata is incomplete + * @throws MissingIconKeyException if an icon key is missing + * @deprecated Use {@link #importPetriNet(InputStream, VersionType, LoggedUser)} instead. + */ @Deprecated ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, String releaseType, LoggedUser user) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException; + /** + * Imports a PetriNet from XML input. + * + * @param xmlFile the input stream of the XML file + * @param releaseType the type of release {@link VersionType} + * @param user the user performing the import + * @return an {@link ImportPetriNetEventOutcome} representing the result + * @throws IOException if an I/O error occurs + * @throws MissingPetriNetMetaDataException if metadata is incomplete + * @throws MissingIconKeyException if an icon key is missing + */ ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, VersionType releaseType, LoggedUser user) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException; + + /** + * Imports a PetriNet from XML input. + * + * @param xmlFile the input stream of the XML file + * @param releaseType the type of release {@link VersionType} + * @param user the user performing the import + * @param params additional parameters + * @return an {@link ImportPetriNetEventOutcome} representing the result + * @throws IOException if an I/O error occurs + * @throws MissingPetriNetMetaDataException if metadata is incomplete + * @throws MissingIconKeyException if an icon key is missing + */ ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, VersionType releaseType, LoggedUser user, Map params) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException; + /** + * Saves a PetriNet object. + * + * @param petriNet the PetriNet to save + * @return an {@link Optional} containing the saved PetriNet or empty if unsuccessful + */ Optional save(PetriNet petriNet); + /** + * Retrieves a {@link PetriNet} by its ID. + * + * @param id the ID of the PetriNet + * @return the corresponding {@link PetriNet} + */ PetriNet getPetriNet(String id); + /** + * Retrieves a {@link PetriNet} by its identifier and version. + * + * @param identifier the unique identifier of the PetriNet + * @param version the version of the PetriNet + * @return the {@link PetriNet} matching the provided identifier and version + */ PetriNet getPetriNet(String identifier, Version version); + /** + * Retrieves a paginated list of {@link PetriNet} objects by their identifier. + * + * @param identifier the unique identifier of the PetriNets + * @param pageable the pagination information + * @return a paginated list of {@link PetriNet} objects + */ Page getByIdentifier(String identifier, Pageable pageable); + /** + * Finds all {@link PetriNet} objects by their IDs. + * + * @param ids a list of PetriNet IDs to retrieve + * @return a list of {@link PetriNet} objects matching the provided IDs + */ List findAllById(List ids); + /** + * Retrieves the newest version of a {@link PetriNet} by its identifier. + * + * @param identifier the unique identifier of the PetriNet + * @return the newest version of the {@link PetriNet} matching the provided identifier + */ PetriNet getNewestVersionByIdentifier(String identifier); + /** + * Retrieves a paginated list of all {@link PetriNet} objects. + * + * @param pageable the pagination information + * @return a paginated list of all {@link PetriNet} objects + */ Page getAll(Pageable pageable); + /** + * Retrieves a {@link FileSystemResource} representing a file associated with a {@link PetriNet}. + * + * @param netId the unique ID of the PetriNet + * @param title the title of the file to retrieve + * @return a {@link FileSystemResource} containing the file + */ FileSystemResource getFile(String netId, String title); + /** + * Retrieves a paginated list of PetriNet references accessible by the given user. + * + * @param user the logged-in user + * @param locale the locale for translations + * @param pageable the pagination information + * @return a {@link Page} of {@link PetriNetReference} objects + */ Page getReferences(LoggedUser user, Locale locale, Pageable pageable); + /** + * Retrieves a paginated list of {@link PetriNetReference} objects by their identifier. + * + * @param identifier the unique identifier of the PetriNet + * @param user the logged-in user making the request + * @param locale the locale for translations + * @param pageable the pagination information + * @return a paginated list of {@link PetriNetReference} objects + */ Page getReferencesByIdentifier(String identifier, LoggedUser user, Locale locale, Pageable pageable); + /** + * Retrieves a paginated list of {@link PetriNetReference} objects by version. + * + * @param version the {@link Version} of the PetriNet + * @param user the logged-in user making the request + * @param locale the locale for translations + * @param pageable the pagination information + * @return a paginated list of {@link PetriNetReference} objects + */ Page getReferencesByVersion(Version version, LoggedUser user, Locale locale, Pageable pageable); + /** + * Retrieves a list of {@link PetriNetReference} objects accessible by the user's process roles. + * + * @param user the logged-in user making the request + * @param locale the locale for translations + * @return a list of {@link PetriNetReference} objects accessible by the user + */ List getReferencesByUsersProcessRoles(LoggedUser user, Locale locale); + /** + * Retrieves a single {@link PetriNetReference} by identifier and version. + * + * @param identifier the unique identifier of the PetriNet + * @param version the {@link Version} of the PetriNet + * @param user the logged-in user making the request + * @param locale the locale for translations + * @return the {@link PetriNetReference} object corresponding to the given identifier and version + */ PetriNetReference getReference(String identifier, Version version, LoggedUser user, Locale locale); + /** + * Retrieves a list of {@link TransitionReference} objects for the given PetriNet IDs. + * + * @param netsIds the list of PetriNet IDs for which to retrieve transitions + * @param user the logged-in user making the request + * @param locale the locale for translations + * @return a list of {@link TransitionReference} objects for the given PetriNet IDs + */ List getTransitionReferences(List netsIds, LoggedUser user, Locale locale); + /** + * Retrieves a list of {@link DataFieldReference} objects for the given transition references. + * + * @param transitions the list of {@link TransitionReference} objects + * @param locale the locale for translations + * @return a list of {@link DataFieldReference} objects + */ List getDataFieldReferences(List transitions, Locale locale); + /** + * Performs a search for {@link PetriNetReference} objects based on the provided criteria. + * + * @param criteria the {@link PetriNetSearch} criteria to filter results + * @param user the logged-in user making the request + * @param pageable the pagination information + * @param locale the locale for translations + * @return a paginated list of {@link PetriNetReference} objects matching the criteria + */ Page search(PetriNetSearch criteria, LoggedUser user, Pageable pageable, Locale locale); + /** + * Finds a {@link PetriNet} by its import ID. + * + * @param id the import ID of the PetriNet + * @return an {@link Optional} containing the {@link PetriNet} if found, otherwise empty + */ Optional findByImportId(String id); + /** + * Evicts all cached PetriNet data from caches. + */ void evictAllCaches(); + /** + * Evicts the cache for the given {@link PetriNet}. + * + * @param net the {@link PetriNet} to remove from the cache + */ void evictCache(PetriNet net); + /** + * Retrieves a {@link PetriNet} by its MongoDB {@link ObjectId}. + * + * @param petriNetId the {@link ObjectId} of the PetriNet to retrieve + * @return the {@link PetriNet} associated with the given ID + */ PetriNet get(ObjectId petriNetId); + /** + * Retrieves a list of {@link PetriNet} objects by their MongoDB {@link ObjectId}s. + * + * @param petriNetId a collection of {@link ObjectId}s of the PetriNets to retrieve + * @return a list of {@link PetriNet} objects corresponding to the provided IDs + */ List get(Collection petriNetId); + /** + * Retrieves a list of {@link PetriNet} objects by their unique string identifiers. + * + * @param petriNetIds a list of unique identifiers of PetriNets to retrieve + * @return a list of {@link PetriNet} objects corresponding to the provided IDs + */ List get(List petriNetIds); + /** + * Deletes a PetriNet by its ID. + * + * @param id the ID of the PetriNet to delete + * @param loggedUser the user requesting the deletion + */ void deletePetriNet(String id, LoggedUser loggedUser); + /** + * Runs the specified set of actions on a PetriNet. + * + * @param actions the actions to execute + * @param petriNet the PetriNet on which actions are executed + */ void runActions(List actions, PetriNet petriNet); + /** + * Retrieves a list of existing PetriNet identifiers from the given list of identifiers. + * Each provided identifier is validated, and only identifiers corresponding to existing PetriNets are returned. + * + * @param identifiers the list of PetriNet identifiers to validate + * @return a list of validated and existing PetriNet identifiers + */ List getExistingPetriNetIdentifiersFromIdentifiersList(List identifiers); + /** + * Retrieves the reference of a {@link PetriNet} associated with a case ID. + * + * @param caseId the ID of the workflow case + * @return a {@link PetriNetImportReference} linking the PetriNet + */ PetriNetImportReference getNetFromCase(String caseId); -} +} \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java index 436a376bc4..aca8f7b720 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java @@ -3,7 +3,6 @@ import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.objects.petrinet.domain.I18nString; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; -import com.netgrif.application.engine.objects.petrinet.domain.dataset.Field; import com.netgrif.application.engine.objects.workflow.domain.Case; import com.netgrif.application.engine.objects.workflow.domain.Task; import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome; @@ -68,20 +67,13 @@ public interface IWorkflowService { Map listToMap(List cases); -// @Deprecated -// List getData(String caseId); - Page search(Map request, Pageable pageable, LoggedUser user, Locale locale); long count(Map request, LoggedUser user, Locale locale); -// List getCaseFieldChoices(Pageable pageable, String caseId, String fieldId); - boolean removeTasksFromCase(List tasks, String caseId); boolean removeTasksFromCase(List tasks, Case useCase); - Case decrypt(Case useCase); - Page search(Predicate predicate, Pageable pageable); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java index 25c83ca385..98ea7e6da8 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java @@ -94,7 +94,7 @@ public EntityModel createCase(@RequestBody CreateCaseBo } } - @Operation(summary = "Get all cases of the system", security = {@SecurityRequirement(name = "BasicAuth")}) + @Operation(summary = "Get all cases of the system, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/all", produces = MediaTypes.HAL_JSON_VALUE) public PagedModel getAll(Pageable pageable, PagedResourcesAssembler assembler) { Page cases = workflowService.getAll(pageable); @@ -105,7 +105,7 @@ public PagedModel getAll(Pageable pageable, PagedResourcesAssemble return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } - @Operation(summary = "Generic case search with QueryDSL predicate", security = {@SecurityRequirement(name = "BasicAuth")}) + @Operation(summary = "Generic case search with QueryDSL predicate, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search2", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel search2(@QuerydslPredicate(root = Case.class) Predicate predicate, Pageable pageable, PagedResourcesAssembler assembler) { Page cases = workflowService.search(predicate, pageable); @@ -116,7 +116,7 @@ public PagedModel search2(@QuerydslPredicate(root = Case.class) Pr return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } - @Operation(summary = "Generic case search on Elasticsearch database", security = {@SecurityRequirement(name = "BasicAuth")}) + @Operation(summary = "Generic case search on Elasticsearch database, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel search(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Pageable pageable, PagedResourcesAssembler assembler, Authentication auth, Locale locale) { LoggedUser user = (LoggedUser) auth.getPrincipal(); @@ -129,7 +129,7 @@ public PagedModel search(@RequestBody SingleCaseSearchRequestAsLis return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } - @Operation(summary = "Generic case search on Mongo database", security = {@SecurityRequirement(name = "BasicAuth")}) + @Operation(summary = "Generic case search on Mongo database, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search_mongo", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel searchMongo(@RequestBody Map searchBody, Pageable pageable, Authentication auth, PagedResourcesAssembler assembler, Locale locale) { Page cases = workflowService.search(searchBody, pageable, (LoggedUser) auth.getPrincipal(), locale); @@ -157,7 +157,7 @@ public CaseResource getOne(@PathVariable("id") String caseId) { return new CaseResource(aCase); } - @Operation(summary = "Get all cases by user that created them", security = {@SecurityRequirement(name = "BasicAuth")}) + @Operation(summary = "Get all cases by user that created them, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @RequestMapping(value = "/case/author/{id}", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel findAllByAuthor(@PathVariable("id") String authorId, @RequestBody String petriNet, PagedResourcesAssembler assembler, Pageable pageable) { Page cases = workflowService.findAllByAuthor(authorId, petriNet, pageable); diff --git a/application-engine/src/main/resources/petriNets/engine-processes/preference_item.xml b/application-engine/src/main/resources/petriNets/engine-processes/preference_item.xml index 719c2bbe78..eec1f3aa97 100644 --- a/application-engine/src/main/resources/petriNets/engine-processes/preference_item.xml +++ b/application-engine/src/main/resources/petriNets/engine-processes/preference_item.xml @@ -147,9 +147,9 @@ moveDestUri: f.move_dest_uri; - String uriNodeId = elasticCaseService.findUriNodeId(useCase) - def node = uriService.findById(uriNodeId) - updateMultichoiceWithCurrentNode(moveDestUri, node) +// String uriNodeId = elasticCaseService.findUriNodeId(useCase) +// def node = uriService.findById(uriNodeId) +// updateMultichoiceWithCurrentNode(moveDestUri, node) prevDestUri: f.move_previous_dest_uri, diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java index b39bb79382..d2ccbaf0a6 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java @@ -4,12 +4,26 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +/** + * Configuration properties class for pagination settings in the application. + * This class retrieves and holds pagination-related properties defined under the {@code netgrif.pagination} prefix. + */ @Data @Configuration @ConfigurationProperties(prefix = "netgrif.pagination") public class PaginationProperties { + /** + * The size of a single page for backend pagination. + * This value determines the number of records or elements that should be fetched per page in backend-related operations. + * The default value is {@code 100}. + */ private int backendPageSize = 100; + /** + * The size of a single page for frontend pagination. + * This value determines the number of records or elements that should be displayed per page in the application's frontend. + * The default value is {@code 20}. + */ private int frontendPageSize = 20; -} +} \ No newline at end of file diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java index efdba39a8b..49b7c7f1a7 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.auth.service; +import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.repository.GroupRepository; import com.netgrif.application.engine.objects.auth.domain.Authority; @@ -7,11 +8,13 @@ import com.netgrif.application.engine.objects.auth.domain.IUser; import com.netgrif.application.engine.objects.common.ResourceNotFoundException; import com.netgrif.application.engine.objects.common.ResourceNotFoundExceptionCode; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import com.querydsl.core.types.Predicate; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import java.util.*; @@ -30,6 +33,8 @@ public class GroupServiceImpl implements GroupService { private Group defaultSystemGroup; + private PaginationProperties paginationProperties; + @Autowired public void setGroupRepository(GroupRepository groupRepository) { this.groupRepository = groupRepository; @@ -50,6 +55,11 @@ public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; } + @Autowired + public void setPaginationProperties(PaginationProperties paginationProperties) { + this.paginationProperties = paginationProperties; + } + @Override public void delete(Group group) { if (!groupRepository.existsById(group.getStringId())) { @@ -98,7 +108,7 @@ public Group getDefaultSystemGroup() { @Override public Group create(IUser user) { log.info("Creating default group for user: [{}]", user.getStringId()); - Set userGroups = groupRepository.findByOwnerId(user.getStringId()); + Page userGroups = groupRepository.findByOwnerId(user.getStringId(), Pageable.ofSize(1)); if (!userGroups.isEmpty() && !Objects.equals(user.getStringId(), userService.getSystem().getStringId())) { throw new IllegalArgumentException("Default group for user [%s] already exists.".formatted(user.getUsername())); } @@ -121,12 +131,20 @@ public Group create(String identifier, String title, IUser user) { @Override public Group getDefaultUserGroup(IUser user) { - Set userGroup = groupRepository.findByOwnerId(user.getStringId()); String errorMessage = "Default user group for user [%s] does not exist.".formatted(user.getUsername()); - if (userGroup.isEmpty()) { - throw new ResourceNotFoundException(ResourceNotFoundExceptionCode.DEFAULT_USER_GROUP_NOT_FOUND, errorMessage); - } - return userGroup.stream().filter(g -> g.getIdentifier().equals(user.getUsername())).findFirst().orElseThrow(() -> new ResourceNotFoundException(ResourceNotFoundExceptionCode.DEFAULT_USER_GROUP_NOT_FOUND, errorMessage)); + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page userGroups; + do { + userGroups = groupRepository.findByOwnerId(user.getStringId(), pageable); + + Optional group = userGroups.stream().filter(g -> g.getIdentifier().equals(user.getUsername())).findFirst(); + if (group.isPresent()) { + return group.get(); + } + + pageable = pageable.next(); + } while (userGroups.hasNext()); + throw new ResourceNotFoundException(ResourceNotFoundExceptionCode.DEFAULT_USER_GROUP_NOT_FOUND, errorMessage); } @Override diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java index 79252267f1..faddbf55eb 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java @@ -6,14 +6,31 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; + import java.util.List; import java.util.Optional; +/** + * Repository interface for managing {@link Authority} entities in the MongoDB database. + * Extends the {@link MongoRepository} interface for basic CRUD operations. + */ @Repository public interface AuthorityRepository extends MongoRepository { - Optional findByName(String name); - List findAllByNameStartsWith(String prefix); + /** + * Finds an {@link Authority} entity by its name. + * + * @param name the name of the authority to search for + * @return an {@link Optional} containing the {@link Authority} if found, or empty if not found + */ + Optional findByName(String name); + /** + * Retrieves a paginated list of {@link Authority} entities whose IDs match a given list of {@link ObjectId}s. + * + * @param ids a list of {@link ObjectId}s to search for + * @param pageable the pagination information defined by a {@link Pageable} object + * @return a {@link Page} containing the matching {@link Authority} entities + */ Page findAllBy_idIn(List ids, Pageable pageable); -} +} \ No newline at end of file diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java index ad5150fe87..7cfc8a7820 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java @@ -9,17 +9,46 @@ import java.util.Collection; import java.util.Optional; -import java.util.Set; +/** + * Repository interface for managing {@link Group} entities in the MongoDB database. + * Extends the {@link MongoRepository} interface for basic CRUD operations. + */ @Repository public interface GroupRepository extends MongoRepository, QuerydslPredicateExecutor { - Set findByOwnerId(String id); - Optional findByIdentifier(String identifier); + /** + * Finds paginated list of all {@link Group} entities that have the given owner ID. + * + * @param id the ID of the owner + * @param pageable the pagination information + * @return a set of {@link Group} entities associated with the specified owner ID + */ + Page findByOwnerId(String id, Pageable pageable); - Page findAllByMemberIdsContains(String memberId, Pageable pageable); + /** + * Finds a {@link Group} by its unique identifier. + * + * @param identifier the unique identifier of the group + * @return an {@link Optional} containing the {@link Group} if it exists, otherwise {@code Optional.empty()} + */ + Optional findByIdentifier(String identifier); + /** + * Finds all {@link Group}s with given IDs in a pageable format. + * + * @param ids the collection of group IDs to query + * @param pageable the pagination information + * @return a {@link Page} of {@link Group}s with the specified IDs + */ Page findAllByIdIn(Collection ids, Pageable pageable); + /** + * Finds all {@link Group}s associated with a particular realm ID in a pageable format. + * + * @param realmId the ID of the realm + * @param pageable the pagination information + * @return a {@link Page} of {@link Group}s belonging to the specified realm + */ Page findAllByRealmId(String realmId, Pageable pageable); -} +} \ No newline at end of file diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java index 3f557f0240..9f60c1d2a4 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/UserRepository.java @@ -24,24 +24,61 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * Repository interface for managing {@link User} entities in the MongoDB database. + * Extends the {@link MongoRepository} interface for basic CRUD operations. + */ @Repository public interface UserRepository extends MongoRepository, QuerydslPredicateExecutor { + /** + * Throws an exception since this method is not supported. + * Use {@link #saveUser(User, MongoTemplate, String)} instead. + * + * @param entity the entity to save + * @param subtype of {@link User} + * @return never returns, always throws exception + * @throws UnsupportedOperationException as this method is not supported + */ @Override default S save(S entity) { throw new UnsupportedOperationException("This method is not supported. Use 'UserRepository.saveUser' instead.'"); } + /** + * Throws an exception since this method is not supported. + * Use {@link #findAllByQuery(Predicate, Pageable, MongoTemplate, String)} instead. + * + * @param predicate filter condition + * @param pageable pagination details + * @return never returns, always throws exception + * @throws UnsupportedOperationException as this method is not supported + */ @Override default Page findAll(Predicate predicate, Pageable pageable) { throw new UnsupportedOperationException("This method is not supported. Use 'UserRepository.findAll(Predicate, Pageable, MongoTemplate, Collection)' instead.'"); } + /** + * Throws an exception since this method is not supported. + * Use {@link #deleteAll(MongoTemplate, Collection)} instead. + * + * @throws UnsupportedOperationException as this method is not supported + */ @Override default void deleteAll() { throw new UnsupportedOperationException("This method is not supported. Use 'UserRepository.deleteAll(MongoTemplate, Collection)' instead.'"); } + /** + * Finds a paginated list of all {@link User} entities matching the given {@link Predicate}. + * + * @param predicate the condition to filter users + * @param pageable pagination details + * @param mongoTemplate the MongoDB template + * @param collection the target collection + * @return paginated result of users matching the condition + */ default Page findAllByQuery(Predicate predicate, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Assert.notNull(predicate, "Predicate must not be null"); Assert.notNull(pageable, "Pageable must not be null"); @@ -50,32 +87,79 @@ default Page findAllByQuery(Predicate predicate, Pageable pageable, MongoT return query.fetchPage(pageable).map(User.class::cast); } + /** + * Finds a {@link User} by its ObjectId. + * + * @param objectId the ID of the user to search + * @param mongoTemplate the MongoDB template + * @param collectionName the collection name + * @return optional of the user if found, otherwise empty + */ default Optional findById(ObjectId objectId, MongoTemplate mongoTemplate, String collectionName) { return Optional.ofNullable( mongoTemplate.findOne(Query.query(Criteria.where("id").is(objectId)), com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName) ); } + /** + * Finds a {@link User} by their username. + * + * @param username the username to search for + * @param mongoTemplate the MongoDB template + * @param collectionName the collection name + * @return optional of the user if found, otherwise empty + */ default Optional findByUsername(String username, MongoTemplate mongoTemplate, String collectionName) { return Optional.ofNullable( mongoTemplate.findOne(Query.query(Criteria.where("username").is(username)), com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName) ); } + /** + * Finds a {@link User} by their email address. + * + * @param email the email to search for + * @param mongoTemplate the MongoDB template + * @param collectionName the collection name + * @return optional of the user if found, otherwise empty + */ default Optional findByEmail(String email, MongoTemplate mongoTemplate, String collectionName) { return Optional.ofNullable( mongoTemplate.findOne(Query.query(Criteria.where("email").is(email)), com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName) ); } + /** + * Saves a {@link User} into the specified collection. + * + * @param user the user to save + * @param mongoTemplate the MongoDB template + * @param collectionName the collection to save the user in + * @return the saved user + */ default User saveUser(User user, MongoTemplate mongoTemplate, String collectionName) { return mongoTemplate.save(user, collectionName); } + /** + * Deletes all data from specified collections. + * + * @param mongoTemplate the MongoDB template + * @param collectionName the names of the collections to clear + */ default void deleteAll(MongoTemplate mongoTemplate, Collection collectionName) { collectionName.forEach(collection -> mongoTemplate.remove(new Query(), collection)); } + /** + * Finds a paginated list of all {@link User} entities using a specified {@link Query} + * + * @param query the Mongo query to execute; if null, a new {@link Query} object will be created. + * @param pageable the pagination information to apply to the query results. + * @param mongoTemplate the MongoDB template used to perform the query. + * @param collection the name of the collection where the query will be executed. + * @return a {@link Page} of {@link User} entities obtained from the query results. + */ default Page findAllByQuery(Query query, Pageable pageable, MongoTemplate mongoTemplate, String collection) { if (query == null) { query = new Query(); @@ -83,6 +167,16 @@ default Page findAllByQuery(Query query, Pageable pageable, MongoTemplate return resolveUserPage(pageable, mongoTemplate, collection, query); } + /** + * Finds a paginated list of distinct {@link User}s by their {@link UserState} and process role IDs. + * + * @param state the state of the users + * @param roleId the IDs of the process roles + * @param pageable pagination details + * @param mongoTemplate the MongoDB template + * @param collection the collection name + * @return a paginated list of distinct users with the specified state and process roles + */ default Page findDistinctByStateAndProcessRoles__idIn(UserState state, Collection roleId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("state").is(state) @@ -90,18 +184,45 @@ default Page findDistinctByStateAndProcessRoles__idIn(UserState state, Col return resolveUserPage(pageable, mongoTemplate, collection, query); } + /** + * Finds a paginated list of all {@link User} entities by their associated process role IDs. + * + * @param rolesId the collection of {@link ProcessResourceId}s to filter users by. + * @param pageable the pagination details for the query result. + * @param mongoTemplate the MongoDB template used to perform the query. + * @param collection the name of the collection where the query will be executed. + * @return a {@link Page} containing the {@link User} entities that match the specified process role IDs. + */ default Page findAllByProcessRoles__idIn(Collection rolesId, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("processRoles._id").in(rolesId)); return resolveUserPage(pageable, mongoTemplate, collection, query); } + /** + * Removes all {@link User} entities with the specified state and an expiration date before the given time. + * + * @param state the {@link UserState} to filter users. + * @param dateTime the expiration date before which users will be deleted. + * @param mongoTemplate the MongoDB template used to perform the operation. + * @param collectionNames the set of collection names in which the deletion will be performed. + */ default void removeAllByStateAndExpirationDateBefore(UserState state, LocalDateTime dateTime, MongoTemplate mongoTemplate, Set collectionNames) { collectionNames.forEach(collectionName -> mongoTemplate.remove(Query.query(Criteria.where("state").is(state).and("credentials.token.properties.expirationDate").lt(dateTime)), com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName) ); } + /** + * Finds a paginated list of all {@link User} entities with the specified state and an expiration date before the given time + * + * @param state the {@link UserState} to filter the users. + * @param dateTime the expiration date before which users should be filtered. + * @param pageable the pagination details for the query result. + * @param mongoTemplate the MongoDB template used to perform the query. + * @param collection the name of the collection where the query will be executed. + * @return a {@link Page} containing {@link User} entities matching the specified state and expiration date criteria. + */ default Page findAllByStateAndExpirationDateBefore(UserState state, LocalDateTime dateTime, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("state").is(state) @@ -109,23 +230,43 @@ default Page findAllByStateAndExpirationDateBefore(UserState state, LocalD return resolveUserPage(pageable, mongoTemplate, collection, query); } + /** + * Finds a paginated list of all {@link User}s by their IDs. + * + * @param ids the list of user IDs + * @param pageable pagination details + * @param mongoTemplate the MongoDB template + * @param collection the collection name + * @return a paginated list of users with the specified IDs + */ default Page findAllByIds(Collection ids, Pageable pageable, MongoTemplate mongoTemplate, String collection) { Query query = Query.query( Criteria.where("id").in(ids)); return resolveUserPage(pageable, mongoTemplate, collection, query); } - default Page findAllByIdInAndState(Collection ids, UserState state, Pageable pageable, MongoTemplate mongoTemplate, String collection) { - Query query = Query.query( - Criteria.where("id").in(ids) - .and("state").is(state)); - return resolveUserPage(pageable, mongoTemplate, collection, query); - } - + /** + * Creates a {@link SpringDataMongodbQuery} for {@link com.netgrif.application.engine.adapter.spring.auth.domain.User}. + * + * @param predicate the filter condition to apply to the query. + * @param mongoTemplate the MongoDB template used to execute the query. + * @param collectionName the name of the collection to query. + * @return a {@link SpringDataMongodbQuery} configured with the given predicate and collection name. + */ private SpringDataMongodbQuery createQuery(Predicate predicate, MongoTemplate mongoTemplate, String collectionName) { return new SpringDataMongodbQuery<>(mongoTemplate, com.netgrif.application.engine.adapter.spring.auth.domain.User.class, collectionName).where(predicate); } + /** + * Resolves a {@link PageImpl} of {@link User} entities. + * Helper method to execute queries and handle pagination. + * + * @param pageable the pagination information + * @param mongoTemplate the MongoDB template + * @param collection the collection name + * @param query the query to execute + * @return a paginated implementation of users + */ private static PageImpl resolveUserPage(Pageable pageable, MongoTemplate mongoTemplate, String collection, Query query) { List resultUserList = mongoTemplate.find( query.with(pageable), @@ -137,4 +278,4 @@ private static PageImpl resolveUserPage(Pageable pageable, MongoTemplate m long total = mongoTemplate.count(query.limit(-1).skip(-1), com.netgrif.application.engine.adapter.spring.auth.domain.User.class); return new PageImpl<>(resultUserList, pageable, total); } -} +} \ No newline at end of file diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java index 13860f6ca1..59aeb2b9e9 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java @@ -6,13 +6,41 @@ import java.util.List; +/** + * Service interface for managing {@link Authority} entities. + */ public interface AuthorityService { + /** + * Retrieves all {@link Authority} entities with pagination support. + * + * @param pageable the {@link Pageable} object containing pagination information. + * @return a {@link Page} of {@link Authority} entities. + */ Page findAll(Pageable pageable); + /** + * Retrieves an existing {@link Authority} entity by its name or creates a new one if it does not exist. + * + * @param name the name of the {@link Authority}. + * @return the {@link Authority} entity. + */ Authority getOrCreate(String name); + /** + * Retrieves a single {@link Authority} entity by its unique identifier. + * + * @param id the unique identifier of the {@link Authority}. + * @return the {@link Authority} entity. + */ Authority getOne(String id); + /** + * Retrieves all {@link Authority} entities matching the given list of identifiers, with pagination support. + * + * @param ids the list of unique identifiers for the {@link Authority} entities. + * @param pageable the {@link Pageable} object containing pagination information. + * @return a {@link Page} of {@link Authority} entities. + */ Page findAllByIds(List ids, Pageable pageable); -} +} \ No newline at end of file diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java index f24856fb4e..9c2d38dc1f 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/UserSearchRequestBody.java @@ -4,14 +4,33 @@ import java.util.List; +/** + * Represents the request body for searching users with specific criteria. + */ @Data public class UserSearchRequestBody { + /** + * The ID of the realm in which to search for users. + * This field is used to scope the search to a particular multi-tenancy realm. + */ private String realmId; + /** + * A full-text search string to filter the users. + * The search is typically applied to user attributes like name, username, or email. + */ private String fulltext; + /** + * A list of roles the users must have. + * The search will include users who possess all of the specified roles. + */ private List roles; + /** + * A list of roles the users must not have. + * The search will exclude users who possess any of the specified roles. + */ private List negativeRoles; } \ No newline at end of file From 28da58362793dc938394eefe59dc931e8953baae Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Fri, 4 Jul 2025 16:05:25 +0200 Subject: [PATCH 7/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Fix tests --- .../domain/roles/ProcessRoleRepository.java | 25 ++++++++----------- .../petrinet/service/PetriNetService.java | 19 +++++++++----- .../workflow/service/WorkflowService.java | 6 ----- .../engine/action/AssignActionTest.groovy | 5 ++-- .../engine/auth/UserServiceTest.groovy | 4 +-- .../petrinet/domain/FunctionsTest.groovy | 5 ++-- .../petrinet/domain/PetriNetTest.groovy | 3 ++- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java index 78b4835ab5..ed42cbc04e 100755 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java @@ -103,19 +103,15 @@ default Set findAllByIdsSet(Collection ids) { List forObjectIds = partitionedIds.get(false).stream() .map(ObjectId::new) - .collect(Collectors.toList()); + .toList(); - List forNetworkObjectIds = new ArrayList<>(); - List forNetworkNetworkIds = new ArrayList<>(); - partitionedIds.get(true).forEach(id -> { - String[] parts = id.split(ProcessResourceId.ID_SEPARATOR); - forNetworkNetworkIds.add(parts[0]); - forNetworkObjectIds.add(new ObjectId(parts[1])); - }); + List processResourceIds = partitionedIds.get(true).stream() + .map(ProcessResourceId::new) + .toList(); List processRoles = new ArrayList<>(); processRoles.addAll(findByObjectIds(forObjectIds)); - processRoles.addAll(findByNetworkIdsAndObjectIds(forNetworkNetworkIds, forNetworkObjectIds)); + processRoles.addAll(findByProcessResourceIds(processResourceIds)); return new HashSet<>(processRoles); } @@ -148,12 +144,11 @@ default Optional findByCompositeId(String compositeId) { Optional findByNetworkIdAndObjectId(String networkId, ObjectId objectId); /** - * Finds all {@link ProcessRole} entities matching the specified network IDs and object IDs. + * Finds all {@link ProcessRole} entities by a collection of composite resource IDs. * - * @param networkIds a collection of short process IDs - * @param objectIds a collection of object IDs - * @return a {@link List} of {@link ProcessRole} entities + * @param compositeIds a collection of {@link ProcessResourceId} instances representing the composite IDs to filter + * @return a {@link List} of {@link ProcessRole} entities matching the provided composite IDs */ - @Query("{ $or: [ { '_id.shortProcessId': ?0, '_id.objectId': ?1 } ] }") - List findByNetworkIdsAndObjectIds(Collection networkIds, Collection objectIds); + @Query("{ '_id': { $in: ?0 } }") + List findByProcessResourceIds(Collection compositeIds); } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 447aa56a86..6cc675bb3f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -358,12 +358,19 @@ public Page getReferencesByVersion(Version version, LoggedUse Page references; if (version == null) { GroupOperation groupByIdentifier = Aggregation.group("identifier").max("version").as("version"); - Aggregation aggregation = Aggregation.newAggregation( - groupByIdentifier, - Aggregation.sort(pageable.getSort()), - Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()), - Aggregation.limit(pageable.getPageSize()) - ); + Aggregation aggregation; + if (pageable == null || pageable.isUnpaged()) { + aggregation = Aggregation.newAggregation( + groupByIdentifier + ); + } else { + aggregation = Aggregation.newAggregation( + groupByIdentifier, + Aggregation.sort(pageable.getSort()), + Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()), + Aggregation.limit(pageable.getPageSize()) + ); + } List results = mongoTemplate.aggregate(aggregation, "petriNet", Document.class).getMappedResults(); List referenceList = results.stream() .map(doc -> { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index 84dd4d7a90..e6dca3319d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -456,12 +456,6 @@ public boolean removeTasksFromCase(List tasks, Case useCase) { return useCase.removeTasks(tasks); } - @Override - public Case decrypt(Case useCase) { - decryptDataSet(useCase); - return useCase; - } - @Override public Page searchAll(Predicate predicate) { return search(predicate, new FullPageRequest()); diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy index c8138c3274..af79e522a0 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.domain.Pageable import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.hateoas.MediaTypes import org.springframework.http.MediaType @@ -148,8 +149,8 @@ class AssignActionTest { IUser updatedUser = userService.findByEmail(USER_EMAIL, null) Set roles = updatedUser.getProcessRoles() - String adminMainId = processRoleRepository.findAllByName_DefaultValue("admin_main")?.first()?.stringId - String adminSecondaryId = processRoleRepository.findAllByName_DefaultValue("admin_secondary")?.first()?.stringId + String adminMainId = processRoleRepository.findAllByName_DefaultValue("admin_main", Pageable.ofSize(1))?.first()?.stringId + String adminSecondaryId = processRoleRepository.findAllByName_DefaultValue("admin_secondary", Pageable.ofSize(1))?.first()?.stringId assert roles.find { it.stringId == adminMainId } assert roles.find { it.stringId == adminSecondaryId } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy index 5b7020b010..1bff0d38ef 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy @@ -103,7 +103,7 @@ class UserServiceTest { user = userService.addRole(user, dummyRole.get_id()) assert user.getProcessRoles().size() == 2 && user.getProcessRoles().stream().anyMatch { it.stringId == dummyRole.stringId } - List userList = userService.findAllByProcessRoles(Set.of(dummyRole.get_id()), null) + List userList = userService.findAllByProcessRoles(Set.of(dummyRole.get_id()), null, Pageable.unpaged()) assert userList.size() == 1 && userList.getFirst().stringId == user.stringId } @@ -114,7 +114,7 @@ class UserServiceTest { ((User) user).setExpirationDate(LocalDateTime.now()) userService.saveUser(user, null) - List userList = userService.findAllByStateAndExpirationDateBefore(UserState.INACTIVE, LocalDateTime.now(), null) + List userList = userService.findAllByStateAndExpirationDateBefore(UserState.INACTIVE, LocalDateTime.now(), null, Pageable.unpaged()) assert userList.size() == 1 && userList.getFirst().stringId == user.stringId } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/FunctionsTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/FunctionsTest.groovy index 6a33aa08bb..acd971bd0a 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/FunctionsTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/FunctionsTest.groovy @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.Resource +import org.springframework.data.domain.Pageable import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.junit.jupiter.SpringExtension @@ -123,7 +124,7 @@ class FunctionsTest { @Test void testNamespaceFunctionException() { assertThrows(NullPointerException.class, () -> { - def nets = petriNetService.getByIdentifier(FUNCTION_RES_IDENTIFIER) + def nets = petriNetService.getByIdentifier(FUNCTION_RES_IDENTIFIER, Pageable.unpaged()) if (nets) { nets.each { petriNetService.deletePetriNet(it.getStringId(), userService.transformToLoggedUser(userService.getLoggedOrSystem())) @@ -141,7 +142,7 @@ class FunctionsTest { @Test void testNewNetVersionMissingMethodException() { assertThrows(NullPointerException.class, () -> { - def nets = petriNetService.getByIdentifier(FUNCTION_TEST_IDENTIFIER) + def nets = petriNetService.getByIdentifier(FUNCTION_TEST_IDENTIFIER, Pageable.unpaged()) if (nets) { nets.each { petriNetService.deletePetriNet(it.getStringId(), userService.transformToLoggedUser(userService.getLoggedOrSystem())) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy index 72b43e2d37..3df33d1905 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy @@ -18,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.Resource +import org.springframework.data.domain.Pageable import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.junit.jupiter.SpringExtension @@ -88,7 +89,7 @@ class PetriNetTest { def netOptional3 = petriNetService.importPetriNet(netResource2.inputStream, VersionType.MAJOR, superCreator.loggedSuper) assert netOptional3.getNet() != null - def nets = petriNetService.getReferencesByVersion(null, superCreator.loggedSuper, Locale.UK) + def nets = petriNetService.getReferencesByVersion(null, superCreator.loggedSuper, Locale.UK, Pageable.unpaged()) assert nets.findAll { it.identifier in [netOptional.getNet().identifier, netOptional3.getNet().identifier] }.size() == 2 assert nets.find { it.identifier == "new_model" }.version == "1.0.0" assert nets.find { it.identifier == "test" }.version == "2.0.0" From cb7a59f854feec7b331ba25ad8e39426b7de693d Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Fri, 18 Jul 2025 13:37:06 +0200 Subject: [PATCH 8/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - changes/fixes based on PR comments. --- .../engine/petrinet/service/PetriNetService.java | 2 +- .../engine/petrinet/service/ProcessRoleService.java | 2 +- .../engine/startup/runner/SuperCreatorRunner.java | 2 +- .../adapter/spring/utils/PaginationProperties.java | 2 +- .../application/engine/auth/service/UserServiceImpl.java | 9 ++------- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 6cc675bb3f..e2ba27cc7e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -385,7 +385,7 @@ public Page getReferencesByVersion(Version version, LoggedUse ); AggregationResults countResults = mongoTemplate.aggregate( countAggregation, - "your_collection_name", + "petriNet", Document.class ); long total = countResults.getUniqueMappedResult() != null diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java index b43fba519b..bc97690e3d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java @@ -235,7 +235,7 @@ public ProcessRole findById(ProcessResourceId processResourceId) { @Override public List saveAll(Iterable entities) { return StreamSupport.stream(entities.spliterator(), false).map(processRole -> { - if (!processRole.isGlobal() || processRoleRepository.findByImportId(processRole.getImportId()) == null) { + if (!processRole.isGlobal() || processRoleRepository.findByImportId(processRole.getImportId()).isEmpty()) { return processRoleRepository.save(processRole); } return null; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java index ad3ec6d87c..00e3bb9b56 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java @@ -113,7 +113,7 @@ public void setAllProcessRoles() { } public void setAllAuthorities() { - superUser.setAuthorities(Set.copyOf(authorityService.findAll(Pageable.unpaged()).stream().toList())); + superUser.setAuthorities(new HashSet<>(authorityService.findAll(Pageable.unpaged()).stream().toList())); superUser = userService.saveUser(superUser, null); } diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java index d2ccbaf0a6..5e0cf46de7 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/PaginationProperties.java @@ -10,7 +10,7 @@ */ @Data @Configuration -@ConfigurationProperties(prefix = "netgrif.pagination") +@ConfigurationProperties(prefix = "netgrif.engine.pagination") public class PaginationProperties { /** diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java index 63dfa7bcb2..029b9dd11e 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java @@ -252,13 +252,8 @@ public void addAllRolesToAdminByUsername(String username) { } IUser user = userOptional.get(); - Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); - Page processRoles; - do { - processRoles = processRoleService.findAll(pageable); - user.getProcessRoles().addAll(processRoles.getContent()); - pageable = pageable.next(); - } while (processRoles.hasNext()); + Page processRoles = processRoleService.findAll(Pageable.unpaged()); + user.getProcessRoles().addAll(processRoles.getContent()); saveUser(user, user.getRealmId()); } From c1af45df3e830c9f8148bb26dcd51e42d04572c0 Mon Sep 17 00:00:00 2001 From: Machac Date: Sun, 20 Jul 2025 20:42:17 +0200 Subject: [PATCH 9/9] [NAE-2122] Implement Structured and Efficient Pagination in gRPC - Updated tests in `PetriNetTest.groovy` by introducing the `Page` type for the `nets` variable to ensure type safety and clarity. - Imported `Page` and `PetriNetReference` and adjusted relevant usages in `PetriNetTest.groovy`. - Refined total result calculation logic in `PetriNetService` by transitioning the `total` value handling to use a `Number` type, avoiding potential null pointer exceptions. - Adopted safer numeric type conversions ensuring robust and error-free handling of MongoDB aggregation results. - Maintained backward compatibility while improving code readability and reliability. --- .../engine/petrinet/service/PetriNetService.java | 9 ++++++--- .../engine/petrinet/domain/PetriNetTest.groovy | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index e2ba27cc7e..8ded889885 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -388,9 +388,12 @@ public Page getReferencesByVersion(Version version, LoggedUse "petriNet", Document.class ); - long total = countResults.getUniqueMappedResult() != null - ? countResults.getUniqueMappedResult().getLong("total") - : 0L; + + Number totalNumber = countResults.getUniqueMappedResult() != null + ? countResults.getUniqueMappedResult().get("total", Number.class) + : 0; + long total = totalNumber != null ? totalNumber.longValue() : 0L; + references = new PageImpl<>(referenceList, pageable, total); } else { references = repository.findAllByVersion(version, pageable).map(net -> transformToReference(net, locale)); diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy index 3df33d1905..24bc1fa01f 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy @@ -10,6 +10,7 @@ import com.netgrif.application.engine.objects.petrinet.domain.arcs.ReadArc import com.netgrif.application.engine.objects.petrinet.domain.arcs.ResetArc import com.netgrif.application.engine.petrinet.domain.roles.ProcessRoleRepository import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference import com.netgrif.application.engine.startup.runner.SuperCreatorRunner import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -18,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.Resource +import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.junit.jupiter.SpringExtension @@ -89,7 +91,7 @@ class PetriNetTest { def netOptional3 = petriNetService.importPetriNet(netResource2.inputStream, VersionType.MAJOR, superCreator.loggedSuper) assert netOptional3.getNet() != null - def nets = petriNetService.getReferencesByVersion(null, superCreator.loggedSuper, Locale.UK, Pageable.unpaged()) + Page nets = petriNetService.getReferencesByVersion(null, superCreator.loggedSuper, Locale.UK, Pageable.unpaged()) assert nets.findAll { it.identifier in [netOptional.getNet().identifier, netOptional3.getNet().identifier] }.size() == 2 assert nets.find { it.identifier == "new_model" }.version == "1.0.0" assert nets.find { it.identifier == "test" }.version == "2.0.0"