From 20a9f8e705c3835f5d3297c4ac0e541591e38670 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 9 Jan 2019 17:03:54 +0100 Subject: [PATCH] SEBSERV-14 #added pagination to user activity log API --- .../ch/ethz/seb/sebserver/gbl/model/Page.java | 2 +- .../servicelayer/PaginationService.java | 76 ++++++++----- .../api/UserActivityLogController.java | 82 +++++++++++--- .../integration/api/UserAPITest.java | 102 ++++++++++++++---- 4 files changed, 197 insertions(+), 65 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/Page.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/Page.java index ccca1a97..5de2b4ab 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/Page.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/Page.java @@ -23,7 +23,7 @@ public final class Page { } public static final String ATTR_NUMBER_OF_PAGES = "numberOfPages"; - public static final String ATTR_PAGE_NUMBER = "pageNum"; + public static final String ATTR_PAGE_NUMBER = "pageNumber"; public static final String ATTR_PAGE_SIZE = "pageSize"; public static final String ATTR_SORT_BY = "sortBy"; public static final String ATTR_SORT_ORDER = "sortOrder"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java index 821a215c..6f589710 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java @@ -9,20 +9,22 @@ package ch.ethz.seb.sebserver.webservice.servicelayer; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; -import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.github.pagehelper.PageHelper; +import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Page; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport.UserRecord; @Service public class PaginationService { @@ -30,12 +32,16 @@ public class PaginationService { private final int defaultPageSize; private final int maxPageSize; + private final Map> sortColumnMapping; + public PaginationService( @Value("${sebserver.webservice.api.pagination.defaultPageSize:10}") final int defaultPageSize, @Value("${sebserver.webservice.api.pagination.maxPageSize:500}") final int maxPageSize) { this.defaultPageSize = defaultPageSize; this.maxPageSize = maxPageSize; + this.sortColumnMapping = new HashMap<>(); + initSortColumnMapping(); } public void setOnePageLimit(final SqlTable table) { @@ -70,12 +76,12 @@ public class PaginationService { final com.github.pagehelper.Page startPage = PageHelper.startPage(_pageNumber, _pageSize, true, true, false); - final SqlColumn sortColumn = verifySortColumn(sortBy, table); - if (sortColumn != null) { + final String sortColumnName = verifySortColumnName(sortBy, table); + if (StringUtils.isNoneBlank(sortColumnName)) { if (sortOrder == Page.SortOrder.DESCENDING) { - PageHelper.orderBy(sortColumn.name() + " DESC"); + PageHelper.orderBy(sortColumnName + " DESC"); } else { - PageHelper.orderBy(sortColumn.name()); + PageHelper.orderBy(sortColumnName); } } @@ -95,33 +101,51 @@ public class PaginationService { return new Page<>(page.getPages(), pageNumber, sortBy, sortOrder, pageList); } - private SqlColumn verifySortColumn(final String sortBy, final SqlTable table) { - if (table == UserRecordDynamicSqlSupport.userRecord) { - return verifySortColumn(sortBy, UserRecordDynamicSqlSupport.userRecord); + private String verifySortColumnName(final String sortBy, final SqlTable table) { + + if (StringUtils.isBlank(sortBy)) { + return null; + } + + final Map mapping = this.sortColumnMapping.get(table.name()); + if (mapping != null) { + return mapping.get(sortBy); } return null; } - private SqlColumn verifySortColumn(final String sortBy, final UserRecord table) { + // TODO is it possible to generate this within MyBatis generator? + private void initSortColumnMapping() { + // User Table + final Map userTableMap = new HashMap<>(); + userTableMap.put(Domain.USER.ATTR_NAME, UserRecordDynamicSqlSupport.name.name()); + userTableMap.put(Domain.USER.ATTR_USERNAME, UserRecordDynamicSqlSupport.username.name()); + userTableMap.put(Domain.USER.ATTR_EMAIL, UserRecordDynamicSqlSupport.email.name()); + userTableMap.put(Domain.USER.ATTR_LOCALE, UserRecordDynamicSqlSupport.locale.name()); + this.sortColumnMapping.put(UserRecordDynamicSqlSupport.userRecord.name(), userTableMap); - if (StringUtils.isBlank(sortBy)) { - return UserRecordDynamicSqlSupport.id; - } + // User Activity Log Table + final Map userActivityLogTableMap = new HashMap<>(); + userActivityLogTableMap.put( + Domain.USER_ACTIVITY_LOG.ATTR_USER_UUID, + UserActivityLogRecordDynamicSqlSupport.userUuid.name()); + userActivityLogTableMap.put( + Domain.USER_ACTIVITY_LOG.ATTR_ACTIVITY_TYPE, + UserActivityLogRecordDynamicSqlSupport.activityType.name()); + userActivityLogTableMap.put( + Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID, + UserActivityLogRecordDynamicSqlSupport.entityId.name()); + userActivityLogTableMap.put( + Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_TYPE, + UserActivityLogRecordDynamicSqlSupport.entityType.name()); + userActivityLogTableMap.put( + Domain.USER_ACTIVITY_LOG.ATTR_TIMESTAMP, + UserActivityLogRecordDynamicSqlSupport.timestamp.name()); + this.sortColumnMapping.put( + UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord.name(), + userActivityLogTableMap); - if (sortBy.equals(UserRecordDynamicSqlSupport.name.name())) { - return UserRecordDynamicSqlSupport.name; - } - - if (sortBy.equals(UserRecordDynamicSqlSupport.username.name())) { - return UserRecordDynamicSqlSupport.username; - } - - if (sortBy.equals(UserRecordDynamicSqlSupport.email.name())) { - return UserRecordDynamicSqlSupport.email; - } - - return null; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java index e260adcd..884b1597 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; -import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -24,8 +23,12 @@ import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.EntityType; +import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport; +import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; @@ -37,13 +40,16 @@ public class UserActivityLogController { private final UserActivityLogDAO userActivityLogDAO; private final AuthorizationGrantService authorizationGrantService; + private final PaginationService paginationService; public UserActivityLogController( final UserActivityLogDAO userActivityLogDAO, - final AuthorizationGrantService authorizationGrantService) { + final AuthorizationGrantService authorizationGrantService, + final PaginationService paginationService) { this.userActivityLogDAO = userActivityLogDAO; this.authorizationGrantService = authorizationGrantService; + this.paginationService = paginationService; } @RequestMapping(method = RequestMethod.GET) @@ -51,10 +57,11 @@ public class UserActivityLogController { @RequestParam(required = false) final Long from, @RequestParam(required = false) final Long to, @RequestParam(required = false) final String activityTypes, - @RequestParam(required = false) final String entityTypes, - final Principal principal) { + @RequestParam(required = false) final String entityTypes) { - return _getAll(null, from, to, activityTypes, entityTypes, principal); + checkBaseReadPrivilege(); + this.paginationService.setOnePageLimit(UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord); + return _getAll(null, from, to, activityTypes, entityTypes); } @RequestMapping(path = "/{userId}", method = RequestMethod.GET) @@ -63,10 +70,60 @@ public class UserActivityLogController { @RequestParam(required = false) final Long from, @RequestParam(required = false) final Long to, @RequestParam(required = false) final String activityTypes, - @RequestParam(required = false) final String entityTypes, - final Principal principal) { + @RequestParam(required = false) final String entityTypes) { - return _getAll(userId, from, to, activityTypes, entityTypes, principal); + checkBaseReadPrivilege(); + this.paginationService.setOnePageLimit(UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord); + return _getAll(userId, from, to, activityTypes, entityTypes); + } + + @RequestMapping(path = "/page", method = RequestMethod.GET) + public Page getPage( + @RequestParam(required = false) final Long from, + @RequestParam(required = false) final Long to, + @RequestParam(required = false) final String activityTypes, + @RequestParam(required = false) final String entityTypes, + @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, + @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, + @RequestParam(name = Page.ATTR_SORT_BY, required = false) final String sortBy, + @RequestParam(name = Page.ATTR_SORT_ORDER, required = false) final Page.SortOrder sortOrder) { + + checkBaseReadPrivilege(); + return this.paginationService.getPage( + pageNumber, + pageSize, + sortBy, + sortOrder, + UserRecordDynamicSqlSupport.userRecord, + () -> _getAll(null, from, to, activityTypes, entityTypes)); + } + + @RequestMapping(path = "/page/{userId}", method = RequestMethod.GET) + public Page getPageForUser( + @PathVariable final String userId, + @RequestParam(required = false) final Long from, + @RequestParam(required = false) final Long to, + @RequestParam(required = false) final String activityTypes, + @RequestParam(required = false) final String entityTypes, + @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, + @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, + @RequestParam(name = Page.ATTR_SORT_BY, required = false) final String sortBy, + @RequestParam(name = Page.ATTR_SORT_ORDER, required = false) final Page.SortOrder sortOrder) { + + checkBaseReadPrivilege(); + return this.paginationService.getPage( + pageNumber, + pageSize, + sortBy, + sortOrder, + UserRecordDynamicSqlSupport.userRecord, + () -> _getAll(userId, from, to, activityTypes, entityTypes)); + } + + private void checkBaseReadPrivilege() { + this.authorizationGrantService.checkHasAnyPrivilege( + EntityType.USER_ACTIVITY_LOG, + PrivilegeType.READ_ONLY); } private Collection _getAll( @@ -74,13 +131,7 @@ public class UserActivityLogController { final Long from, final Long to, final String activityTypes, - final String entityTypes, - final Principal principal) { - - // fist check if current user has any privileges for this action - this.authorizationGrantService.checkHasAnyPrivilege( - EntityType.USER_ACTIVITY_LOG, - PrivilegeType.READ_ONLY); + final String entityTypes) { final Set _activityTypes = (activityTypes != null) ? Collections.unmodifiableSet(new HashSet<>( @@ -108,7 +159,6 @@ public class UserActivityLogController { return this.userActivityLogDAO.all(userId, from, to, record -> true) .getOrThrow(); - } } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/UserAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/UserAPITest.java index 09d0dcc2..5dd687fe 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/UserAPITest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/UserAPITest.java @@ -163,6 +163,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { }); assertNotNull(userInfos); + assertTrue(userInfos.numberOfPages == 1); assertNotNull(userInfos.content); assertTrue(userInfos.content.size() == 7); assertEquals("[user1, user2, user3, user4, user5, user6, user7]", getOrderedUUIDs(userInfos.content)); @@ -180,6 +181,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { }); assertNotNull(userInfos); + assertTrue(userInfos.numberOfPages == 1); assertNotNull(userInfos.content); assertTrue(userInfos.content.size() == 7); assertEquals("[user7, user6, user5, user4, user3, user2, user1]", getOrderedUUIDs(userInfos.content)); @@ -200,10 +202,45 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { }); assertNotNull(userInfos); + assertTrue(userInfos.numberOfPages == 3); assertNotNull(userInfos.content); assertTrue(userInfos.content.size() == 3); assertEquals("[user1, user2, user3]", getOrderedUUIDs(userInfos.content)); + // second page default sort order + userInfos = this.jsonMapper.readValue( + this.mockMvc + .perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + + "/page?pageNumber=2&pageSize=3") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + new TypeReference>() { + }); + + assertNotNull(userInfos); + assertTrue(userInfos.numberOfPages == 3); + assertNotNull(userInfos.content); + assertTrue(userInfos.content.size() == 3); + assertEquals("[user4, user5, user6]", getOrderedUUIDs(userInfos.content)); + + // third page default sort order + userInfos = this.jsonMapper.readValue( + this.mockMvc + .perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + + "/page?pageNumber=3&pageSize=3") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + new TypeReference>() { + }); + + assertNotNull(userInfos); + assertTrue(userInfos.numberOfPages == 3); + assertNotNull(userInfos.content); + assertTrue(userInfos.content.size() == 1); + assertEquals("[user7]", getOrderedUUIDs(userInfos.content)); + // first page descending sort order userInfos = this.jsonMapper.readValue( this.mockMvc @@ -216,6 +253,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { }); assertNotNull(userInfos); + assertTrue(userInfos.numberOfPages == 3); assertNotNull(userInfos.content); assertTrue(userInfos.content.size() == 3); assertEquals("[user7, user6, user5]", getOrderedUUIDs(userInfos.content)); @@ -230,7 +268,39 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { } @Test - public void getAllUserInfoWithSearchInactive() throws Exception { + public void getAllUserInfo() throws Exception { + final String token = getSebAdminAccess(); + final List userInfos = this.jsonMapper.readValue( + this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT) + .header("Authorization", "Bearer " + token)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + new TypeReference>() { + }); + + assertNotNull(userInfos); + assertTrue(userInfos.size() == 7); + assertNotNull(getUserInfo("deactivatedUser", userInfos)); + } + + @Test + public void getAllUserInfoWithOnlyActice() throws Exception { + final String token = getSebAdminAccess(); + final List userInfos = this.jsonMapper.readValue( + this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=true") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + new TypeReference>() { + }); + + assertNotNull(userInfos); + assertTrue(userInfos.size() == 6); + assertNull(getUserInfo("deactivatedUser", userInfos)); + } + + @Test + public void getAllUserInfoOnlyInactive() throws Exception { final String token = getSebAdminAccess(); final List userInfos = this.jsonMapper.readValue( this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=false") @@ -245,22 +315,6 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { assertNotNull(getUserInfo("deactivatedUser", userInfos)); } -// @Test -// public void getAllUserInfoWithFilterNameAndNoActiveFlagSet() throws Exception { -// final String token = getSebAdminAccess(); -// final List userInfos = this.jsonMapper.readValue( -// this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=false") -// .header("Authorization", "Bearer " + token)) -// .andExpect(status().isOk()) -// .andReturn().getResponse().getContentAsString(), -// new TypeReference>() { -// }); -// -// assertNotNull(userInfos); -// assertTrue(userInfos.size() == 1); -// assertNotNull(getUserInfo("deactivatedUser", userInfos)); -// } - @Test public void getAllUserInfoWithSearchUsernameLike() throws Exception { final String token = getSebAdminAccess(); @@ -688,11 +742,15 @@ public class UserAPITest extends AdministrationAPIIntegrationTest { } private UserInfo getUserInfo(final String name, final Collection infos) { - return infos - .stream() - .filter(ui -> ui.username.equals(name)) - .findFirst() - .orElseThrow(NoSuchElementException::new); + try { + return infos + .stream() + .filter(ui -> ui.username.equals(name)) + .findFirst() + .orElseThrow(NoSuchElementException::new); + } catch (final Exception e) { + return null; + } } }