branch develop updated (bf95eed -> 6a6e527)
This is an automated email from the git hooks/post-receive script. New change to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git from bf95eed show only active user by default new 51a0ca3 fix js new 3363548 add way to add parents with questions creation new f663f64 cannot set a question as parent of itself new 2ac5fb9 add a Question search service new 7c0480d add privacy and status as indexed property for question, and use QuestionSearchBean for lucene question search new fbea684 integrate index search for question new 84a0543 add search by keywords for questions new 66e7792 fix dependencies new 6385289 add question parents and children in ui new 6a6e527 fixes #6285 : Merge branch 'feature/6285-manage-relation-between-questions' into develop The 10 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 6a6e52797ce0a96db09d42b9894d3f7eb7983c28 Merge: 51a0ca3 6385289 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 18:31:52 2015 +0100 fixes #6285 : Merge branch 'feature/6285-manage-relation-between-questions' into develop commit 63852895497e96511ebb1eb13b01e7c967fe0a03 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 18:31:43 2015 +0100 add question parents and children in ui commit 66e7792a49e881b7ba34af2b216a096895512379 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 17:34:35 2015 +0100 fix dependencies commit 84a054359d6e29d3b48b116b56393e17f0772629 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 17:10:50 2015 +0100 add search by keywords for questions commit fbea68405a181f596388b98595791a540c166d62 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 14:50:19 2015 +0100 integrate index search for question commit 7c0480d6a3a56791c1888aec0a67049bd6f2b50a Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 11:36:47 2015 +0100 add privacy and status as indexed property for question, and use QuestionSearchBean for lucene question search commit 2ac5fb9653ee6e40a552724e6f9e768bb080e37f Author: Yannick Martel <martel@©odelutin.com> Date: Tue Jan 6 17:22:05 2015 +0100 add a Question search service commit f663f640569e86d88df89ceb2e9148cd304edc25 Author: Yannick Martel <martel@©odelutin.com> Date: Mon Jan 5 16:42:50 2015 +0100 cannot set a question as parent of itself commit 3363548872e4b6e443b09abcdc0ed9e762c7fbbc Author: Yannick Martel <martel@©odelutin.com> Date: Mon Jan 5 16:03:04 2015 +0100 add way to add parents with questions creation commit 51a0ca377f196dd6740b714a7c24e192716b1bf8 Author: Yannick Martel <martel@©odelutin.com> Date: Mon Jan 5 14:07:37 2015 +0100 fix js Summary of changes: coselmar-persistence/pom.xml | 16 -- .../ifremer/coselmar/beans/QuestionSearchBean.java | 40 +++ .../fr/ifremer/coselmar/persistence/DaoUtils.java | 15 ++ .../persistence/entity/QuestionTopiaDao.java | 162 ++++++++++- coselmar-rest/pom.xml | 4 - .../coselmar/converter/BeanEntityConverter.java | 2 +- .../services/CoselmarRestApplicationListener.java | 4 +- .../indexation/QuestionsIndexationService.java | 77 ++++-- .../services/v1/InitialisationService.java | 13 + .../coselmar/services/v1/QuestionsWebService.java | 189 +++++++++++-- .../services/FakeCoselmarApplicationContext.java | 8 + .../coselmar/services/QuestionsWebServiceTest.java | 299 +++++++++++++++++++++ .../indexation/QuestionsIndexationServiceTest.java | 106 ++++++-- .../src/main/webapp/js/coselmar-controllers.js | 82 +++++- .../main/webapp/js/coselmar-questions-services.js | 14 +- coselmar-ui/src/main/webapp/js/coselmar.js | 3 - .../main/webapp/views/questions/editquestion.html | 37 +++ .../views/questions/modalQuestionSearch.html | 48 ++++ .../src/main/webapp/views/questions/questions.html | 8 + .../views/questions/viewRestrictedQuestion.html | 32 ++- .../main/webapp/views/questions/viewquestion.html | 28 +- 21 files changed, 1087 insertions(+), 100 deletions(-) create mode 100644 coselmar-persistence/src/main/java/fr/ifremer/coselmar/beans/QuestionSearchBean.java create mode 100644 coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java create mode 100644 coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 51a0ca377f196dd6740b714a7c24e192716b1bf8 Author: Yannick Martel <martel@©odelutin.com> Date: Mon Jan 5 14:07:37 2015 +0100 fix js --- coselmar-ui/src/main/webapp/js/coselmar.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/coselmar-ui/src/main/webapp/js/coselmar.js b/coselmar-ui/src/main/webapp/js/coselmar.js index 35d73d0..f10805e 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar.js +++ b/coselmar-ui/src/main/webapp/js/coselmar.js @@ -108,9 +108,6 @@ coselmarApp.config(function($httpProvider) { 'responseError': function(rejection) { if (rejection.status == 401) { - //logout bad user - delete $scope.currentUser; - localStorage.removeItem('coselmar-jwt'); // create return to path wanted var returnTo = $location.path(); $location.path("/401").search('returnTo', returnTo); -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 3363548872e4b6e443b09abcdc0ed9e762c7fbbc Author: Yannick Martel <martel@©odelutin.com> Date: Mon Jan 5 16:03:04 2015 +0100 add way to add parents with questions creation --- .../src/main/webapp/js/coselmar-controllers.js | 61 +++++++++++++++++++++- .../main/webapp/views/questions/editquestion.html | 37 +++++++++++++ .../views/questions/modalQuestionSearch.html | 47 +++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 2e82b34..7cff4f0 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -707,7 +707,40 @@ coselmarControllers.controller("QuestionCtrl", ['$scope', '$route', '$routeParam if (document && position != -1) { documentList.splice(position, 1); } - } + }; + + // Modals part for parent questions + $scope.modalSearchParents = function () { + + var modalInstance = $modal.open({ + templateUrl: 'views/questions/modalQuestionSearch.html', + controller: 'ModalSearchQuestionsCtrl', + size: 'lg' + }); + + modalInstance.result.then(function (selectedQuestion) { + var already = false; + if (! $scope.question.parents) { + $scope.question.parents = []; + } else { + for (var i = 0; i < $scope.question.parents.length; i++) { + if ($scope.question.parents[i].id == selectedQuestion.id) { + already = true; + } + } + } + if (!already) { + $scope.question.parents.push(selectedQuestion); + } + }); + }; + + $scope.removeParent = function(parent) { + var position = $scope.question.parents.indexOf(parent); + if (parent && position != -1) { + $scope.question.parents.splice(position, 1); + } + }; $scope.isClient = function() { var isClient = false; @@ -743,6 +776,32 @@ coselmarControllers.controller('ModalSearchDocumentsCtrl', function ($scope, $mo }); +coselmarControllers.controller('ModalSearchQuestionsCtrl', function ($scope, $modalInstance, questionsService) { + + $scope.searchKeywords = []; + + questionsService.getQuestions(function(questions) { + $scope.questions = questions; + }); + + $scope.searchQuestions = function(searchKeywords) { + $scope.searchKeywords = searchKeywords; + questionsService.getQuestions(function(questions) { + $scope.questions = questions; + }); + + } + + $scope.select = function (question) { + $modalInstance.close(question); + }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + +}); + coselmarControllers.controller('ModalCreateDocumentsCtrl', function ($scope, $modalInstance, documentService) { $scope.document = {'privacy': 'PUBLIC', 'keywords' : []}; diff --git a/coselmar-ui/src/main/webapp/views/questions/editquestion.html b/coselmar-ui/src/main/webapp/views/questions/editquestion.html index b0e79c1..9a82637 100644 --- a/coselmar-ui/src/main/webapp/views/questions/editquestion.html +++ b/coselmar-ui/src/main/webapp/views/questions/editquestion.html @@ -251,6 +251,43 @@ <div class="form-group" > + <label class="col-md-2 control-label">Parent Questions</label> + + <div class="col-md-10"> + <table class="table table-bordered table-condensed"> + <tr> + <th>Title</th> + <th>Submission Date</th> + <th>Themes</th> + <th>Status</th> + <th> + <a class="btn fa fa-search" title="Search parent question" + ng-click="modalSearchParents()" /> + </th> + </tr> + <tr ng-repeat="parent in question.parents"> + <td tooltip-placement="bottom" tooltip-html-unsafe="{{parent.summary}}"> + {{parent.title}} + </td> + <td>{{parent.submissionDate | date:'mediumDate'}}</td> + <td><span ng-repeat="theme in parent.themes">{{theme}}, </span></td> + <td>{{parent.status}}</td> + <td><a class="btn fa fa-minus" title="Remove parent question" + ng-click="removeParent(parent)" + ng-if="currentUser.role == 'SUPERVISOR'"/></td> + </tr> + </table> + </div> + + </div> + + <!-- End Line with Related Document --> + + <!-- Line with Related Document --> + + <div class="form-group" > + + <label class="col-md-2 control-label">Related Documents</label> <div class="col-md-10"> diff --git a/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html b/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html new file mode 100644 index 0000000..6753413 --- /dev/null +++ b/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html @@ -0,0 +1,47 @@ +<div style="padding: 0px 0px 0px 30px"> + <div class="page-header"> + <h1> + <!-- Heading goes here --> + Assign Parent Question + </h1> + </div> + + <div> + <!--<div>--> + <!--<form class="form-inline pull-right" role="questionOptions" ng-submit="searchQuestions(searchKeywords)">--> + <!--<div class="form-group">--> + <!--<input type="search" class="form-control" placeholder="word1,word2,..." ng-model="searchKeywords" ng-list />--> + <!--</div>--> + <!--<div class="form-group">--> + <!--<button type="submit" class="btn btn-default fa fa-search"></button>--> + <!--</div>--> + <!--</form>--> + <!--</div>--> + <br/> + <table class="table"> + <tr> + <th>Title</th> + <th>Submission Date</th> + <th>Theme</th> + <th>Status</th> + <th></th> + </tr> + <tr ng-repeat="question in questions"> + <td> + <a href="#/questions/{{question.id}}" target="_blank" + tooltip-placement="bottom" tooltip-html-unsafe="{{question.summary}}"> + {{question.title}} + </a> + </td> + <td>{{question.submissionDate | date:'mediumDate'}}</td> + <td><span ng-repeat="theme in question.themes">{{theme}} ,</span></td> + <td>{{document.status}}</td> + <td> + <a class="btn fa fa-plus" title="Add question as parent" ng-click="select(question)"/></td> + </tr> + </table> + </div> + <div class="modal-footer"> + <button class="btn btn-warning" ng-click="cancel()">Cancel</button> + </div> +</div> \ No newline at end of file -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 f663f640569e86d88df89ceb2e9148cd304edc25 Author: Yannick Martel <martel@©odelutin.com> Date: Mon Jan 5 16:42:50 2015 +0100 cannot set a question as parent of itself --- coselmar-ui/src/main/webapp/js/coselmar-controllers.js | 13 +++++++++++-- .../main/webapp/views/questions/modalQuestionSearch.html | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 7cff4f0..1e8572e 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -715,11 +715,19 @@ coselmarControllers.controller("QuestionCtrl", ['$scope', '$route', '$routeParam var modalInstance = $modal.open({ templateUrl: 'views/questions/modalQuestionSearch.html', controller: 'ModalSearchQuestionsCtrl', - size: 'lg' + size: 'lg', + resolve : { + currentQuestionId : function() { + return $scope.question.id; + } + } }); modalInstance.result.then(function (selectedQuestion) { var already = false; + if ($scope.question.id && $scope.question.id == selectedQuestion.id) { + already = true; + } if (! $scope.question.parents) { $scope.question.parents = []; } else { @@ -776,9 +784,10 @@ coselmarControllers.controller('ModalSearchDocumentsCtrl', function ($scope, $mo }); -coselmarControllers.controller('ModalSearchQuestionsCtrl', function ($scope, $modalInstance, questionsService) { +coselmarControllers.controller('ModalSearchQuestionsCtrl', function ($scope, $modalInstance, currentQuestionId, questionsService) { $scope.searchKeywords = []; + $scope.currentQuestionId = currentQuestionId; questionsService.getQuestions(function(questions) { $scope.questions = questions; diff --git a/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html b/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html index 6753413..3b86fad 100644 --- a/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html +++ b/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html @@ -37,7 +37,8 @@ <td><span ng-repeat="theme in question.themes">{{theme}} ,</span></td> <td>{{document.status}}</td> <td> - <a class="btn fa fa-plus" title="Add question as parent" ng-click="select(question)"/></td> + <a class="btn fa fa-plus" title="Add question as parent" + ng-click="select(question)" ng-if="currentQuestionId != question.id"/></td> </tr> </table> </div> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 2ac5fb9653ee6e40a552724e6f9e768bb080e37f Author: Yannick Martel <martel@©odelutin.com> Date: Tue Jan 6 17:22:05 2015 +0100 add a Question search service --- .../ifremer/coselmar/beans/QuestionSearchBean.java | 40 +++ .../persistence/entity/QuestionTopiaDao.java | 93 ++++++- .../services/CoselmarRestApplicationListener.java | 4 +- .../services/v1/InitialisationService.java | 13 + .../coselmar/services/v1/QuestionsWebService.java | 108 ++++++-- .../services/FakeCoselmarApplicationContext.java | 8 + .../coselmar/services/QuestionsWebServiceTest.java | 299 +++++++++++++++++++++ 7 files changed, 544 insertions(+), 21 deletions(-) diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/beans/QuestionSearchBean.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/beans/QuestionSearchBean.java new file mode 100644 index 0000000..7e29a07 --- /dev/null +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/beans/QuestionSearchBean.java @@ -0,0 +1,40 @@ +package fr.ifremer.coselmar.beans; + +import java.io.Serializable; +import java.util.List; + +/** + * @author ymartel <martel@codelutin.com> + */ +public class QuestionSearchBean implements Serializable { + + private static final long serialVersionUID = -3318003570685481073L; + + protected List<String> keywords; + protected String status; + protected String privacy; + + public List<String> getKeywords() { + return keywords; + } + + public void setKeywords(List<String> keywords) { + this.keywords = keywords; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getPrivacy() { + return privacy; + } + + public void setPrivacy(String privacy) { + this.privacy = privacy; + } +} diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java index 54151cf..d1d1e71 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java @@ -28,11 +28,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.persistence.DaoUtils; +import org.apache.commons.lang3.StringUtils; public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { - public List<Question> findForExpert(CoselmarUser expert) { + public List<Question> findForExpert(CoselmarUser expert, QuestionSearchBean searchBean) { StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q " + " INNER JOIN Q." + Question.PROPERTY_PARTICIPANTS + " CUG "); @@ -41,7 +43,7 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { String publicCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PUBLIC, ""); - hqlBuilder.append(" WHERE (" + publicCondition + " ) "); + hqlBuilder.append(" WHERE ( (" + publicCondition + " ) "); String privateCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PRIVATE, ""); @@ -49,7 +51,49 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { String userCondition = DaoUtils.andAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); - hqlBuilder.append(userCondition + ")"); + hqlBuilder.append(userCondition + ") )"); + + if (searchBean != null) { + String finerHql = refineSearch(searchBean, "Q", args); + hqlBuilder.append(" AND (" + finerHql + ")" ); + } + + + List<Question> questions = forHql(hqlBuilder.toString(), args).findAll(); + + return questions; + } + + public List<Question> findForClient(CoselmarUser client, QuestionSearchBean searchBean) { + + StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q "); + + Map<String, Object> args = new HashMap<>(); + + String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_PRIVACY, args, Privacy.PUBLIC); + + hqlBuilder.append(" WHERE 1=1 AND (" + clientCondition + " ) "); + + if (searchBean != null) { + String finerHql = refineSearch(searchBean, "Q", args); + hqlBuilder.append(" AND (" + finerHql + ")" ); + } + + List<Question> questions = forHql(hqlBuilder.toString(), args).findAll(); + + return questions; + } + + public List<Question> findWithSearchBean(QuestionSearchBean searchBean) { + + StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q "); + + Map<String, Object> args = new HashMap<>(); + + if (searchBean != null) { + String finerHql = refineSearch(searchBean, "Q", args); + hqlBuilder.append(" WHERE (" + finerHql + ")" ); + } List<Question> questions = forHql(hqlBuilder.toString(), args).findAll(); @@ -79,4 +123,47 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { return values; } + public String refineSearch(QuestionSearchBean searchBean, String alias, Map args) { + + StringBuilder finerHqlBuilder = new StringBuilder(" 1=1 "); + + String privacy = searchBean.getPrivacy(); + + if (StringUtils.isNotBlank(privacy)) { + String privacyHql = DaoUtils.andAttributeEquals(alias, Question.PROPERTY_PRIVACY, args, Privacy.valueOf(privacy.toUpperCase())); + finerHqlBuilder.append(privacyHql); + } + + String status = searchBean.getStatus(); + + if (StringUtils.isNotBlank(status)) { + String statusHql = DaoUtils.andAttributeEquals(alias, Question.PROPERTY_STATUS, args, Status.valueOf(status.toUpperCase())); + finerHqlBuilder.append(statusHql); + } + + List<String> keywords = searchBean.getKeywords(); + if (keywords != null && !keywords.isEmpty()) { + // should be like : """ ( (1=1) + // AND ( 1=0 OR title like keyword1 OR summary like keyword1 OR theme contains keyword1 ) + // AND ( 1=0 OR title like keyword2 OR summary like keyword2 OR theme contains keyword2 ) + // ) """ + StringBuilder keywordsBuilder = new StringBuilder(" ( ( 1 = 1 ) "); + + for (String keyword : keywords) { + keywordsBuilder.append(" AND ( 1=0 "); + keywordsBuilder.append(DaoUtils.orAttributeLike(alias, Question.PROPERTY_TITLE, args, keyword)); + keywordsBuilder.append(DaoUtils.orAttributeLike(alias, Question.PROPERTY_SUBMISSION_DATE, args, keyword)); + keywordsBuilder.append(DaoUtils.orAttributeContains(alias, Question.PROPERTY_THEME, args, keyword)); + keywordsBuilder.append(" ) "); + } + + keywordsBuilder.append(" ) "); + String keywordHql = keywordsBuilder.toString(); + finerHqlBuilder.append(" AND " + keywordHql); + } + + String finerHql = finerHqlBuilder.toString(); + return finerHql; + } + } //QuestionTopiaDao diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java index 8af741c..5715dd0 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java @@ -30,6 +30,7 @@ import java.util.Set; import com.google.common.collect.Sets; import fr.ifremer.coselmar.beans.DocumentBean; import fr.ifremer.coselmar.beans.QuestionBean; +import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.beans.UserBean; import fr.ifremer.coselmar.beans.UserSearchBean; import fr.ifremer.coselmar.converter.DateConverter; @@ -50,7 +51,8 @@ public class CoselmarRestApplicationListener implements WebMotionServerListener DocumentBean.class, UserBean.class, QuestionBean.class, - UserSearchBean.class + UserSearchBean.class, + QuestionSearchBean.class ); @Override diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/InitialisationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/InitialisationService.java index 1cd58d1..7ab6844 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/InitialisationService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/InitialisationService.java @@ -70,6 +70,19 @@ public class InitialisationService extends CoselmarSimpleServiceSupport { lambdaUser.setPassword(lambdaPassword); lambdaUser.setSalt(lambdaSalt); + // Set a default supervisor + CoselmarUser defaultSupervisor = getCoselmarUserDao().create(); + defaultSupervisor.setFirstname("Default"); + defaultSupervisor.setName("Supervisor"); + defaultSupervisor.setMail("default.supervisor@temporary.coselmar"); + defaultSupervisor.setRole(CoselmarUserRole.SUPERVISOR); + defaultSupervisor.setActive(true); + + String defaultSupSalt = servicesContext.generateSalt(); + String defaultSupPassword = servicesContext.encodePassword(defaultSupSalt, "manager1234"); + defaultSupervisor.setPassword(defaultSupPassword); + defaultSupervisor.setSalt(defaultSupSalt); + commit(); } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java index 2434279..57ad349 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java @@ -37,6 +37,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import fr.ifremer.coselmar.beans.DocumentBean; import fr.ifremer.coselmar.beans.QuestionBean; +import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.beans.UserBean; import fr.ifremer.coselmar.beans.UserWebToken; import fr.ifremer.coselmar.converter.BeanEntityConverter; @@ -218,7 +219,7 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { } } - public List<QuestionBean> getQuestions() throws InvalidCredentialException, UnauthorizedException { + public List<QuestionBean> getQuestions(QuestionSearchBean searchBean) throws InvalidCredentialException, UnauthorizedException { // Check authentication String authorization = getContext().getHeader("Authorization"); @@ -232,24 +233,11 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { List<Question> questionList; - if (StringUtils.equalsIgnoreCase(CoselmarUserRole.ADMIN.name(), currentUserRole) - || StringUtils.equalsIgnoreCase(CoselmarUserRole.SUPERVISOR.name(), currentUserRole)) { - questionList = getQuestionDao().findAll(); - - } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.MEMBER.name(), currentUserRole)) { - questionList = getQuestionDao().forPrivacyEquals(Privacy.PUBLIC).findAll(); - - } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.EXPERT.name(), currentUserRole)) { - questionList = getQuestionDao().findForExpert(currentUser); + if (searchBean != null) { + questionList = getAllFilteredQuestions(currentUser, searchBean); - } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.CLIENT.name(), currentUserRole)) { - questionList = getQuestionDao().forClientsContains(currentUser).findAll(); } else { - String message = "Not allowed to access this page"; - if (log.isWarnEnabled()) { - log.warn("Unknown user type try to access questions list."); - } - throw new UnauthorizedException(message); + questionList = getAllQuestions(currentUser); } List<QuestionBean> result = new ArrayList<>(questionList.size()); @@ -824,4 +812,90 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { } + /** + * Methods that retrieve all questions depending of current user role permission : + * <ul> + * <li>{@code ADMIN} and {@code SUPERVISOR} can access to all questions ;</li> + * <li>{@code MEMBER} can access to all public questions ;</li> + * <li>{@code EXPERT} can access to all private questions where he is participant or client and all public questions ;</li> + * <li>{@code CLIENT} can access to all questions where he is client.</li> + * </ul> + * + * @param currentUser : current user that make request + * + * @return list of all question user is authorized to access. + * @throws UnauthorizedException + */ + protected List<Question> getAllQuestions(CoselmarUser currentUser) throws UnauthorizedException { + String currentUserRole = currentUser.getRole().name(); + + List<Question> questionList; + if (StringUtils.equalsIgnoreCase(CoselmarUserRole.ADMIN.name(), currentUserRole) + || StringUtils.equalsIgnoreCase(CoselmarUserRole.SUPERVISOR.name(), currentUserRole)) { + questionList = getQuestionDao().findAll(); + + } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.MEMBER.name(), currentUserRole)) { + questionList = getQuestionDao().forPrivacyEquals(Privacy.PUBLIC).findAll(); + + } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.EXPERT.name(), currentUserRole)) { + questionList = getQuestionDao().findForExpert(currentUser, null); + + } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.CLIENT.name(), currentUserRole)) { + questionList = getQuestionDao().forClientsContains(currentUser).findAll(); + } else { + String message = "Not allowed to access this page"; + if (log.isWarnEnabled()) { + log.warn("Unknown user type try to access questions list."); + } + throw new UnauthorizedException(message); + } + return questionList; + } + + /** + * Methods that retrieve all questions matching search filter (based on + * title, summary, themes, status, privacy), also depending of current user role permission : + * <ul> + * <li>{@code ADMIN} and {@code SUPERVISOR} can access to all questions ;</li> + * <li>{@code MEMBER} can only access to all public questions (so, if filter is on private, it has no result) ;</li> + * <li>{@code EXPERT} can access to all private questions where he is participant or client and all public questions ;</li> + * <li>{@code CLIENT} can access to all questions where he is client.</li> + * </ul> + * + * @param currentUser : current user that make request + * @param searchBean : bean containing search param to filter result + * + * @return list of all question user is authorized to access. + * @throws UnauthorizedException + */ + protected List<Question> getAllFilteredQuestions(CoselmarUser currentUser, QuestionSearchBean searchBean) throws UnauthorizedException { + String currentUserRole = currentUser.getRole().name(); + + List<Question> questionList; + if (StringUtils.equalsIgnoreCase(CoselmarUserRole.ADMIN.name(), currentUserRole) + || StringUtils.equalsIgnoreCase(CoselmarUserRole.SUPERVISOR.name(), currentUserRole)) { + questionList = getQuestionDao().findWithSearchBean(searchBean); + + } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.MEMBER.name(), currentUserRole)) { + if (StringUtils.equals(searchBean.getPrivacy(), Privacy.PRIVATE.name())) { + questionList = new ArrayList<>(0); + } else { + questionList = getQuestionDao().findWithSearchBean(searchBean); + } + + } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.EXPERT.name(), currentUserRole)) { + questionList = getQuestionDao().findForExpert(currentUser, searchBean); + + } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.CLIENT.name(), currentUserRole)) { + questionList = getQuestionDao().findForClient(currentUser, searchBean); + } else { + String message = "Not allowed to access this page"; + if (log.isWarnEnabled()) { + log.warn("Unknown user type try to access questions list."); + } + throw new UnauthorizedException(message); + } + return questionList; + } + } diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/FakeCoselmarApplicationContext.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/FakeCoselmarApplicationContext.java index bdfacbc..c7c3129 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/FakeCoselmarApplicationContext.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/FakeCoselmarApplicationContext.java @@ -39,6 +39,7 @@ import fr.ifremer.coselmar.persistence.CoselmarTopiaApplicationContext; import fr.ifremer.coselmar.persistence.CoselmarTopiaPersistenceContext; import fr.ifremer.coselmar.services.config.CoselmarServicesConfig; import fr.ifremer.coselmar.services.config.CoselmarServicesConfigOption; +import fr.ifremer.coselmar.services.v1.InitialisationService; import org.apache.commons.logging.Log; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -120,6 +121,13 @@ public class FakeCoselmarApplicationContext extends TestWatcher implements Cosel Map<String, String> topiaProperties = configuration.getTopiaProperties(); applicationContext = new CoselmarTopiaApplicationContext(topiaProperties); + {//Init some users + CoselmarTopiaPersistenceContext persistenceContext = newPersistenceContext(); + CoselmarServicesContext serviceContext = newServiceContext(persistenceContext, Locale.FRANCE); + serviceContext.newService(InitialisationService.class).createDefaultUsers(); + persistenceContext.close(); + } + } @Override diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java new file mode 100644 index 0000000..f315b76 --- /dev/null +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java @@ -0,0 +1,299 @@ +package fr.ifremer.coselmar.services; + +/* + * #%L + * Coselmar :: Rest Services + * $Id:$ + * $HeadURL:$ + * %% + * Copyright (C) 2014 Ifremer, Code Lutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import fr.ifremer.coselmar.beans.QuestionBean; +import fr.ifremer.coselmar.converter.JsonHelper; +import org.apache.http.HttpResponse; +import org.apache.http.client.fluent.Request; +import org.apache.http.client.fluent.Response; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author ymartel <martel@codelutin.com> + */ +public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { + + protected FakeCoselmarServicesContext serviceContext; + + protected FakeCoselmarServicesContext getServiceContext() { + + if (serviceContext == null) { + serviceContext = application.newServiceContext(application.newPersistenceContext(), Locale.FRANCE); + } + + return serviceContext; + } + + @Test + public void testSearchQuestions() throws Exception { + + //First : login ! + Request loginRequest = createRequest("/v1/users/login") + .addParameter("mail", "default.supervisor@temporary.coselmar") + .addParameter("password", "manager1234") + .Post(); + + Response loginResponse = loginRequest.execute(); + String loginContent = loginResponse.returnContent().asString(); + Assert.assertNotNull(loginContent); + showTestResult(loginContent); + + Gson gson = new Gson(); + Map<String, String> loginMap = gson.fromJson(loginContent, Map.class); + + String supervisorToken = loginMap.get("jwt"); + + // Create first document + String documentOneTitle = "Ceci est le titre"; + Request addQuestionsRequest = createRequest("/v1/questions") + .addParameter("question", + "{title: '" + documentOneTitle + "', summary: 'ceci est le resume'," + + " submissionDate: '" + (new Date()).getTime() + "'," + + " themes: ['test', 'questionOne'], type: 'question'," + + " privacy : 'PUBLIC' }") + .Post() + .addHeader("Authorization", "Bearer " + supervisorToken); + + HttpResponse addQuestionResponse = addQuestionsRequest.execute().returnResponse(); + Assert.assertEquals(200, addQuestionResponse.getStatusLine().getStatusCode()); + + // Create second document + String documentTwoTitle = "There s someone missing. The question s Who?"; + addQuestionsRequest = createRequest("/v1/questions") + .addParameter("question", + "{title: '" + documentTwoTitle + "'," + + " summary: 'Something old, Something new, Something borrowed, Something blue.'," + + " submissionDate: '" + (new Date()).getTime() + "'," + + " themes: ['big bang two', 'Pandorica', 'River', 'Universe'], type: 'question'," + + " privacy : 'PUBLIC' }") + .Post() + .addHeader("Authorization", "Bearer " + supervisorToken); + + addQuestionResponse = addQuestionsRequest.execute().returnResponse(); + Assert.assertEquals(200, addQuestionResponse.getStatusLine().getStatusCode()); + + // And a third document ! + String documentThreeTitle = "Private things"; + addQuestionsRequest = createRequest("/v1/questions") + .addParameter("question", + "{title: '" + documentThreeTitle + "'," + + " summary: 'This question is private, zero access for others'," + + " submissionDate: '" + (new Date()).getTime() + "'," + + " themes: ['Universe', 'test'], type: 'question'," + + " privacy : 'PRIVATE' }") + .Post() + .addHeader("Authorization", "Bearer " + supervisorToken); + + addQuestionResponse = addQuestionsRequest.execute().returnResponse(); + Assert.assertEquals(200, addQuestionResponse.getStatusLine().getStatusCode()); + + // First search : as supervisor, no search bean : retrieve the three documents + Request searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{}") + .Get() + .addHeader("Authorization", "Bearer " + supervisorToken); + + Response searchResponse = searchRequest.execute(); + String searchResultContent = searchResponse.returnContent().asString(); + JsonHelper jsonHelper = new JsonHelper(true); + List<QuestionBean> result = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(3, result.size()); + Assert.assertNotNull(result.get(0)); + Assert.assertNotNull(result.get(1)); + Assert.assertNotNull(result.get(2)); + + + // Second search : as supervisor, searchBean only with public privacy : retrieve two documents (one and two) + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{privacy : 'public'}") + .Get() + .addHeader("Authorization", "Bearer " + supervisorToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + List<QuestionBean> publicPrivacyResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(2, publicPrivacyResult.size()); + List<String> expectedTitles = Arrays.asList(documentOneTitle, documentTwoTitle); + Assert.assertTrue(expectedTitles.contains(publicPrivacyResult.get(0).getTitle())); + Assert.assertTrue(expectedTitles.contains(publicPrivacyResult.get(1).getTitle())); + + + // Third search : as supervisor, searchBean with public privacy and test keywords : retrieve documentOne + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{privacy : 'public', keywords : ['test']}") + .Get() + .addHeader("Authorization", "Bearer " + supervisorToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + List<QuestionBean> publicTestResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(1, publicTestResult.size()); + Assert.assertEquals(documentOneTitle, publicTestResult.get(0).getTitle()); + + + // Fourth search : as supervisor, searchBean with test and pandorica keywords : retrieve nothing + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{privacy : 'public', keywords : ['test', 'pandorica']}") + .Get() + .addHeader("Authorization", "Bearer " + supervisorToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + List<QuestionBean> shouldNoResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertTrue(shouldNoResult.isEmpty()); + + + // Fifth search : as supervisor, searchBean with test keyword : retrieve documentOne and documentThree + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{keywords : ['test']}") + .Get() + .addHeader("Authorization", "Bearer " + supervisorToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + List<QuestionBean> testKeywordResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(2, testKeywordResult.size()); + expectedTitles = Arrays.asList(documentOneTitle, documentThreeTitle); + Assert.assertTrue(expectedTitles.contains(testKeywordResult.get(0).getTitle())); + Assert.assertTrue(expectedTitles.contains(testKeywordResult.get(1).getTitle())); + + + // Sixth search : as supervisor, searchBean with test and universe keywords : retrieve documentThree + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{keywords : ['test', 'Universe']}") + .Get() + .addHeader("Authorization", "Bearer " + supervisorToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + List<QuestionBean> testUniverseKeywordsResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(1, testUniverseKeywordsResult.size()); + Assert.assertEquals(documentThreeTitle, testUniverseKeywordsResult.get(0).getTitle()); + + + + //Have login for an expert ! + loginRequest = createRequest("/v1/users/login") + .addParameter("mail", "lambda.expert@temporary.coselmar") + .addParameter("password", "manager1234") + .Post(); + + loginResponse = loginRequest.execute(); + loginContent = loginResponse.returnContent().asString(); + Assert.assertNotNull(loginContent); + showTestResult(loginContent); + + loginMap = gson.fromJson(loginContent, Map.class); + + String expertToken = loginMap.get("jwt"); + + // Seventh search : as expert, no search bean : retrieve the two public documents + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{}") + .Get() + .addHeader("Authorization", "Bearer " + expertToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + result = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(2, result.size()); + expectedTitles = Arrays.asList(documentOneTitle, documentTwoTitle); + Assert.assertTrue(expectedTitles.contains(result.get(0).getTitle())); + + + // Eighth search : as expert, searchBean only with public privacy : retrieve two documents (one and two) (same as seventh) + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{privacy : 'public'}") + .Get() + .addHeader("Authorization", "Bearer " + expertToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + publicPrivacyResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(2, publicPrivacyResult.size()); + expectedTitles = Arrays.asList(documentOneTitle, documentTwoTitle); + Assert.assertTrue(expectedTitles.contains(publicPrivacyResult.get(0).getTitle())); + Assert.assertTrue(expectedTitles.contains(publicPrivacyResult.get(1).getTitle())); + + + // Ninth search : as expert, searchBean with public privacy and test keywords : retrieve documentOne + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{privacy : 'public', keywords : ['test']}") + .Get() + .addHeader("Authorization", "Bearer " + expertToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + publicTestResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertEquals(1, publicTestResult.size()); + Assert.assertEquals(documentOneTitle, publicTestResult.get(0).getTitle()); + + + // Tenth search : as supervisor, searchBean with test and pandorica keywords : retrieve nothing + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{privacy : 'public', keywords : ['test', 'pandorica']}") + .Get() + .addHeader("Authorization", "Bearer " + expertToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + shouldNoResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertTrue(shouldNoResult.isEmpty()); + + + // Eleventh search : as expert, searchBean with pandorica keyword : retrieve nothing (documentThree is private) + searchRequest = createRequest("/v1/questions") + .addParameter("searchBean", "{keywords : ['pandorica']}") + .Get() + .addHeader("Authorization", "Bearer " + expertToken); + + searchResponse = searchRequest.execute(); + searchResultContent = searchResponse.returnContent().asString(); + shouldNoResult = jsonHelper.fromJson(searchResultContent, new TypeToken<ArrayList<QuestionBean>>(){}.getType()); + + Assert.assertTrue(shouldNoResult.isEmpty()); + } +} -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 7c0480d6a3a56791c1888aec0a67049bd6f2b50a Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 11:36:47 2015 +0100 add privacy and status as indexed property for question, and use QuestionSearchBean for lucene question search --- .../indexation/QuestionsIndexationService.java | 77 +++++++++++---- .../coselmar/services/v1/QuestionsWebService.java | 71 +++++++++++++- .../indexation/QuestionsIndexationServiceTest.java | 106 +++++++++++++++++---- 3 files changed, 212 insertions(+), 42 deletions(-) diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java index a1f1773..475559c 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java @@ -6,7 +6,9 @@ import java.util.List; import java.util.Set; import fr.ifremer.coselmar.beans.QuestionBean; +import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.services.CoselmarSimpleServiceSupport; +import org.apache.commons.lang3.StringUtils; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; @@ -41,6 +43,8 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { protected static final String QUESTION_TITLE_INDEX_PROPERTY = "questionTitle"; protected static final String QUESTION_SUMMARY_INDEX_PROPERTY = "questionSummary"; protected static final String QUESTION_THEME_INDEX_PROPERTY = "questionTheme"; + protected static final String QUESTION_STATUS_INDEX_PROPERTY = "questionStatus"; + protected static final String QUESTION_PRIVACY_INDEX_PROPERTY = "questionPrivacy"; protected static final String DOCUMENT_TYPE = "question"; public void indexQuestion(QuestionBean question) throws IOException { @@ -69,6 +73,12 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { doc.add(new Field(QUESTION_THEME_INDEX_PROPERTY, theme, TextField.TYPE_STORED)); } + doc.removeField(QUESTION_STATUS_INDEX_PROPERTY); + doc.add(new TextField(QUESTION_STATUS_INDEX_PROPERTY, question.getStatus(), Field.Store.YES)); + + doc.removeField(QUESTION_PRIVACY_INDEX_PROPERTY); + doc.add(new TextField(QUESTION_PRIVACY_INDEX_PROPERTY, question.getPrivacy(), Field.Store.YES)); + getLuceneUtils().getIndexWriter().updateDocument(new Term(QUESTION_ID_INDEX_PROPERTY, question.getId()), doc); } else { @@ -79,13 +89,17 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { doc.add(new TextField(QUESTION_TITLE_INDEX_PROPERTY, question.getTitle(), Field.Store.YES)); doc.add(new TextField(QUESTION_SUMMARY_INDEX_PROPERTY, question.getSummary(), Field.Store.YES)); - doc.add(new Field("type", DOCUMENT_TYPE, TextField.TYPE_STORED)); + + doc.add(new TextField(QUESTION_STATUS_INDEX_PROPERTY, question.getStatus(), Field.Store.YES)); + doc.add(new TextField(QUESTION_PRIVACY_INDEX_PROPERTY, question.getPrivacy(), Field.Store.YES)); Set<String> themes = question.getThemes(); for (String theme : themes) { doc.add(new Field(QUESTION_THEME_INDEX_PROPERTY, theme, TextField.TYPE_STORED)); } + doc.add(new Field("type", DOCUMENT_TYPE, TextField.TYPE_STORED)); + getLuceneUtils().getIndexWriter().addDocument(doc); } @@ -96,35 +110,58 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { } - public List<String> searchQuestion(String text) throws IOException, ParseException { + public List<String> searchQuestion(QuestionSearchBean searchBean) throws IOException, ParseException { DirectoryReader ireader = DirectoryReader.open(getLuceneUtils().getIndexWriter(), false); IndexSearcher isearcher = new IndexSearcher(ireader); - String[] words = text.replaceAll("[^a-zA-Z ]", "").toLowerCase().split(" "); - - // Parse a simple query that searches for the "text": - BooleanQuery query = new BooleanQuery(); + // Combine that with the type + BooleanQuery fullQuery = new BooleanQuery(); + fullQuery.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); - PhraseQuery nameQuery = new PhraseQuery(); - PhraseQuery summaryQuery = new PhraseQuery(); - PhraseQuery authorsQuery = new PhraseQuery(); + String searchPrivacy = searchBean.getPrivacy(); + if(StringUtils.isNotBlank(searchPrivacy)) { + fullQuery.add(new TermQuery(new Term(QUESTION_PRIVACY_INDEX_PROPERTY, searchPrivacy.toLowerCase())), BooleanClause.Occur.MUST); + } - for (String word : words) { - nameQuery.add(new Term(QUESTION_TITLE_INDEX_PROPERTY, word.toLowerCase())); - summaryQuery.add(new Term(QUESTION_SUMMARY_INDEX_PROPERTY, word.toLowerCase())); + String searchStatus = searchBean.getStatus(); + if(StringUtils.isNotBlank(searchStatus)) { + fullQuery.add(new TermQuery(new Term(QUESTION_STATUS_INDEX_PROPERTY, searchStatus.toLowerCase())), BooleanClause.Occur.MUST); } - query.add(nameQuery, BooleanClause.Occur.SHOULD); - query.add(summaryQuery, BooleanClause.Occur.SHOULD); - query.add(authorsQuery, BooleanClause.Occur.SHOULD); + // Keywords part + List<String> keywords = searchBean.getKeywords(); + if (keywords != null && !keywords.isEmpty()) { + BooleanQuery keywordsQuery = new BooleanQuery(); - query.add(new TermQuery(new Term(QUESTION_THEME_INDEX_PROPERTY, text.toLowerCase())), BooleanClause.Occur.SHOULD); + for (String text : keywords) { + String[] words = text.replaceAll("[^a-zA-Z ]", "").toLowerCase().split(" "); + + // Parse a simple query that searches for the "text": + BooleanQuery query = new BooleanQuery(); + + PhraseQuery nameQuery = new PhraseQuery(); + PhraseQuery summaryQuery = new PhraseQuery(); + PhraseQuery authorsQuery = new PhraseQuery(); + + for (String word : words) { + nameQuery.add(new Term(QUESTION_TITLE_INDEX_PROPERTY, word.toLowerCase())); + summaryQuery.add(new Term(QUESTION_SUMMARY_INDEX_PROPERTY, word.toLowerCase())); + } + + query.add(nameQuery, BooleanClause.Occur.SHOULD); + query.add(summaryQuery, BooleanClause.Occur.SHOULD); + query.add(authorsQuery, BooleanClause.Occur.SHOULD); + + query.add(new TermQuery(new Term(QUESTION_THEME_INDEX_PROPERTY, text.toLowerCase())), BooleanClause.Occur.SHOULD); + + keywordsQuery.add(query, BooleanClause.Occur.MUST); + } + + // add to complete query + fullQuery.add(keywordsQuery, BooleanClause.Occur.MUST); + } - // Combine that with the type - BooleanQuery fullQuery = new BooleanQuery(); - fullQuery.add(query, BooleanClause.Occur.MUST); - fullQuery.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); ScoreDoc[] hits = isearcher.search(fullQuery, null, 1000).scoreDocs; diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java index 57ad349..05ce72e 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java @@ -55,8 +55,10 @@ import fr.ifremer.coselmar.services.indexation.QuestionsIndexationService; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.lucene.queryparser.classic.ParseException; import org.nuiton.topia.persistence.TopiaIdFactory; import org.nuiton.topia.persistence.TopiaNoResultException; +import org.nuiton.topia.persistence.TopiaQueryBuilderAddCriteriaOrRunQueryStep; /** * @author ymartel <martel@codelutin.com> @@ -870,17 +872,69 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { */ protected List<Question> getAllFilteredQuestions(CoselmarUser currentUser, QuestionSearchBean searchBean) throws UnauthorizedException { String currentUserRole = currentUser.getRole().name(); + QuestionsIndexationService questionsIndexationService = getServicesContext().newService(QuestionsIndexationService.class); List<Question> questionList; if (StringUtils.equalsIgnoreCase(CoselmarUserRole.ADMIN.name(), currentUserRole) || StringUtils.equalsIgnoreCase(CoselmarUserRole.SUPERVISOR.name(), currentUserRole)) { - questionList = getQuestionDao().findWithSearchBean(searchBean); + + if (searchBean != null && searchBean.getKeywords() != null && !searchBean.getKeywords().isEmpty()) { + try { + //TODO martel 20150106 : maybe its should be better if privacy and status are indexed too ? + List<String> questionIds = questionsIndexationService.searchQuestion(searchBean); + List<String> questionFullIds = getQuestionsFullId(questionIds); + TopiaQueryBuilderAddCriteriaOrRunQueryStep<Question> queryBuilder = getQuestionDao().forTopiaIdIn(questionFullIds); + if (StringUtils.isNotBlank(searchBean.getPrivacy())) { + queryBuilder.addEquals(Question.PROPERTY_PRIVACY, Privacy.valueOf(searchBean.getPrivacy().toUpperCase())); + } + if (StringUtils.isNotBlank(searchBean.getStatus())) { + queryBuilder.addEquals(Question.PROPERTY_STATUS, Privacy.valueOf(searchBean.getStatus().toUpperCase())); + } + questionList = queryBuilder.findAll(); + + } catch (IOException |ParseException e) { + if (log.isErrorEnabled()) { + log.error("Unable to search by lucene, make search directly in database", e); + } + questionList = getQuestionDao().findWithSearchBean(searchBean); + } + + // classical search with DAO + } else { + questionList = getQuestionDao().findWithSearchBean(searchBean); + + } } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.MEMBER.name(), currentUserRole)) { if (StringUtils.equals(searchBean.getPrivacy(), Privacy.PRIVATE.name())) { questionList = new ArrayList<>(0); } else { - questionList = getQuestionDao().findWithSearchBean(searchBean); + + if (searchBean != null && searchBean.getKeywords() != null && !searchBean.getKeywords().isEmpty()) { + try { + //TODO martel 20150106 : maybe its should be better if privacy and status are indexed too ? + List<String> questionIds = questionsIndexationService.searchQuestion(searchBean); + List<String> questionFullIds = getQuestionsFullId(questionIds); + TopiaQueryBuilderAddCriteriaOrRunQueryStep<Question> queryBuilder = getQuestionDao().forTopiaIdIn(questionFullIds); + if (StringUtils.isNotBlank(searchBean.getPrivacy())) { + queryBuilder.addEquals(Question.PROPERTY_PRIVACY, Privacy.valueOf(searchBean.getPrivacy().toUpperCase())); + } + if (StringUtils.isNotBlank(searchBean.getStatus())) { + queryBuilder.addEquals(Question.PROPERTY_STATUS, Privacy.valueOf(searchBean.getStatus().toUpperCase())); + } + questionList = queryBuilder.findAll(); + + } catch (IOException |ParseException e) { + if (log.isErrorEnabled()) { + log.error("Unable to search by lucene, make search directly in database", e); + } + questionList = getQuestionDao().findWithSearchBean(searchBean); + } + + // classical search with DAO + } else { + questionList = getQuestionDao().findWithSearchBean(searchBean); + } } } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.EXPERT.name(), currentUserRole)) { @@ -898,4 +952,17 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { return questionList; } + protected List<String> getQuestionsFullId(List<String> questionShortIds) { + + Function<String, String> getFullIds = new Function<String, String>() { + @Override + public String apply(String shortId) { + return Question.class.getCanonicalName() + getPersistenceContext().getTopiaIdFactory().getSeparator() + shortId; + } + }; + + List<String> fullIds = Lists.transform(questionShortIds, getFullIds); + return fullIds; + } + } diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java index d6175e8..9f486bc 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java @@ -1,11 +1,13 @@ package fr.ifremer.coselmar.services.indexation; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; import com.google.common.collect.Sets; import fr.ifremer.coselmar.beans.QuestionBean; +import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.persistence.entity.Privacy; import fr.ifremer.coselmar.persistence.entity.Status; import fr.ifremer.coselmar.services.AbstractCoselmarServiceTest; @@ -87,7 +89,7 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest questionThree.setDeadline(DateUtil.createDateAfterToday(16, 0, 0)); questionThree.setSubmissionDate(new Date()); questionThree.setStatus(Status.OPEN.name()); - questionThree.setPrivacy(Privacy.PUBLIC.name()); + questionThree.setPrivacy(Privacy.PRIVATE.name()); questionThree.setThemes(Sets.newHashSet("big bang two", "Pandorica", "River", "Universe")); @@ -100,37 +102,81 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest questionsIndexationService.indexQuestion(questionThree); // Ok, let's search now ! - List<String> questionMatchingTitleIds = questionsIndexationService.searchQuestion("ultimate"); + QuestionSearchBean searchBean = new QuestionSearchBean(); + searchBean.setKeywords(Arrays.asList("ultimate")); + List<String> questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingTitleIds.size()); Assert.assertEquals(questionTwoId, questionMatchingTitleIds.get(0)); - List<String> questionMatchingAmyIds = questionsIndexationService.searchQuestion("amy"); + searchBean.setKeywords(Arrays.asList("amy")); + List<String> questionMatchingAmyIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertTrue(questionMatchingAmyIds.isEmpty()); - List<String> questionMatchingPandoricaIds = questionsIndexationService.searchQuestion("Pandorica"); + searchBean.setKeywords(Arrays.asList("Pandorica")); + List<String> questionMatchingPandoricaIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingPandoricaIds.size()); Assert.assertEquals(questionThreeId, questionMatchingPandoricaIds.get(0)); - List<String> questionMatchingUniverseIds = questionsIndexationService.searchQuestion("universe"); + searchBean.setKeywords(Arrays.asList("universe")); + List<String> questionMatchingUniverseIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(2, questionMatchingUniverseIds.size()); Assert.assertTrue(questionMatchingUniverseIds.contains(questionThreeId)); Assert.assertTrue(questionMatchingUniverseIds.contains(questionOneId)); - List<String> documentMatchingUnknownIds = questionsIndexationService.searchQuestion("Unknown"); + searchBean.setKeywords(Arrays.asList("universe", "someone")); + List<String> questionMatchingUniverseAndSomeoneIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertEquals(1, questionMatchingUniverseAndSomeoneIds.size()); + Assert.assertEquals(questionThreeId, questionMatchingUniverseAndSomeoneIds.get(0)); + + searchBean.setKeywords(Arrays.asList("Unknown")); + List<String> documentMatchingUnknownIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertTrue(documentMatchingUnknownIds.isEmpty()); - List<String> documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion("Something blue"); + searchBean.setKeywords(Arrays.asList("Something blue")); + List<String> documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, documentMatchingPartOfSummaryIds.size()); Assert.assertEquals(questionThreeId, documentMatchingPartOfSummaryIds.get(0)); - documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion("Something borrowed,"); + searchBean.setKeywords(Arrays.asList("Something borrowed,")); + documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, documentMatchingPartOfSummaryIds.size()); Assert.assertEquals(questionThreeId, documentMatchingPartOfSummaryIds.get(0)); - documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion("can we"); + searchBean.setKeywords(Arrays.asList("can we")); + documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, documentMatchingPartOfSummaryIds.size()); Assert.assertEquals(questionOneId, documentMatchingPartOfSummaryIds.get(0)); + searchBean.setKeywords(null); + searchBean.setPrivacy(Privacy.PUBLIC.name()); + List<String> documentMatchingPrivacyIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertEquals(2, documentMatchingPrivacyIds.size()); + Assert.assertTrue(documentMatchingPrivacyIds.contains(questionOneId)); + Assert.assertTrue(documentMatchingPrivacyIds.contains(questionTwoId)); + + searchBean.setKeywords(null); + searchBean.setPrivacy(Privacy.PRIVATE.name()); + documentMatchingPrivacyIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertEquals(1, documentMatchingPrivacyIds.size()); + Assert.assertEquals(questionThreeId, documentMatchingPrivacyIds.get(0)); + + searchBean.setKeywords(Arrays.asList("Universe")); + searchBean.setPrivacy(Privacy.PUBLIC.name()); + List<String> documentMatchingPrivacyAndUniverseKeywordIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertEquals(1, documentMatchingPrivacyAndUniverseKeywordIds.size()); + Assert.assertEquals(questionOneId, documentMatchingPrivacyAndUniverseKeywordIds.get(0)); + + searchBean.setKeywords(Arrays.asList("Something blue")); + searchBean.setPrivacy(Privacy.PRIVATE.name()); + List<String> documentMatchingPrivacyAndPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertEquals(1, documentMatchingPrivacyAndPartOfSummaryIds.size()); + Assert.assertEquals(questionThreeId, documentMatchingPrivacyAndPartOfSummaryIds.get(0)); + + searchBean.setKeywords(Arrays.asList("Something blue")); + searchBean.setPrivacy(Privacy.PUBLIC.name()); + documentMatchingPrivacyAndPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertTrue(documentMatchingPrivacyAndPartOfSummaryIds.isEmpty()); + } @Test @@ -168,18 +214,29 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest questionsIndexationService.indexQuestion(questionTwo); questionOne.setTitle("How old is the doctor who ?"); + questionOne.setPrivacy(Privacy.PRIVATE.name()); questionsIndexationService.indexQuestion(questionOne); // Ok, let's search now ! - List<String> questionMatchingTitleIds = questionsIndexationService.searchQuestion("awesome"); + QuestionSearchBean searchBean = new QuestionSearchBean(); + searchBean.setKeywords(Arrays.asList("awesome")); + List<String> questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertTrue(questionMatchingTitleIds.isEmpty()); - questionMatchingTitleIds = questionsIndexationService.searchQuestion("who"); + searchBean.setKeywords(Arrays.asList("who")); + questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(2, questionMatchingTitleIds.size()); Assert.assertTrue(questionMatchingTitleIds.contains(questionOneId)); Assert.assertTrue(questionMatchingTitleIds.contains(questionTwoId)); - questionMatchingTitleIds = questionsIndexationService.searchQuestion("doctor"); + searchBean.setKeywords(Arrays.asList("doctor")); + questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); + Assert.assertEquals(1, questionMatchingTitleIds.size()); + Assert.assertTrue(questionMatchingTitleIds.contains(questionOneId)); + + searchBean.setKeywords(null); + searchBean.setPrivacy(Privacy.PRIVATE.name()); + questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingTitleIds.size()); Assert.assertTrue(questionMatchingTitleIds.contains(questionOneId)); @@ -220,15 +277,19 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest questionsIndexationService.indexQuestion(questionTwo); // Ok, let's search now ! - List<String> questionMatchingTitleIds = questionsIndexationService.searchQuestion("awesome"); + QuestionSearchBean searchBean = new QuestionSearchBean(); + searchBean.setKeywords(Arrays.asList("awesome")); + List<String> questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingTitleIds.size()); Assert.assertEquals(questionOneId, questionMatchingTitleIds.get(0)); - questionMatchingTitleIds = questionsIndexationService.searchQuestion("missing"); + searchBean.setKeywords(Arrays.asList("missing")); + questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingTitleIds.size()); Assert.assertEquals(questionTwoId, questionMatchingTitleIds.get(0)); - List<String> questionMatchingUniverseIds = questionsIndexationService.searchQuestion("universe"); + searchBean.setKeywords(Arrays.asList("universe")); + List<String> questionMatchingUniverseIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(2, questionMatchingUniverseIds.size()); Assert.assertTrue(questionMatchingUniverseIds.contains(questionOneId)); Assert.assertTrue(questionMatchingUniverseIds.contains(questionTwoId)); @@ -237,22 +298,27 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest questionsIndexationService.deleteQuestion(questionOneId); // and let's search same now ! - questionMatchingTitleIds = questionsIndexationService.searchQuestion("awesome"); + searchBean.setKeywords(Arrays.asList("awesome")); + questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertTrue(questionMatchingTitleIds.isEmpty()); - questionMatchingTitleIds = questionsIndexationService.searchQuestion("missing"); + searchBean.setKeywords(Arrays.asList("missing")); + questionMatchingTitleIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingTitleIds.size()); Assert.assertTrue(questionMatchingTitleIds.contains(questionTwoId)); - questionMatchingUniverseIds = questionsIndexationService.searchQuestion("universe"); + searchBean.setKeywords(Arrays.asList("universe")); + questionMatchingUniverseIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, questionMatchingUniverseIds.size()); Assert.assertTrue(questionMatchingUniverseIds.contains(questionTwoId)); - List<String> documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion("can we"); + searchBean.setKeywords(Arrays.asList("can we")); + List<String> documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertTrue(documentMatchingPartOfSummaryIds.isEmpty()); - documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion("Something blue"); + searchBean.setKeywords(Arrays.asList("Something blue")); + documentMatchingPartOfSummaryIds = questionsIndexationService.searchQuestion(searchBean); Assert.assertEquals(1, documentMatchingPartOfSummaryIds.size()); Assert.assertEquals(questionTwoId, documentMatchingPartOfSummaryIds.get(0)); -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 fbea68405a181f596388b98595791a540c166d62 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 14:50:19 2015 +0100 integrate index search for question --- .../fr/ifremer/coselmar/persistence/DaoUtils.java | 15 ++++ .../persistence/entity/QuestionTopiaDao.java | 83 ++++++++++++++++++-- .../coselmar/services/v1/QuestionsWebService.java | 88 +++++++++++----------- .../coselmar/services/QuestionsWebServiceTest.java | 4 +- 4 files changed, 136 insertions(+), 54 deletions(-) diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/DaoUtils.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/DaoUtils.java index 2435ab7..b6fa567 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/DaoUtils.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/DaoUtils.java @@ -128,11 +128,26 @@ public class DaoUtils { return result; } + protected static String getQueryForAttributeIn(String entityAlias, String entityAttributeName, Map<String, Object> args, Object value, String operator) { + String result = ""; + + String alias = StringUtils.isBlank(entityAlias) ? "" : entityAlias + "."; + String queryAttributeName = addQueryAttribute(args, entityAttributeName, value); + result += String.format(" %s %s in ( :%s )", operator, alias + entityAttributeName, queryAttributeName); + + return result; + } + public static String andAttributeContains(String entityAlias, String entityAttributeName, Map<String, Object> args, Object value) { String result = getQueryForAttributeContains(entityAlias, entityAttributeName, args, value, "AND"); return result; } + public static String andAttributeIn(String entityAlias, String entityAttributeName, Map<String, Object> args, Object value) { + String result = getQueryForAttributeIn(entityAlias, entityAttributeName, args, value, "AND"); + return result; + } + public static String orAttributeContains(String entityAlias, String entityAttributeName, Map<String, Object> args, Object value) { String result = getQueryForAttributeContains(entityAlias, entityAttributeName, args, value, "OR"); return result; diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java index d1d1e71..389bfc9 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java @@ -31,6 +31,7 @@ import java.util.Map; import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.persistence.DaoUtils; import org.apache.commons.lang3.StringUtils; +import org.nuiton.topia.persistence.TopiaQueryBuilderAddCriteriaOrRunQueryStep; public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { @@ -49,15 +50,73 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { hqlBuilder.append(" OR (" + privateCondition); - String userCondition = DaoUtils.andAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); + String participantCondition = DaoUtils.andAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); + hqlBuilder.append(participantCondition); - hqlBuilder.append(userCondition + ") )"); + String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_CLIENTS, args, expert); - if (searchBean != null) { - String finerHql = refineSearch(searchBean, "Q", args); - hqlBuilder.append(" AND (" + finerHql + ")" ); - } + hqlBuilder.append(clientCondition + ") )"); + + String finerHql = refineSearch(searchBean, "Q", args); + hqlBuilder.append(" AND (" + finerHql + ")" ); + + + List<Question> questions = forHql(hqlBuilder.toString(), args).findAll(); + + return questions; + } + + public List<Question> findForExpert(CoselmarUser expert) { + + StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q " + + " INNER JOIN Q." + Question.PROPERTY_PARTICIPANTS + " CUG "); + + Map<String, Object> args = new HashMap<>(); + + String publicCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PUBLIC, ""); + + hqlBuilder.append(" WHERE ( (" + publicCondition + " ) "); + + String privateCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PRIVATE, ""); + + hqlBuilder.append(" OR (" + privateCondition); + + String participantCondition = DaoUtils.andAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); + hqlBuilder.append(participantCondition); + + String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_CLIENTS, args, expert); + + hqlBuilder.append(clientCondition + ") )"); + List<Question> questions = forHql(hqlBuilder.toString(), args).findAll(); + + return questions; + } + + public List<Question> findForExpert(CoselmarUser expert, List<String> topiaIds) { + + StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q " + + " INNER JOIN Q." + Question.PROPERTY_PARTICIPANTS + " CUG "); + + Map<String, Object> args = new HashMap<>(); + + String publicCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PUBLIC, ""); + + hqlBuilder.append(" WHERE ( (" + publicCondition + " ) "); + + String privateCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PRIVATE, ""); + + hqlBuilder.append(" OR (" + privateCondition); + + String participantCondition = DaoUtils.andAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); + hqlBuilder.append(participantCondition); + + String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_CLIENTS, args, topiaIds); + + hqlBuilder.append(clientCondition + ") )"); + + String topiaIdsCondition = DaoUtils.andAttributeIn("Q", Question.PROPERTY_TOPIA_ID, args, topiaIds); + hqlBuilder.append(topiaIdsCondition); List<Question> questions = forHql(hqlBuilder.toString(), args).findAll(); @@ -70,7 +129,7 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { Map<String, Object> args = new HashMap<>(); - String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_PRIVACY, args, Privacy.PUBLIC); + String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_CLIENTS, args, client); hqlBuilder.append(" WHERE 1=1 AND (" + clientCondition + " ) "); @@ -84,6 +143,16 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { return questions; } + public List<Question> findForClient(CoselmarUser client, List<String> topiaIds) { + + TopiaQueryBuilderAddCriteriaOrRunQueryStep<Question> queryBuilder = forTopiaIdIn(topiaIds); + queryBuilder.addContains(Question.PROPERTY_CLIENTS, client); + + List<Question> questions = queryBuilder.findAll(); + + return questions; + } + public List<Question> findWithSearchBean(QuestionSearchBean searchBean) { StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q "); diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java index 05ce72e..5092a89 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java @@ -58,7 +58,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.lucene.queryparser.classic.ParseException; import org.nuiton.topia.persistence.TopiaIdFactory; import org.nuiton.topia.persistence.TopiaNoResultException; -import org.nuiton.topia.persistence.TopiaQueryBuilderAddCriteriaOrRunQueryStep; /** * @author ymartel <martel@codelutin.com> @@ -840,7 +839,7 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { questionList = getQuestionDao().forPrivacyEquals(Privacy.PUBLIC).findAll(); } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.EXPERT.name(), currentUserRole)) { - questionList = getQuestionDao().findForExpert(currentUser, null); + questionList = getQuestionDao().findForExpert(currentUser); } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.CLIENT.name(), currentUserRole)) { questionList = getQuestionDao().forClientsContains(currentUser).findAll(); @@ -874,30 +873,30 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { String currentUserRole = currentUser.getRole().name(); QuestionsIndexationService questionsIndexationService = getServicesContext().newService(QuestionsIndexationService.class); + //Try to retrieve corresponding questionIds from the index if searchBean given + List<String> fromIndexQuestionIds = null; + if (searchBean != null) { + try { + List<String> questionIds = questionsIndexationService.searchQuestion(searchBean); + fromIndexQuestionIds = getQuestionsFullId(questionIds); + + } catch (IOException |ParseException e) { + if (log.isErrorEnabled()) { + log.error("Unable to search by lucene, make search directly in database", e); + } + } + + // classical search with DAO + } + List<Question> questionList; + + // Supervisor or Admin : access to all questions, no restriction if (StringUtils.equalsIgnoreCase(CoselmarUserRole.ADMIN.name(), currentUserRole) || StringUtils.equalsIgnoreCase(CoselmarUserRole.SUPERVISOR.name(), currentUserRole)) { - if (searchBean != null && searchBean.getKeywords() != null && !searchBean.getKeywords().isEmpty()) { - try { - //TODO martel 20150106 : maybe its should be better if privacy and status are indexed too ? - List<String> questionIds = questionsIndexationService.searchQuestion(searchBean); - List<String> questionFullIds = getQuestionsFullId(questionIds); - TopiaQueryBuilderAddCriteriaOrRunQueryStep<Question> queryBuilder = getQuestionDao().forTopiaIdIn(questionFullIds); - if (StringUtils.isNotBlank(searchBean.getPrivacy())) { - queryBuilder.addEquals(Question.PROPERTY_PRIVACY, Privacy.valueOf(searchBean.getPrivacy().toUpperCase())); - } - if (StringUtils.isNotBlank(searchBean.getStatus())) { - queryBuilder.addEquals(Question.PROPERTY_STATUS, Privacy.valueOf(searchBean.getStatus().toUpperCase())); - } - questionList = queryBuilder.findAll(); - - } catch (IOException |ParseException e) { - if (log.isErrorEnabled()) { - log.error("Unable to search by lucene, make search directly in database", e); - } - questionList = getQuestionDao().findWithSearchBean(searchBean); - } + if (fromIndexQuestionIds != null && !fromIndexQuestionIds.isEmpty()) { + questionList = getQuestionDao().forTopiaIdIn(fromIndexQuestionIds).findAll(); // classical search with DAO } else { @@ -905,43 +904,42 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { } + // Member : access to public question only } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.MEMBER.name(), currentUserRole)) { + if (StringUtils.equals(searchBean.getPrivacy(), Privacy.PRIVATE.name())) { + // Asking for private question ? directly no result ! questionList = new ArrayList<>(0); + } else { - if (searchBean != null && searchBean.getKeywords() != null && !searchBean.getKeywords().isEmpty()) { - try { - //TODO martel 20150106 : maybe its should be better if privacy and status are indexed too ? - List<String> questionIds = questionsIndexationService.searchQuestion(searchBean); - List<String> questionFullIds = getQuestionsFullId(questionIds); - TopiaQueryBuilderAddCriteriaOrRunQueryStep<Question> queryBuilder = getQuestionDao().forTopiaIdIn(questionFullIds); - if (StringUtils.isNotBlank(searchBean.getPrivacy())) { - queryBuilder.addEquals(Question.PROPERTY_PRIVACY, Privacy.valueOf(searchBean.getPrivacy().toUpperCase())); - } - if (StringUtils.isNotBlank(searchBean.getStatus())) { - queryBuilder.addEquals(Question.PROPERTY_STATUS, Privacy.valueOf(searchBean.getStatus().toUpperCase())); - } - questionList = queryBuilder.findAll(); - - } catch (IOException |ParseException e) { - if (log.isErrorEnabled()) { - log.error("Unable to search by lucene, make search directly in database", e); - } - questionList = getQuestionDao().findWithSearchBean(searchBean); - } + if (fromIndexQuestionIds != null && !fromIndexQuestionIds.isEmpty()) { + questionList = getQuestionDao().forTopiaIdIn(fromIndexQuestionIds).findAll(); - // classical search with DAO + // classical search with DAO } else { questionList = getQuestionDao().findWithSearchBean(searchBean); } } + // Expert : access to all public question and private question if he is participant or client } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.EXPERT.name(), currentUserRole)) { - questionList = getQuestionDao().findForExpert(currentUser, searchBean); + if (fromIndexQuestionIds != null && !fromIndexQuestionIds.isEmpty()) { + questionList = getQuestionDao().findForExpert(currentUser, fromIndexQuestionIds); + + } else { + questionList = getQuestionDao().findForExpert(currentUser, searchBean); + } + // Client : access to question he is client } else if (StringUtils.equalsIgnoreCase(CoselmarUserRole.CLIENT.name(), currentUserRole)) { - questionList = getQuestionDao().findForClient(currentUser, searchBean); + if (fromIndexQuestionIds != null && !fromIndexQuestionIds.isEmpty()) { + questionList = getQuestionDao().findForClient(currentUser, fromIndexQuestionIds); + + } else { + questionList = getQuestionDao().findForClient(currentUser, searchBean); + } + } else { String message = "Not allowed to access this page"; if (log.isWarnEnabled()) { diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java index f315b76..f47edc7 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java @@ -112,7 +112,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { "{title: '" + documentThreeTitle + "'," + " summary: 'This question is private, zero access for others'," + " submissionDate: '" + (new Date()).getTime() + "'," - + " themes: ['Universe', 'test'], type: 'question'," + + " themes: ['Universe', 'test', 'zero'], type: 'question'," + " privacy : 'PRIVATE' }") .Post() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -286,7 +286,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Eleventh search : as expert, searchBean with pandorica keyword : retrieve nothing (documentThree is private) searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{keywords : ['pandorica']}") + .addParameter("searchBean", "{keywords : ['zero']}") .Get() .addHeader("Authorization", "Bearer " + expertToken); -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 84a054359d6e29d3b48b116b56393e17f0772629 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 17:10:50 2015 +0100 add search by keywords for questions --- .../persistence/entity/QuestionTopiaDao.java | 13 +++++++++---- .../coselmar/services/v1/QuestionsWebService.java | 9 ++++++--- .../coselmar/services/QuestionsWebServiceTest.java | 22 +++++++++++----------- .../src/main/webapp/js/coselmar-controllers.js | 20 +++++++++++++++----- .../main/webapp/js/coselmar-questions-services.js | 14 ++++++++++++-- .../views/questions/modalQuestionSearch.html | 20 ++++++++++---------- .../src/main/webapp/views/questions/questions.html | 8 ++++++++ 7 files changed, 71 insertions(+), 35 deletions(-) diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java index 389bfc9..23ff184 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java @@ -31,10 +31,15 @@ import java.util.Map; import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.persistence.DaoUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; import org.nuiton.topia.persistence.TopiaQueryBuilderAddCriteriaOrRunQueryStep; +import static org.apache.commons.logging.LogFactory.getLog; + public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { + private static final Log log = getLog(QuestionTopiaDao.class); + public List<Question> findForExpert(CoselmarUser expert, QuestionSearchBean searchBean) { StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q " @@ -106,14 +111,14 @@ public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { String privateCondition = DaoUtils.getQueryForAttributeEquals("Q", Question.PROPERTY_PRIVACY, args, Privacy.PRIVATE, ""); - hqlBuilder.append(" OR (" + privateCondition); + hqlBuilder.append(" OR (" + privateCondition + " AND ( 0 = 1 "); - String participantCondition = DaoUtils.andAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); + String participantCondition = DaoUtils.orAttributeContains("CUG", CoselmarUserGroup.PROPERTY_MEMBERS, args, expert); hqlBuilder.append(participantCondition); - String clientCondition = DaoUtils.andAttributeContains("Q", Question.PROPERTY_CLIENTS, args, topiaIds); + String clientCondition = DaoUtils.orAttributeContains("Q", Question.PROPERTY_CLIENTS, args, expert); - hqlBuilder.append(clientCondition + ") )"); + hqlBuilder.append(clientCondition + ") ) )"); String topiaIdsCondition = DaoUtils.andAttributeIn("Q", Question.PROPERTY_TOPIA_ID, args, topiaIds); hqlBuilder.append(topiaIdsCondition); diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java index 5092a89..1fa4241 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java @@ -220,7 +220,7 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { } } - public List<QuestionBean> getQuestions(QuestionSearchBean searchBean) throws InvalidCredentialException, UnauthorizedException { + public List<QuestionBean> getQuestions(QuestionSearchBean searchOption) throws InvalidCredentialException, UnauthorizedException { // Check authentication String authorization = getContext().getHeader("Authorization"); @@ -234,8 +234,8 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { List<Question> questionList; - if (searchBean != null) { - questionList = getAllFilteredQuestions(currentUser, searchBean); + if (searchOption != null) { + questionList = getAllFilteredQuestions(currentUser, searchOption); } else { questionList = getAllQuestions(currentUser); @@ -879,6 +879,9 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { try { List<String> questionIds = questionsIndexationService.searchQuestion(searchBean); fromIndexQuestionIds = getQuestionsFullId(questionIds); + if (log.isInfoEnabled()) { + log.info("Found " + fromIndexQuestionIds.size() + " questions from index."); + } } catch (IOException |ParseException e) { if (log.isErrorEnabled()) { diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java index f47edc7..d579413 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/QuestionsWebServiceTest.java @@ -122,7 +122,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // First search : as supervisor, no search bean : retrieve the three documents Request searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{}") + .addParameter("searchOption", "{}") .Get() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -139,7 +139,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Second search : as supervisor, searchBean only with public privacy : retrieve two documents (one and two) searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{privacy : 'public'}") + .addParameter("searchOption", "{privacy : 'public'}") .Get() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -155,7 +155,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Third search : as supervisor, searchBean with public privacy and test keywords : retrieve documentOne searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{privacy : 'public', keywords : ['test']}") + .addParameter("searchOption", "{privacy : 'public', keywords : ['test']}") .Get() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -169,7 +169,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Fourth search : as supervisor, searchBean with test and pandorica keywords : retrieve nothing searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{privacy : 'public', keywords : ['test', 'pandorica']}") + .addParameter("searchOption", "{privacy : 'public', keywords : ['test', 'pandorica']}") .Get() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -182,7 +182,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Fifth search : as supervisor, searchBean with test keyword : retrieve documentOne and documentThree searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{keywords : ['test']}") + .addParameter("searchOption", "{keywords : ['test']}") .Get() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -198,7 +198,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Sixth search : as supervisor, searchBean with test and universe keywords : retrieve documentThree searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{keywords : ['test', 'Universe']}") + .addParameter("searchOption", "{keywords : ['test', 'Universe']}") .Get() .addHeader("Authorization", "Bearer " + supervisorToken); @@ -228,7 +228,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Seventh search : as expert, no search bean : retrieve the two public documents searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{}") + .addParameter("searchOption", "{}") .Get() .addHeader("Authorization", "Bearer " + expertToken); @@ -243,7 +243,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Eighth search : as expert, searchBean only with public privacy : retrieve two documents (one and two) (same as seventh) searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{privacy : 'public'}") + .addParameter("searchOption", "{privacy : 'public'}") .Get() .addHeader("Authorization", "Bearer " + expertToken); @@ -259,7 +259,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Ninth search : as expert, searchBean with public privacy and test keywords : retrieve documentOne searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{privacy : 'public', keywords : ['test']}") + .addParameter("searchOption", "{privacy : 'public', keywords : ['test']}") .Get() .addHeader("Authorization", "Bearer " + expertToken); @@ -273,7 +273,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Tenth search : as supervisor, searchBean with test and pandorica keywords : retrieve nothing searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{privacy : 'public', keywords : ['test', 'pandorica']}") + .addParameter("searchOption", "{privacy : 'public', keywords : ['test', 'pandorica']}") .Get() .addHeader("Authorization", "Bearer " + expertToken); @@ -286,7 +286,7 @@ public class QuestionsWebServiceTest extends AbstractCoselmarWebServiceTest { // Eleventh search : as expert, searchBean with pandorica keyword : retrieve nothing (documentThree is private) searchRequest = createRequest("/v1/questions") - .addParameter("searchBean", "{keywords : ['zero']}") + .addParameter("searchOption", "{keywords : ['zero']}") .Get() .addHeader("Authorization", "Bearer " + expertToken); diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 1e8572e..f2402cc 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -352,7 +352,9 @@ coselmarControllers.controller("UserViewCtrl", coselmarControllers.controller("QuestionsCtrl", ['$scope', '$route', '$routeParams', '$location', 'questionsService', function($scope, $route, $routeParams, $location, questionsService){ - questionsService.getQuestions(function(questions) { + $scope.searchOptions = { 'privacy' : '', 'status' : '', 'keywords' : []}; + + questionsService.getQuestions($scope.searchOptions, function(questions) { // success : just get the questions $scope.questions = questions; @@ -361,6 +363,13 @@ coselmarControllers.controller("QuestionsCtrl", ['$scope', '$route', '$routePara console.log(error); }); + $scope.searchQuestions = function() { + questionsService.getQuestions($scope.searchOptions, function(questions) { + $scope.questions = questions; + }); + + }; + $scope.deleteQuestion = function(questionId) { @@ -787,19 +796,20 @@ coselmarControllers.controller('ModalSearchDocumentsCtrl', function ($scope, $mo coselmarControllers.controller('ModalSearchQuestionsCtrl', function ($scope, $modalInstance, currentQuestionId, questionsService) { $scope.searchKeywords = []; + $scope.searchOptions = { 'privacy' : '', 'status' : '', 'keywords' : []}; $scope.currentQuestionId = currentQuestionId; - questionsService.getQuestions(function(questions) { + questionsService.getQuestions($scope.searchOptions, function(questions) { $scope.questions = questions; }); $scope.searchQuestions = function(searchKeywords) { - $scope.searchKeywords = searchKeywords; - questionsService.getQuestions(function(questions) { + $scope.searchOptions = { 'privacy' : '', 'status' : '', 'keywords' : searchKeywords}; + questionsService.getQuestions($scope.searchOptions, function(questions) { $scope.questions = questions; }); - } + }; $scope.select = function (question) { $modalInstance.close(question); diff --git a/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js b/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js index db19e3f..31a05ff 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-questions-services.js @@ -61,8 +61,18 @@ function Question(resource, config){ }; - this.getQuestions = function(successFunction, failFunction) { - var questionResource = resource(baseURL); + this.getQuestions = function(searchOptions, successFunction, failFunction) { + + if (searchOptions.privacy != "private" && searchOptions.privacy != "public" ) { + searchOptions.privacy = undefined; + } + + if (searchOptions.status != "open" && searchOptions.status != "close" + && searchOptions.status != "in_progress" && searchOptions.status != "adjourned") { + searchOptions.status = undefined; + } + + var questionResource = resource(baseURL, {'searchOption' : searchOptions}); questionResource.query().$promise.then(successFunction, failFunction); }; diff --git a/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html b/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html index 3b86fad..ba1a24b 100644 --- a/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html +++ b/coselmar-ui/src/main/webapp/views/questions/modalQuestionSearch.html @@ -7,16 +7,16 @@ </div> <div> - <!--<div>--> - <!--<form class="form-inline pull-right" role="questionOptions" ng-submit="searchQuestions(searchKeywords)">--> - <!--<div class="form-group">--> - <!--<input type="search" class="form-control" placeholder="word1,word2,..." ng-model="searchKeywords" ng-list />--> - <!--</div>--> - <!--<div class="form-group">--> - <!--<button type="submit" class="btn btn-default fa fa-search"></button>--> - <!--</div>--> - <!--</form>--> - <!--</div>--> + <div> + <form class="form-inline pull-right" role="questionOptions" ng-submit="searchQuestions(searchKeywords)"> + <div class="form-group"> + <input type="search" class="form-control" placeholder="word1,word2,..." ng-model="searchKeywords" ng-list /> + </div> + <div class="form-group"> + <button type="submit" class="btn btn-default fa fa-search"></button> + </div> + </form> + </div> <br/> <table class="table"> <tr> diff --git a/coselmar-ui/src/main/webapp/views/questions/questions.html b/coselmar-ui/src/main/webapp/views/questions/questions.html index 484dfac..4e0e31f 100644 --- a/coselmar-ui/src/main/webapp/views/questions/questions.html +++ b/coselmar-ui/src/main/webapp/views/questions/questions.html @@ -35,6 +35,14 @@ <div class="form-group" ng-if="currentUser.role == 'SUPERVISOR'"> <a href="#/questions/new" class="form-inline navbar-left btn btn-primary">Add a Question</a> </div> + <form class="form-inline pull-right" role="questionOptions" ng-submit="searchQuestions()"> + <div class="form-group"> + <input type="search" class="form-control" placeholder="word1,word2,..." ng-model="searchOptions.keywords" ng-list /> + </div> + <div class="form-group"> + <button type="submit" class="btn btn-default fa fa-search"></button> + </div> + </form> </div> <br/> <table class="table"> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 66e7792a49e881b7ba34af2b216a096895512379 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 17:34:35 2015 +0100 fix dependencies --- coselmar-persistence/pom.xml | 16 ---------------- .../coselmar/persistence/entity/QuestionTopiaDao.java | 5 ----- coselmar-rest/pom.xml | 4 ---- 3 files changed, 25 deletions(-) diff --git a/coselmar-persistence/pom.xml b/coselmar-persistence/pom.xml index 1bddff9..c19d4cd 100644 --- a/coselmar-persistence/pom.xml +++ b/coselmar-persistence/pom.xml @@ -61,22 +61,6 @@ <artifactId>hibernate-core</artifactId> </dependency> - <!-- Tests --> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-jcl</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>log4j</groupId> - <artifactId>log4j</artifactId> - <scope>test</scope> - </dependency> - </dependencies> <build> diff --git a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java index 23ff184..2152adf 100644 --- a/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java +++ b/coselmar-persistence/src/main/java/fr/ifremer/coselmar/persistence/entity/QuestionTopiaDao.java @@ -31,15 +31,10 @@ import java.util.Map; import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.persistence.DaoUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; import org.nuiton.topia.persistence.TopiaQueryBuilderAddCriteriaOrRunQueryStep; -import static org.apache.commons.logging.LogFactory.getLog; - public class QuestionTopiaDao extends AbstractQuestionTopiaDao<Question> { - private static final Log log = getLog(QuestionTopiaDao.class); - public List<Question> findForExpert(CoselmarUser expert, QuestionSearchBean searchBean) { StringBuilder hqlBuilder = new StringBuilder("SELECT Q FROM " + Question.class.getName() + " Q " diff --git a/coselmar-rest/pom.xml b/coselmar-rest/pom.xml index ef22ee3..e9cc316 100644 --- a/coselmar-rest/pom.xml +++ b/coselmar-rest/pom.xml @@ -159,10 +159,6 @@ </dependency> <dependency> <groupId>org.apache.lucene</groupId> - <artifactId>lucene-queries</artifactId> - </dependency> - <dependency> - <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> </dependency> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 63852895497e96511ebb1eb13b01e7c967fe0a03 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 18:31:43 2015 +0100 add question parents and children in ui --- .../coselmar/converter/BeanEntityConverter.java | 2 +- .../coselmar/services/v1/QuestionsWebService.java | 13 +++++++-- .../views/questions/viewRestrictedQuestion.html | 32 ++++++++++++++++++---- .../main/webapp/views/questions/viewquestion.html | 28 ++++++++++++++++--- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java index 950ca54..3ae8e53 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/converter/BeanEntityConverter.java @@ -189,7 +189,7 @@ public class BeanEntityConverter { Collection<Question> parents = question.getParents(); if (parents != null && !parents.isEmpty()) { for (Question parent : parents) { - QuestionBean questionBean = toBean(idFactory, parent); + QuestionBean questionBean = toLightBean(idFactory, parent); result.addParent(questionBean); } } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java index 1fa4241..b2614b0 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/QuestionsWebService.java @@ -373,7 +373,13 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { } - QuestionBean result = BeanEntityConverter.toBean(getPersistenceContext().getTopiaIdFactory(), question);; + QuestionBean result = BeanEntityConverter.toBean(getPersistenceContext().getTopiaIdFactory(), question); + //manager child + List<Question> children = getQuestionDao().forParentsContains(question).findAll(); + for (Question child : children) { + QuestionBean childBean = BeanEntityConverter.toLightBean(getPersistenceContext().getTopiaIdFactory(), child); + result.addChild(childBean); + } // Client is not allowed to see documents if (CoselmarUserRole.CLIENT == currentUser.getRole() @@ -396,7 +402,10 @@ public class QuestionsWebService extends CoselmarWebServiceSupport { for (Question parent : question.getParents()) { result.addParent(BeanEntityConverter.toLightBean(getPersistenceContext().getTopiaIdFactory(), parent)); } - //TODO ymartel : manager children + for (Question child : children) { + QuestionBean childBean = BeanEntityConverter.toLightBean(getPersistenceContext().getTopiaIdFactory(), child); + result.addChild(childBean); + } } } diff --git a/coselmar-ui/src/main/webapp/views/questions/viewRestrictedQuestion.html b/coselmar-ui/src/main/webapp/views/questions/viewRestrictedQuestion.html index 1802bbe..a2e31cb 100644 --- a/coselmar-ui/src/main/webapp/views/questions/viewRestrictedQuestion.html +++ b/coselmar-ui/src/main/webapp/views/questions/viewRestrictedQuestion.html @@ -24,13 +24,35 @@ <div class=""> - Cette question n'est pas disponible à la consultation. + <div class="form-group col-md-12"> + <h4>Cette question n'est pas disponible à la consultation.</h4> + </div> - <!-- TODO ymartel 20141208 : manage parents and children <div class="form-group col-md-12"> - <div class="col-md-6">TODO Parents</div> - <div class="col-md-6">TODO Children</div> + <div class="col-md-6"> + <dl> + <dt>Produced or inspired by</dt> + <dd> + <ul> + <li ng-repeat="parent in question.parents"> + <a href="#/questions/{{parent.id}}" target="_blank">{{parent.title}}</a> + </li> + </ul> + </dd> + </dl> + </div> + <div class="col-md-6"> + <dl> + <dt>Has produced or inspired</dt> + <dd> + <ul> + <li ng-repeat="child in question.children"> + <a href="#/questions/{{child.id}}" target="_blank">{{child.title}}</a> + </li> + </ul> + </dd> + </dl> + </div> </div> - --> </div> \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/questions/viewquestion.html b/coselmar-ui/src/main/webapp/views/questions/viewquestion.html index 2962379..99dbcae 100644 --- a/coselmar-ui/src/main/webapp/views/questions/viewquestion.html +++ b/coselmar-ui/src/main/webapp/views/questions/viewquestion.html @@ -201,11 +201,31 @@ </div> - <!-- TODO ymartel 20141208 : manage parents and children <div class="form-group col-md-12"> - <div class="col-md-6">TODO Parents</div> - <div class="col-md-6">TODO Children</div> + <div class="col-md-6"> + <dl> + <dt>Produced or inspired by</dt> + <dd> + <ul> + <li ng-repeat="parent in question.parents"> + <a href="#/questions/{{parent.id}}" target="_blank">{{parent.title}}</a> + </li> + </ul> + </dd> + </dl> + </div> + <div class="col-md-6"> + <dl> + <dt>Has produced or inspired</dt> + <dd> + <ul> + <li ng-repeat="child in question.children"> + <a href="#/questions/{{child.id}}" target="_blank">{{child.title}}</a> + </li> + </ul> + </dd> + </dl> + </div> </div> - --> </div> \ No newline at end of file -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
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 6a6e52797ce0a96db09d42b9894d3f7eb7983c28 Merge: 51a0ca3 6385289 Author: Yannick Martel <martel@©odelutin.com> Date: Thu Jan 8 18:31:52 2015 +0100 fixes #6285 : Merge branch 'feature/6285-manage-relation-between-questions' into develop coselmar-persistence/pom.xml | 16 -- .../ifremer/coselmar/beans/QuestionSearchBean.java | 40 +++ .../fr/ifremer/coselmar/persistence/DaoUtils.java | 15 ++ .../persistence/entity/QuestionTopiaDao.java | 162 ++++++++++- coselmar-rest/pom.xml | 4 - .../coselmar/converter/BeanEntityConverter.java | 2 +- .../services/CoselmarRestApplicationListener.java | 4 +- .../indexation/QuestionsIndexationService.java | 77 ++++-- .../services/v1/InitialisationService.java | 13 + .../coselmar/services/v1/QuestionsWebService.java | 189 +++++++++++-- .../services/FakeCoselmarApplicationContext.java | 8 + .../coselmar/services/QuestionsWebServiceTest.java | 299 +++++++++++++++++++++ .../indexation/QuestionsIndexationServiceTest.java | 106 ++++++-- .../src/main/webapp/js/coselmar-controllers.js | 82 +++++- .../main/webapp/js/coselmar-questions-services.js | 14 +- .../main/webapp/views/questions/editquestion.html | 37 +++ .../views/questions/modalQuestionSearch.html | 48 ++++ .../src/main/webapp/views/questions/questions.html | 8 + .../views/questions/viewRestrictedQuestion.html | 32 ++- .../main/webapp/views/questions/viewquestion.html | 28 +- 20 files changed, 1087 insertions(+), 97 deletions(-) -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
participants (1)
-
codelutin.com scm