From 5373263f6ceef17d0130efa8093b1a70e3defe54 Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Thu, 12 Dec 2013 18:28:36 +0530 Subject: [PATCH] Add pagination support to audit API --- api-docs/apiLive.htm | 92 +++++++++++++- .../commands/api/AuditsApiResource.java | 13 +- .../service/AuditReadPlatformService.java | 4 + .../service/AuditReadPlatformServiceImpl.java | 56 ++++++++- .../core/data/PaginationParameters.java | 115 ++++++++++++++++++ .../PaginationParametersDataValidator.java | 44 +++++++ 6 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParameters.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParametersDataValidator.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index fba6976dc..ab6eed3ed 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -10657,8 +10657,8 @@ Content-Type: application/json

List Audits

-

Get a list of audits that match the criteria supplied, - and are within the requestors' data scope.

+

Get a 200 list of audits that match the criteria supplied and sorted by audit id in descending order, + and are within the requestors' data scope. Also it supports pagination and sorting

Arguments
actionName
@@ -10749,6 +10749,35 @@ Content-Type: application/json
optional, Values are true, false. Default is false.
+
paged
+
+ Boolean optional, defaults to false +
+
If paged is true then results will be paginated.
+ +
offset
+
+ Integer optional, defaults to 0 +
+
Indicates from what result to start from.
+ +
limit
+
+ Integer optional, defaults to 200 +
+
Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1
+ +
orderBy
+
+ String optional, one of id, actionName, entityName, resourceId, subresourceId, madeOnDate, checkedOnDate, officeName, groupName, clientName, loanAccountNo, savingsAccountNo +
+
Orders the results by the field indicated.
+ +
sortBy
+
+ String optional, one of ASC, DESC +
+
Indicates what way to order results if orderBy is used.

Example Requests:

@@ -10842,6 +10871,65 @@ GET https://DomainName/api/v1/audits },... ] + +GET https://DomainName/api/v1/audits?paged=true&limit=5 + + +{ + "totalFilteredRecords": 30, + "pageItems": [ + { + "id": 671, + "actionName": "PERMISSIONS", + "entityName": "ROLE", + "resourceId": 6, + "maker": "keithwoodlock", + "madeOnDate": 1365014262000, + "processingResult": "processed" + }, + { + "id": 670, + "actionName": "CREATE", + "entityName": "CLIENTNOTE", + "resourceId": 287, + "maker": "keithwoodlock", + "madeOnDate": 1365014204000, + "processingResult": "awaiting.approval", + "officeName": "my office", + "clientName": "gg ggg" + }, + { + "id": 668, + "actionName": "UPDATE", + "entityName": "PERMISSION", + "maker": "keithwoodlock", + "madeOnDate": 1365014186000, + "processingResult": "processed" + }, + { + "id": 667, + "actionName": "CREATE", + "entityName": "CLIENTNOTE", + "resourceId": 286, + "maker": "keithwoodlock", + "madeOnDate": 1365014169000, + "processingResult": "processed", + "officeName": "my office name", + "clientName": "gg ggg" + }, + { + "id": 666, + "actionName": "CREATE", + "entityName": "CLIENT", + "resourceId": 363, + "maker": "keithwoodlock", + "madeOnDate": 1365012843000, + "processingResult": "awaiting.approval", + "officeName": "my office name" + } + ] +} +
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/api/AuditsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/api/AuditsApiResource.java index 4c1d7c60b..24ea892da 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/api/AuditsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/api/AuditsApiResource.java @@ -26,8 +26,10 @@ import org.mifosplatform.commands.data.AuditSearchData; import org.mifosplatform.commands.service.AuditReadPlatformService; import org.mifosplatform.infrastructure.core.api.ApiParameterHelper; import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.data.PaginationParameters; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; @@ -72,15 +74,22 @@ public class AuditsApiResource { @QueryParam("checkerDateTimeTo") final String checkerDateTimeTo, @QueryParam("processingResult") final Integer processingResult, @QueryParam("officeId") final Integer officeId, @QueryParam("groupId") final Integer groupId, @QueryParam("clientId") final Integer clientId, - @QueryParam("loanid") final Integer loanId, @QueryParam("savingsAccountId") final Integer savingsAccountId) { + @QueryParam("loanid") final Integer loanId, @QueryParam("savingsAccountId") final Integer savingsAccountId, + @QueryParam("paged") final Boolean paged, @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit, + @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); - + final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); final String extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo, checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + if (parameters.isPaged()){ + final Page auditEntries = this.auditReadPlatformService.retrievePaginatedAuditEntries(extraCriteria, settings.isIncludeJson(), parameters); + return this.toApiJsonSerializer.serialize(settings, auditEntries, this.RESPONSE_DATA_PARAMETERS); + } + final Collection auditEntries = this.auditReadPlatformService.retrieveAuditEntries(extraCriteria, settings.isIncludeJson()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformService.java index a4d30485a..83d2f9613 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformService.java @@ -9,10 +9,14 @@ import java.util.Collection; import org.mifosplatform.commands.data.AuditData; import org.mifosplatform.commands.data.AuditSearchData; +import org.mifosplatform.infrastructure.core.data.PaginationParameters; +import org.mifosplatform.infrastructure.core.service.Page; public interface AuditReadPlatformService { Collection retrieveAuditEntries(String extraCriteria, boolean includeJson); + + Page retrievePaginatedAuditEntries(String extraCriteria, boolean includeJson, PaginationParameters parameters); Collection retrieveAllEntriesToBeChecked(String extraCriteria, boolean includeJson); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformServiceImpl.java index bd05296de..00f85c2b8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/AuditReadPlatformServiceImpl.java @@ -8,18 +8,25 @@ package org.mifosplatform.commands.service; import java.lang.reflect.Type; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.mifosplatform.commands.data.AuditData; import org.mifosplatform.commands.data.AuditSearchData; import org.mifosplatform.commands.data.ProcessingResultLookup; +import org.mifosplatform.infrastructure.core.data.PaginationParameters; +import org.mifosplatform.infrastructure.core.data.PaginationParametersDataValidator; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.core.service.Page; +import org.mifosplatform.infrastructure.core.service.PaginationHelper; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.office.data.OfficeData; @@ -49,6 +56,9 @@ import com.google.gson.JsonObject; public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { private final static Logger logger = LoggerFactory.getLogger(AuditReadPlatformServiceImpl.class); + private final static Set supportedOrderByValues = new HashSet(Arrays.asList("id", "actionName", "entityName", + "resourceId", "subresourceId", "madeOnDate", "checkedOnDate", "officeName", "groupName", "clientName", "loanAccountNo", + "savingsAccountNo")); private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; @@ -58,12 +68,15 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { private final ClientReadPlatformService clientReadPlatformService; private final LoanProductReadPlatformService loanProductReadPlatformService; private final StaffReadPlatformService staffReadPlatformService; + private final PaginationHelper paginationHelper = new PaginationHelper(); + private final PaginationParametersDataValidator paginationParametersDataValidator; @Autowired public AuditReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, final FromJsonHelper fromApiJsonHelper, final AppUserReadPlatformService appUserReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, final ClientReadPlatformService clientReadPlatformService, - final LoanProductReadPlatformService loanProductReadPlatformService, final StaffReadPlatformService staffReadPlatformService) { + final LoanProductReadPlatformService loanProductReadPlatformService, final StaffReadPlatformService staffReadPlatformService, + final PaginationParametersDataValidator paginationParametersDataValidator) { this.context = context; this.jdbcTemplate = new JdbcTemplate(dataSource); this.fromApiJsonHelper = fromApiJsonHelper; @@ -72,6 +85,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { this.clientReadPlatformService = clientReadPlatformService; this.loanProductReadPlatformService = loanProductReadPlatformService; this.staffReadPlatformService = staffReadPlatformService; + this.paginationParametersDataValidator = paginationParametersDataValidator; } private static final class AuditMapper implements RowMapper { @@ -145,10 +159,44 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { if (StringUtils.isNotBlank(extraCriteria)) { updatedExtraCriteria = " where (" + extraCriteria + ")"; } - - updatedExtraCriteria += " order by aud.id DESC"; + + updatedExtraCriteria += " order by aud.id DESC limit " + PaginationParameters.getCheckedLimit(null); return retrieveEntries("audit", updatedExtraCriteria, includeJson); } + + @Override + public Page retrievePaginatedAuditEntries(final String extraCriteria, final boolean includeJson, final PaginationParameters parameters) { + + this.paginationParametersDataValidator.validateParameterValues(parameters, supportedOrderByValues, "audits"); + final AppUser currentUser = this.context.authenticatedUser(); + final String hierarchy = currentUser.getOffice().getHierarchy(); + + String updatedExtraCriteria = ""; + if (StringUtils.isNotBlank(extraCriteria)) { + updatedExtraCriteria = " where (" + extraCriteria + ")"; + } + + final AuditMapper rm = new AuditMapper(); + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); + sqlBuilder.append(rm.schema(includeJson, hierarchy)); + sqlBuilder.append(' ').append(updatedExtraCriteria); + + if (parameters.isOrderByRequested()) { + sqlBuilder.append(' ').append(parameters.orderBySql()); + }else { + sqlBuilder.append(' ').append(' ').append(" order by aud.id DESC"); + } + + if (parameters.isLimited()) { + sqlBuilder.append(' ').append(parameters.limitSql()); + } + + logger.info("sql: " + sqlBuilder.toString()); + + final String sqlCountRows = "SELECT FOUND_ROWS()"; + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), new Object[] {}, rm); + } @Override public Collection retrieveAllEntriesToBeChecked(final String extraCriteria, final boolean includeJson) { @@ -195,7 +243,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { return this.jdbcTemplate.query(sql, rm, new Object[] {}); } - + @Override public AuditData retrieveAuditEntry(final Long auditId) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParameters.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParameters.java new file mode 100644 index 000000000..efb1a192b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParameters.java @@ -0,0 +1,115 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.core.data; + +import org.apache.commons.lang.StringUtils; + +/** + *

+ * Immutable data object representing pagination parameter values. + *

+ */ +public class PaginationParameters { + + private final boolean paged; + private final Integer offset; + private final Integer limit; + private final String orderBy; + private final String sortOrder; + + public static PaginationParameters instance (Boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder){ + if( null == paged) { + paged = false; + } + + final Integer maxLimitAllowed = getCheckedLimit(limit); + + return new PaginationParameters(paged, offset, maxLimitAllowed, orderBy, sortOrder); + } + + private PaginationParameters(boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) { + this.paged = paged; + this.offset = offset; + this.limit = limit; + this.orderBy = orderBy; + this.sortOrder = sortOrder; + } + + public static Integer getCheckedLimit(final Integer limit) { + + final Integer maxLimitAllowed = 200; + // default to max limit first off + Integer checkedLimit = maxLimitAllowed; + + if (limit != null && limit > 0) { + checkedLimit = limit; + } else if (limit != null) { + // unlimited case: limit provided and 0 or less + checkedLimit = null; + } + + return checkedLimit; + } + + public boolean isPaged() { + return this.paged; + } + + public Integer getOffset() { + return this.offset; + } + + public Integer getLimit() { + return this.limit; + } + + public String getOrderBy() { + return this.orderBy; + } + + public String getSortOrder() { + return this.sortOrder; + } + + public boolean isOrderByRequested() { + return StringUtils.isNotBlank(this.orderBy); + } + + public boolean isSortOrderProvided() { + return StringUtils.isNotBlank(this.sortOrder); + } + + public boolean isLimited() { + return this.limit != null && this.limit.intValue() > 0; + } + + public boolean isOffset() { + return this.offset != null; + } + + public String orderBySql(){ + final StringBuffer sql = new StringBuffer(); + + if (this.isOrderByRequested()) { + sql.append(" order by ").append(this.getOrderBy()); + if (this.isSortOrderProvided()) { + sql.append(' ').append(this.getSortOrder()); + } + } + return sql.toString(); + } + + public String limitSql(){ + final StringBuffer sql = new StringBuffer(); + if (this.isLimited()) { + sql.append(" limit ").append(this.getLimit()); + if (this.isOffset()) { + sql.append(" offset ").append(this.getOffset()); + } + } + return sql.toString(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParametersDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParametersDataValidator.java new file mode 100644 index 000000000..c6f086acf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/PaginationParametersDataValidator.java @@ -0,0 +1,44 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.core.data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.springframework.stereotype.Component; + +@Component +public class PaginationParametersDataValidator { + public static Set sortOrderValues = new HashSet(Arrays.asList("ASC", "DESC")); + public void validateParameterValues(PaginationParameters parameters, final Set supportedOrdeByValues, final String resourceName) { + + final List dataValidationErrors = new ArrayList(); + + if (parameters.isOrderByRequested() && !supportedOrdeByValues.contains(parameters.getOrderBy())){ + final String defaultUserMessage = "The orderBy value '" + parameters.getOrderBy() + "' is not supported. The supported orderBy values are " + supportedOrdeByValues.toString(); + final ApiParameterError error = ApiParameterError.parameterError("validation.msg."+resourceName+".orderBy.value.is.not.supported", + defaultUserMessage, "orderBy", parameters.getOrderBy(), supportedOrdeByValues.toString()); + dataValidationErrors.add(error); + } + + if (parameters.isSortOrderProvided() && !sortOrderValues.contains(parameters.getSortOrder().toUpperCase())){ + final String defaultUserMessage = "The sortOrder value '" + parameters.getSortOrder() + "' is not supported. The supported sortOrder values are " + sortOrderValues.toString(); + final ApiParameterError error = ApiParameterError.parameterError("validation.msg."+resourceName+".sortOrder.value.is.not.supported", + defaultUserMessage, "sortOrder", parameters.getSortOrder(), sortOrderValues.toString()); + dataValidationErrors.add(error); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} \ No newline at end of file