This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 31be59ffe7182819601699339bd0ce73c12e52b0 Author: Yannick Martel <martel@©odelutin.com> Date: Wed Feb 3 18:19:52 2016 +0100 add pagination on documents --- .../persistence/entity/DocumentTopiaDao.java | 83 +++++++++++++--------- .../coselmar/services/v1/DocumentsWebService.java | 29 +++++--- coselmar-rest/src/main/resources/mapping | 1 + .../src/main/webapp/js/coselmar-controllers.js | 52 ++++++++++---- .../src/main/webapp/js/coselmar-services.js | 7 ++ coselmar-ui/src/main/webapp/js/paginationBinder.js | 3 +- .../src/main/webapp/views/documents/documents.html | 3 +- .../src/main/webapp/views/documents/toolsPart.html | 2 +- 8 files changed, 119 insertions(+), 61 deletions(-) diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/DocumentTopiaDao.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/DocumentTopiaDao.java index 2e734cf..fcd7b0e 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/DocumentTopiaDao.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/DocumentTopiaDao.java @@ -33,10 +33,11 @@ import fr.ifremer.coselmar.persistence.DaoUtils; import fr.ifremer.coselmar.persistence.SearchRequestBean; import org.apache.commons.lang3.StringUtils; import org.nuiton.util.pagination.PaginationParameter; +import org.nuiton.util.pagination.PaginationResult; public class DocumentTopiaDao extends AbstractDocumentTopiaDao<Document> { - public List<Document> findAllContainingAllKeywords(List<String> keywords) { + public PaginationResult<Document> findPaginatedContainingAllKeywords(List<String> keywords, PaginationParameter page) { StringBuilder hqlBuilder = new StringBuilder("FROM " + Document.class.getName() + " D"); Map<String, Object> args = new HashMap<>(); @@ -57,8 +58,12 @@ public class DocumentTopiaDao extends AbstractDocumentTopiaDao<Document> { hqlBuilder.append(" )"); } - List<Document> documents = forHql(hqlBuilder.toString(), args).findAll(); - return documents; + String hql = hqlBuilder.toString(); + Long count = forHql(hql).count(); + List<Document> documents = forHql(hql, args).find(page); + + PaginationResult paginationResult = PaginationResult.of(documents, count, page); + return paginationResult; } public List<Document> findAllFilterByUser(CoselmarUser currentUser, List<String> keywords) { @@ -104,12 +109,12 @@ public class DocumentTopiaDao extends AbstractDocumentTopiaDao<Document> { return documents; } - public List<Document> findAllByExample(CoselmarUser userFilter, DocumentSearchExample searchExample) { + public PaginationResult<Document> findAllByExample(CoselmarUser userFilter, DocumentSearchExample searchExample) { - StringBuilder hqlBuilder = new StringBuilder("SELECT DISTINCT(D) FROM " + Document.class.getName() + " D" + StringBuilder hqlWithoutSelectBuilder = new StringBuilder(" FROM " + Document.class.getName() + " D" + " LEFT OUTER JOIN D." + Document.PROPERTY_RESTRICTED_LIST + " CUG " + " LEFT OUTER JOIN D." + Document.PROPERTY_OWNER + " DO "); - hqlBuilder.append(" WHERE 1=1 "); // Just because next clause will begin with operator + hqlWithoutSelectBuilder.append(" WHERE 1=1 "); // Just because next clause will begin with operator Map<String, Object> args = new HashMap<>(); @@ -117,58 +122,58 @@ public class DocumentTopiaDao extends AbstractDocumentTopiaDao<Document> { if (userFilter != null) { // can list all public document String privacyPublicCondition = DaoUtils.getQueryForAttributeEquals("D", Document.PROPERTY_PRIVACY, args, Privacy.PUBLIC, ""); - hqlBuilder.append(" AND ( " + privacyPublicCondition); + hqlWithoutSelectBuilder.append(" AND ( " + privacyPublicCondition); // Can list his own document String ownerCondition = DaoUtils.orAttributeEquals("D", Document.PROPERTY_OWNER, args, userFilter); - hqlBuilder.append(ownerCondition); + hqlWithoutSelectBuilder.append(ownerCondition); // For limited access, check if user is in a restricted list String participantCondition = DaoUtils.orAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, userFilter); - hqlBuilder.append(participantCondition + ")"); + hqlWithoutSelectBuilder.append(participantCondition + ")"); } // Manage example Document example = searchExample.getExample(); if (example != null) { - hqlBuilder.append(" AND ( 1 = 1 "); + hqlWithoutSelectBuilder.append(" AND ( 1 = 1 "); if (StringUtils.isNotBlank(example.getName())) { String nameCondition = DaoUtils.andAttributeLike("D", Document.PROPERTY_NAME, args, example.getName()); - hqlBuilder.append(nameCondition); + hqlWithoutSelectBuilder.append(nameCondition); } if (StringUtils.isNotBlank(example.getAuthors())) { String authorsCondition = DaoUtils.andAttributeLike("D", Document.PROPERTY_AUTHORS, args, example.getAuthors()); - hqlBuilder.append(authorsCondition); + hqlWithoutSelectBuilder.append(authorsCondition); } if (StringUtils.isNotBlank(example.getLicense())) { String licenseCondition = DaoUtils.andAttributeLike("D", Document.PROPERTY_LICENSE, args, example.getLicense()); - hqlBuilder.append(licenseCondition); + hqlWithoutSelectBuilder.append(licenseCondition); } if (StringUtils.isNotBlank(example.getType())) { String typeCondition = DaoUtils.andAttributeLike("D", Document.PROPERTY_TYPE, args, example.getType()); - hqlBuilder.append(typeCondition); + hqlWithoutSelectBuilder.append(typeCondition); } if (example.getPrivacy() != null ) { String privacyCondition = DaoUtils.andAttributeEquals("D", Document.PROPERTY_PRIVACY, args, example.getPrivacy()); - hqlBuilder.append(privacyCondition); + hqlWithoutSelectBuilder.append(privacyCondition); } if (example.getKeywords() != null && !example.getKeywords().isEmpty()) { for (String keyword : example.getKeywords()) { String keywordCondition = DaoUtils.andAttributeContains("D", Document.PROPERTY_KEYWORDS, args, keyword); - hqlBuilder.append(keywordCondition); + hqlWithoutSelectBuilder.append(keywordCondition); } } if (example.getOwner() != null) { String ownerCondition = DaoUtils.andAttributeEquals("D", Document.PROPERTY_OWNER, args, example.getOwner()); - hqlBuilder.append(ownerCondition); + hqlWithoutSelectBuilder.append(ownerCondition); } @@ -176,60 +181,60 @@ public class DocumentTopiaDao extends AbstractDocumentTopiaDao<Document> { if (StringUtils.isNotBlank(searchExample.getOwnerName())) { String activeCondition = DaoUtils.andAttributeEquals("DO", CoselmarUser.PROPERTY_ACTIVE, args, true); - hqlBuilder.append(activeCondition); + hqlWithoutSelectBuilder.append(activeCondition); // try to find name in user#firstName or user#name! - hqlBuilder.append(" AND ( 1=0 "); // Same as previously : need to have an insignificant clause to add all OR after + hqlWithoutSelectBuilder.append(" AND ( 1=0 "); // Same as previously : need to have an insignificant clause to add all OR after String orFirstname = DaoUtils.orAttributeLike("DO", CoselmarUser.PROPERTY_FIRSTNAME, args, searchExample.getOwnerName()); - hqlBuilder.append(orFirstname); + hqlWithoutSelectBuilder.append(orFirstname); // Name String orName = DaoUtils.orAttributeLike("DO", CoselmarUser.PROPERTY_NAME, args, searchExample.getOwnerName()); - hqlBuilder.append(orName); + hqlWithoutSelectBuilder.append(orName); - hqlBuilder.append(" ) "); // Close this keywords clause + hqlWithoutSelectBuilder.append(" ) "); // Close this keywords clause } // Manage date range if (searchExample.getPublicationBeforeDate() != null) { String publicationDateConditionBefore = DaoUtils.andAttributeLesserOrEquals("D", Document.PROPERTY_PUBLICATION_DATE, args, searchExample.getPublicationBeforeDate()); - hqlBuilder.append(publicationDateConditionBefore); + hqlWithoutSelectBuilder.append(publicationDateConditionBefore); } if (searchExample.getPublicationAfterDate() != null) { String publicationDateConditionAfter = DaoUtils.andAttributeGreaterOrEquals("D", Document.PROPERTY_PUBLICATION_DATE, args, searchExample.getPublicationAfterDate()); - hqlBuilder.append(publicationDateConditionAfter); + hqlWithoutSelectBuilder.append(publicationDateConditionAfter); } if (searchExample.getDepositBeforeDate() != null) { String depositDateConditionBefore = DaoUtils.andAttributeLesserOrEquals("D", Document.PROPERTY_DEPOSIT_DATE, args, searchExample.getDepositBeforeDate()); - hqlBuilder.append(depositDateConditionBefore); + hqlWithoutSelectBuilder.append(depositDateConditionBefore); } if (searchExample.getDepositAfterDate() != null) { String depositDateConditionAfter = DaoUtils.andAttributeGreaterOrEquals("D", Document.PROPERTY_DEPOSIT_DATE, args, searchExample.getDepositAfterDate()); - hqlBuilder.append(depositDateConditionAfter); + hqlWithoutSelectBuilder.append(depositDateConditionAfter); } - hqlBuilder.append(" ) "); + hqlWithoutSelectBuilder.append(" ) "); } // Manage keywords search in : title, summary, authors and keywords if (searchExample.getFullTextSearch() != null && !searchExample.getFullTextSearch().isEmpty()) { - hqlBuilder.append(" AND ( 1 = 0 "); + hqlWithoutSelectBuilder.append(" AND ( 1 = 0 "); for (String keyword : searchExample.getFullTextSearch()) { String nameClause = DaoUtils.orAttributeLike("D", Document.PROPERTY_NAME, args, keyword); String summaryClause = DaoUtils.orAttributeLike("D", Document.PROPERTY_SUMMARY, args, keyword); String authorsClause = DaoUtils.orAttributeLike("D", Document.PROPERTY_AUTHORS, args, keyword); String containsKeyword = DaoUtils.orAttributeContains("D", Document.PROPERTY_KEYWORDS, args, keyword); - hqlBuilder.append(nameClause); - hqlBuilder.append(summaryClause); - hqlBuilder.append(authorsClause); - hqlBuilder.append(containsKeyword); + hqlWithoutSelectBuilder.append(nameClause); + hqlWithoutSelectBuilder.append(summaryClause); + hqlWithoutSelectBuilder.append(authorsClause); + hqlWithoutSelectBuilder.append(containsKeyword); } - hqlBuilder.append(" )"); + hqlWithoutSelectBuilder.append(" )"); } // Add the prefix for order clause @@ -237,9 +242,17 @@ public class DocumentTopiaDao extends AbstractDocumentTopiaDao<Document> { PaginationParameter paginationParameter = searchExample.getPaginationParameter(); - List<Document> documents = forHql(hqlBuilder.toString(), args).find(paginationParameter); + // Cannot use forHql().findPage due to topia bug with joins + // count total elements + StringBuilder hqlCountBuilder = new StringBuilder("SELECT count(D." + Document.PROPERTY_TOPIA_ID + ") ").append(hqlWithoutSelectBuilder); + Long count = findUnique(hqlCountBuilder.toString(), args); + // Get elements for wanted page + StringBuilder hqlSelectBuilder = new StringBuilder("SELECT DISTINCT(D) ").append(hqlWithoutSelectBuilder); + List<Document> documents = forHql(hqlSelectBuilder.toString(), args).find(paginationParameter); - return documents; + PaginationResult paginatedDocuments = PaginationResult.of(documents, count, paginationParameter); + + return paginatedDocuments; } public List<String> findAllKeywords() { diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/DocumentsWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/DocumentsWebService.java index a0e6642..7f4c58a 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/DocumentsWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/DocumentsWebService.java @@ -56,6 +56,8 @@ import org.debux.webmotion.server.call.UploadFile; import org.debux.webmotion.server.render.Render; import org.nuiton.topia.persistence.TopiaNoResultException; import org.nuiton.util.DateUtil; +import org.nuiton.util.pagination.PaginationParameter; +import org.nuiton.util.pagination.PaginationResult; import java.io.File; import java.io.FileInputStream; @@ -152,6 +154,13 @@ public class DocumentsWebService extends CoselmarWebServiceSupport { public List<DocumentBean> getDocuments(DocumentSearchBean searchBean) throws InvalidCredentialException { + PaginationResult<DocumentBean> paginatedDocuments = getPaginatedDocuments(searchBean); + + return paginatedDocuments.getElements(); + } + + public PaginationResult<DocumentBean> getPaginatedDocuments(DocumentSearchBean searchBean) throws InvalidCredentialException { + // Check authentication String authorization = getContext().getHeader("Authorization"); CoselmarUser currentUser = checkUserAuthentication(authorization); @@ -195,7 +204,7 @@ public class DocumentsWebService extends CoselmarWebServiceSupport { } - List<Document> documentList; + PaginationResult<Document> paginatedDocuments; // Admin and Supervisor can see all documents (public, private and restricted) if (Lists.newArrayList(CoselmarUserRole.ADMIN, CoselmarUserRole.SUPERVISOR).contains(currentUserRole)) { @@ -207,27 +216,30 @@ public class DocumentsWebService extends CoselmarWebServiceSupport { List<String> documentIds = documentsIndexationService.searchDocuments(searchKeywords); List<String> documentFullIds = getDocumentsFullId(documentIds); - documentList = getDocumentDao().forTopiaIdIn(documentFullIds).findAll(); + Long count = getDocumentDao().forTopiaIdIn(documentFullIds).count(); + List<Document> documentList = getDocumentDao().forTopiaIdIn(documentFullIds).find(searchExample.getPaginationParameter()); + + paginatedDocuments = PaginationResult.of(documentList, count, searchExample.getPaginationParameter()); } catch (IOException | ParseException e) { if (log.isErrorEnabled()) { log.error("Unable to search by lucene, make search directly in database", e); } - documentList = getDocumentDao().findAllContainingAllKeywords(searchKeywords); + paginatedDocuments = getDocumentDao().findPaginatedContainingAllKeywords(searchKeywords, searchExample.getPaginationParameter()); } } else { - documentList = getDocumentDao().findAllByExample(null, searchExample); + paginatedDocuments = getDocumentDao().findAllByExample(null, searchExample); } } else { //Other can only see public, his own private and restricted for which he is allowed - documentList = getDocumentDao().findAllByExample(currentUser, searchExample); + paginatedDocuments = getDocumentDao().findAllByExample(currentUser, searchExample); } - List<DocumentBean> result = new ArrayList<>(documentList.size()); + List<DocumentBean> documentBeans = new ArrayList<>(paginatedDocuments.getElements().size()); - for (Document document : documentList) { + for (Document document : paginatedDocuments.getElements()) { DocumentBean documentBean = BeanEntityConverter.toBean(getPersistenceContext().getTopiaIdFactory(), document); // Manage related Question @@ -237,9 +249,10 @@ public class DocumentsWebService extends CoselmarWebServiceSupport { documentBean.setNbRelatedQuestions((int) (relatedQuestions + relatedQuestionsAsClosing)); - result.add(documentBean); + documentBeans.add(documentBean); } + PaginationResult result = PaginationResult.of(documentBeans, paginatedDocuments.getCount(), paginatedDocuments.getCurrentPage()); return result; } diff --git a/coselmar-rest/src/main/resources/mapping b/coselmar-rest/src/main/resources/mapping index 5c97dc1..156ce9b 100644 --- a/coselmar-rest/src/main/resources/mapping +++ b/coselmar-rest/src/main/resources/mapping @@ -32,6 +32,7 @@ GET /v1/doc DocApi.showMapping # Documents Api GET /v1/documents DocumentsWebService.getDocuments +GET /v2/documents DocumentsWebService.getPaginatedDocuments GET /v1/documents/keywords DocumentsWebService.getKeywords GET /v1/documents/types DocumentsWebService.getTypes GET /v1/documents/{documentId} DocumentsWebService.getDocument diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index cd105f8..6f32f3b 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -180,14 +180,13 @@ coselmarControllers.controller("DocumentsCtrl", ['$scope', '$route', '$routePara $scope.$emit('dataLoading'); //manage keywords if given - $scope.search = { searchKeywords : []}; - $scope.example = { keywords: []}; + $scope.example = { fullTextSearch : [], keywords: [], page: 0, limit: 25}; var keywords = $routeParams.keywords; if (Array.isArray(keywords)) { - $scope.search.searchKeywords = keywords; + $scope.example.fullTextSearch = keywords; } else if (keywords) { - $scope.search.searchKeywords.push(keywords); + $scope.example.fullTextSearch.push(keywords); } var advancedSearch = $routeParams.advancedSearch; @@ -197,10 +196,21 @@ coselmarControllers.controller("DocumentsCtrl", ['$scope', '$route', '$routePara $scope.advanced = false; } - documentService.getDocuments($scope.search.searchKeywords, function(documents) { - $scope.documents = documents; - }, errorService.defaultFailOnCall, function() { - $scope.$emit('dataLoaded'); + var page = $routeParams.page; + if (limit) { + $scope.example.page = page; + } + + var limit = $routeParams.limit; + if (limit) { + $scope.example.limit = limit; + } + + documentService.getPaginatedDocuments($scope.example, function(paginatedDocuments) { + $scope.paginationData = paginatedDocuments; + }, errorService.defaultFailOnCall, function() { + $scope.$broadcast('pageLoaded', $scope.paginationData.currentPage, $scope.paginationData.count) + $scope.$emit('dataLoaded'); }); $scope.deleteDocument = function(documentId){ @@ -220,7 +230,9 @@ coselmarControllers.controller("DocumentsCtrl", ['$scope', '$route', '$routePara }; $scope.searchDocuments = function(){ - $location.search('keywords', $scope.search.searchKeywords); + $location.search('keywords', $scope.example.fullTextSearch); + $location.search('page', $scope.example.page); + $location.search('limit', $scope.example.limit); }; $scope.advancedSearchDocuments = function() { @@ -242,11 +254,12 @@ coselmarControllers.controller("DocumentsCtrl", ['$scope', '$route', '$routePara } $scope.$emit('dataLoading'); - documentService.getAdvancedDocuments($scope.example, function(documents){ - $scope.documents = documents; - }, errorService.defaultFailOnCall, function() { - $scope.$emit('dataLoaded'); - }); + documentService.getPaginatedDocuments($scope.example, function(paginatedDocuments) { + $scope.paginationData = paginatedDocuments; + }, errorService.defaultFailOnCall, function() { + $scope.$broadcast('pageLoaded', $scope.paginationData.currentPage, $scope.paginationData.count) + $scope.$emit('dataLoaded'); + }); }; $scope.searchMode = function(type) { @@ -259,6 +272,17 @@ coselmarControllers.controller("DocumentsCtrl", ['$scope', '$route', '$routePara } }; + $scope.$on('loadPage', function(event, pageNumber, pageSize) { + $scope.example.page = pageNumber; + $scope.example.limit = pageSize; + documentService.getPaginatedDocuments($scope.example, function(paginatedDocuments) { + $scope.paginationData = paginatedDocuments; + }, errorService.defaultFailOnCall, function() { + $scope.$broadcast('pageLoaded', $scope.paginationData.currentPage, $scope.paginationData.count); + $scope.$emit('dataLoaded'); + }); + }); + }]); // Controller for new document View diff --git a/coselmar-ui/src/main/webapp/js/coselmar-services.js b/coselmar-ui/src/main/webapp/js/coselmar-services.js index 2129d30..64f4706 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-services.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-services.js @@ -32,6 +32,7 @@ function Document(resource, config){ this.resource = resource; var baseURL = config.BASE_URL + "v1/documents"; + var baseV2URL = config.BASE_URL + "v2/documents"; var usersURL = config.BASE_URL + "v1/users"; this.saveDocument = function(document, successFunction, failFunction, finallyFunction) { @@ -141,4 +142,10 @@ function Document(resource, config){ docResource.query(successFunction, failFunction).$promise.finally(finallyFunction); }; + this.getPaginatedDocuments = function(searchExample, successFunction, failFunction, finallyFunction){ + // Load documents with search example + var docResource = resource(baseV2URL, {'searchBean' : searchExample}); + docResource.get(successFunction, failFunction).$promise.finally(finallyFunction); + }; + }; \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/js/paginationBinder.js b/coselmar-ui/src/main/webapp/js/paginationBinder.js index a9b8fab..fc6606d 100644 --- a/coselmar-ui/src/main/webapp/js/paginationBinder.js +++ b/coselmar-ui/src/main/webapp/js/paginationBinder.js @@ -46,9 +46,8 @@ paginationBinder.controller('paginationController', function($scope) { if (paginationParameter.pageSize <= 0) { $scope.pageContext.nbPages = 0; } else { - $scope.pageContext.nbPages = Math.ceil(nbElements / paginationParameter.pageSize) - 1; + $scope.pageContext.nbPages = Math.max(0 ,Math.ceil(nbElements / paginationParameter.pageSize) - 1); } - $scope.pageContext.firstPage = $scope.pageContext.pageNumber == 0; $scope.pageContext.lastPage = $scope.pageContext.pageNumber == $scope.pageContext.nbPages; diff --git a/coselmar-ui/src/main/webapp/views/documents/documents.html b/coselmar-ui/src/main/webapp/views/documents/documents.html index 1580631..61b3c1b 100644 --- a/coselmar-ui/src/main/webapp/views/documents/documents.html +++ b/coselmar-ui/src/main/webapp/views/documents/documents.html @@ -45,7 +45,7 @@ </tr> </thead> <tbody> - <tr ng-repeat="document in documents"> + <tr ng-repeat="document in paginationData.elements"> <td><a href="#/documents/{{document.id}}">{{document.name}}</a></td> <td>{{document.authors}}</td> <td>{{document.privacy}}</td> @@ -61,6 +61,7 @@ </tr> </tbody> </table> + <pagination-tool></pagination-tool> <p ng-if="documents && documents.length == 0" translate="common.search.noResult" class="info"/> </div> </div> \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/documents/toolsPart.html b/coselmar-ui/src/main/webapp/views/documents/toolsPart.html index cfac4a9..bf1b457 100644 --- a/coselmar-ui/src/main/webapp/views/documents/toolsPart.html +++ b/coselmar-ui/src/main/webapp/views/documents/toolsPart.html @@ -37,7 +37,7 @@ <span class="fa fa-info-circle" tooltip-placement="bottom" uib-tooltip="{{ 'common.message.info.searchKeywords' | translate }}"></span> <div class="input-group"> - <input type="text" class="form-control" placeholder="keyword1,keyword2,..." ng-model="search.searchKeywords" ng-list /> + <input type="text" class="form-control" placeholder="keyword1,keyword2,..." ng-model="example.fullTextSearch" ng-list /> <span class="input-group-btn"> <button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button> </span> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.