diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/user/api/RefreshUserPostprocessor.java b/lib/taskana-core/src/main/java/pro/taskana/spi/user/api/RefreshUserPostprocessor.java new file mode 100644 index 000000000..229dbbb3a --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/spi/user/api/RefreshUserPostprocessor.java @@ -0,0 +1,18 @@ +package pro.taskana.spi.user.api; + +import pro.taskana.user.api.models.User; + +/** + * The RefreshUserPostprocessor allows to implement custom behaviour after a {@linkplain User} has + * been updated. + */ +public interface RefreshUserPostprocessor { + + /** + * Processes a {@linkplain User} after its refresh. + * + * @param userToProcess {@linkplain User} the User to preprocess + * @return the {@linkplain User} after it has been processed + */ + User processUserAfterRefresh(User userToProcess); +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/user/internal/RefreshUserPostprocessorManager.java b/lib/taskana-core/src/main/java/pro/taskana/spi/user/internal/RefreshUserPostprocessorManager.java new file mode 100644 index 000000000..9be458333 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/spi/user/internal/RefreshUserPostprocessorManager.java @@ -0,0 +1,43 @@ +package pro.taskana.spi.user.internal; + +import static pro.taskana.common.internal.util.CheckedConsumer.wrap; + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import pro.taskana.common.internal.util.SpiLoader; +import pro.taskana.spi.user.api.RefreshUserPostprocessor; +import pro.taskana.user.api.models.User; + +public class RefreshUserPostprocessorManager { + + private static final Logger LOGGER = + LoggerFactory.getLogger(RefreshUserPostprocessorManager.class); + private final List refreshUserPostprocessors; + + public RefreshUserPostprocessorManager() { + refreshUserPostprocessors = SpiLoader.load(RefreshUserPostprocessor.class); + for (RefreshUserPostprocessor postprocessor : refreshUserPostprocessors) { + LOGGER.info( + "Registered RefreshUserPostprocessor provider: {}", postprocessor.getClass().getName()); + } + if (refreshUserPostprocessors.isEmpty()) { + LOGGER.info("No RefreshUserPostprocessor found. Running without RefreshUserPostprocessor."); + } + } + + public User processUserAfterRefresh(User userToProcess) { + LOGGER.debug("Sending user to RefreshUserPostprocessor providers: {}", userToProcess); + + refreshUserPostprocessors.forEach( + wrap( + refreshUserPostprocessor -> + refreshUserPostprocessor.processUserAfterRefresh(userToProcess))); + return userToProcess; + } + + public boolean isEnabled() { + return !refreshUserPostprocessors.isEmpty(); + } +} diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java index f07b46801..4a5c56981 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java @@ -2,6 +2,7 @@ package pro.taskana.user.jobs; import java.sql.PreparedStatement; import java.util.List; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,6 +16,7 @@ import pro.taskana.common.internal.jobs.AbstractTaskanaJob; import pro.taskana.common.internal.transaction.TaskanaTransactionProvider; import pro.taskana.common.rest.ldap.LdapClient; import pro.taskana.common.rest.util.ApplicationContextProvider; +import pro.taskana.spi.user.internal.RefreshUserPostprocessorManager; import pro.taskana.task.internal.jobs.helper.SqlConnectionRunner; import pro.taskana.user.api.exceptions.UserAlreadyExistException; import pro.taskana.user.api.exceptions.UserNotFoundException; @@ -25,6 +27,7 @@ public class UserInfoRefreshJob extends AbstractTaskanaJob { private static final Logger LOGGER = LoggerFactory.getLogger(UserInfoRefreshJob.class); private final SqlConnectionRunner sqlConnectionRunner; + private RefreshUserPostprocessorManager refreshUserPostprocessorManager; public UserInfoRefreshJob(TaskanaEngine taskanaEngine) { this(taskanaEngine, null, null); @@ -38,6 +41,7 @@ public class UserInfoRefreshJob extends AbstractTaskanaJob { runEvery = taskanaEngine.getConfiguration().getUserRefreshJobRunEvery(); firstRun = taskanaEngine.getConfiguration().getUserRefreshJobFirstRun(); sqlConnectionRunner = new SqlConnectionRunner(taskanaEngine); + refreshUserPostprocessorManager = new RefreshUserPostprocessorManager(); } /** @@ -68,9 +72,13 @@ public class UserInfoRefreshJob extends AbstractTaskanaJob { try { List users = ldapClient.searchUsersInUserRole(); - addExistingConfigurationDataToUsers(users); + List usersAfterProcessing = + users.stream() + .map(user -> refreshUserPostprocessorManager.processUserAfterRefresh(user)) + .collect(Collectors.toList()); + addExistingConfigurationDataToUsers(usersAfterProcessing); clearExistingUsers(); - insertNewUsers(users); + insertNewUsers(usersAfterProcessing); LOGGER.info("Job to refresh all user info has finished."); diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java index 43327f890..572d0190e 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java @@ -7,12 +7,16 @@ import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.rest.ldap.LdapClient; +import pro.taskana.common.test.rest.RestHelper; import pro.taskana.common.test.rest.TaskanaSpringBootTest; import pro.taskana.common.test.security.JaasExtension; import pro.taskana.common.test.security.WithAccessId; @@ -22,22 +26,29 @@ import pro.taskana.user.internal.models.UserImpl; @TaskanaSpringBootTest @ExtendWith(JaasExtension.class) +@TestMethodOrder(OrderAnnotation.class) class UserInfoRefreshJobIntTest { TaskanaEngine taskanaEngine; UserService userService; LdapClient ldapClient; + private final RestHelper restHelper; @Autowired public UserInfoRefreshJobIntTest( - TaskanaEngine taskanaEngine, UserService userService, LdapClient ldapClient) { + TaskanaEngine taskanaEngine, + UserService userService, + LdapClient ldapClient, + RestHelper restHelper) { this.taskanaEngine = taskanaEngine; this.userService = userService; this.ldapClient = ldapClient; + this.restHelper = restHelper; } @Test @WithAccessId(user = "businessadmin") + @Order(1) void should_RefreshUserInfo_When_UserInfoRefreshJobIsExecuted() throws Exception { try (Connection connection = taskanaEngine.getConfiguration().getDatasource().getConnection()) { @@ -50,13 +61,38 @@ class UserInfoRefreshJobIntTest { users = getUsers(connection); List ldapusers = ldapClient.searchUsersInUserRole(); - assertThat(users).hasSize(6).hasSameSizeAs(ldapusers); assertThat(users) - .usingRecursiveFieldByFieldElementComparatorIgnoringFields("longName", "data") + .hasSize(6) + .hasSameSizeAs(ldapusers) + .usingRecursiveFieldByFieldElementComparatorIgnoringFields( + "longName", "data", "orgLevel1") .containsExactlyElementsOf(ldapusers); } } + @Test + @WithAccessId(user = "businessadmin") + @Order(2) + void should_PostprocessUser_When_RefrehUserPostprocessorIsActive() throws Exception { + + try (Connection connection = taskanaEngine.getConfiguration().getDatasource().getConnection()) { + + UserInfoRefreshJob userInfoRefreshJob = new UserInfoRefreshJob(taskanaEngine); + userInfoRefreshJob.execute(); + + Statement statement = connection.createStatement(); + ResultSet rs = + statement.executeQuery( + "SELECT * FROM " + + connection.getSchema() + + ".USER_INFO " + + "WHERE USER_ID='user-2-2'"); + rs.next(); + String updatedOrgLevel = rs.getString("ORG_LEVEL_1"); + assertThat(updatedOrgLevel).isEqualTo("FirstSecond"); + } + } + private List getUsers(Connection connection) throws Exception { List users = new ArrayList<>(); diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/FirstRefreshUserPostprocessor.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/FirstRefreshUserPostprocessor.java new file mode 100644 index 000000000..e623d41bb --- /dev/null +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/FirstRefreshUserPostprocessor.java @@ -0,0 +1,14 @@ +package pro.taskana.user.rest; + +import pro.taskana.spi.user.api.RefreshUserPostprocessor; +import pro.taskana.user.api.models.User; + +public class FirstRefreshUserPostprocessor implements RefreshUserPostprocessor { + @Override + public User processUserAfterRefresh(User userToProcess) { + if (userToProcess.getId().equals("user-2-2")) { + userToProcess.setOrgLevel1("First"); + } + return userToProcess; + } +} diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/SecondRefreshUserPostprocessor.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/SecondRefreshUserPostprocessor.java new file mode 100644 index 000000000..ffc48a87f --- /dev/null +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/SecondRefreshUserPostprocessor.java @@ -0,0 +1,14 @@ +package pro.taskana.user.rest; + +import pro.taskana.spi.user.api.RefreshUserPostprocessor; +import pro.taskana.user.api.models.User; + +public class SecondRefreshUserPostprocessor implements RefreshUserPostprocessor { + @Override + public User processUserAfterRefresh(User userToProcess) { + if (userToProcess.getId().equals("user-2-2")) { + userToProcess.setOrgLevel1(userToProcess.getOrgLevel1() + "Second"); + } + return userToProcess; + } +} diff --git a/rest/taskana-rest-spring/src/test/resources/META-INF/services/pro.taskana.spi.user.api.RefreshUserPostprocessor b/rest/taskana-rest-spring/src/test/resources/META-INF/services/pro.taskana.spi.user.api.RefreshUserPostprocessor new file mode 100644 index 000000000..e68981a3a --- /dev/null +++ b/rest/taskana-rest-spring/src/test/resources/META-INF/services/pro.taskana.spi.user.api.RefreshUserPostprocessor @@ -0,0 +1,2 @@ +pro.taskana.user.rest.FirstRefreshUserPostprocessor +pro.taskana.user.rest.SecondRefreshUserPostprocessor \ No newline at end of file