1 /*****************************************************************************
5 * Creation date 05.10.2012
8 *****************************************************************************/
10 package org.splat.service;
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.Iterator;
16 import java.util.List;
18 import org.apache.log4j.Logger;
19 import org.apache.lucene.index.IndexWriter;
20 import org.apache.lucene.index.Term;
21 import org.apache.lucene.search.BooleanClause;
22 import org.apache.lucene.search.BooleanFilter;
23 import org.apache.lucene.search.BooleanQuery;
24 import org.apache.lucene.search.FilterClause;
25 import org.apache.lucene.search.IndexSearcher;
26 import org.apache.lucene.search.ScoreDoc;
27 import org.apache.lucene.search.Sort;
28 import org.apache.lucene.search.SortField;
29 import org.apache.lucene.search.TermQuery;
30 import org.apache.lucene.search.TermsFilter;
31 import org.apache.lucene.search.TopFieldDocs;
32 import org.apache.lucene.store.Directory;
33 import org.apache.lucene.store.FSDirectory;
34 import org.hibernate.Criteria;
35 import org.hibernate.Hibernate;
36 import org.hibernate.criterion.DetachedCriteria;
37 import org.hibernate.criterion.Disjunction;
38 import org.hibernate.criterion.Junction;
39 import org.hibernate.criterion.Order;
40 import org.hibernate.criterion.Restrictions;
41 import org.hibernate.type.Type;
42 import org.splat.dal.bo.kernel.User;
43 import org.splat.dal.bo.som.KnowledgeElement;
44 import org.splat.dal.bo.som.ProgressState;
45 import org.splat.dal.bo.som.Scenario;
46 import org.splat.dal.bo.som.SimulationContext;
47 import org.splat.dal.bo.som.Study;
48 import org.splat.dal.bo.som.Visibility;
49 import org.splat.dal.dao.som.KnowledgeElementDAO;
50 import org.splat.dal.dao.som.StudyDAO;
51 import org.splat.service.dto.ImportedStudyDTO;
52 import org.splat.service.dto.KnowledgeSearchFilterDTO;
53 import org.splat.service.dto.Proxy;
54 import org.splat.service.dto.SearchFilterDTO;
55 import org.splat.service.dto.StudyDTO;
56 import org.splat.service.dto.StudySearchFilterDTO;
57 import org.splat.service.technical.IndexService;
58 import org.splat.service.technical.IndexServiceImpl;
59 import org.splat.service.technical.RepositoryService;
60 import org.splat.util.BeanHelper;
61 import org.springframework.transaction.annotation.Transactional;
64 * Search service implementation.
66 * @author <a href="mailto:roman.kozlov@opencascade.com">Roman Kozlov (RKV)</a>
68 public class SearchServiceImpl implements SearchService {
73 public final static Logger LOG = Logger
74 .getLogger(org.splat.service.SearchServiceImpl.class);
77 * Injected repository service.
79 private RepositoryService _repositoryService;
81 * Injected index service.
83 private IndexService _indexService;
85 * Injected study service.
87 private StudyService _studyService;
91 private StudyDAO _studyDAO;
93 * Injected knowledge element DAO.
95 private KnowledgeElementDAO _knowledgeElementDAO;
98 * Get a list of studies which are currently not presented in the lucene index.
100 * @return list of ImportedStudy DTO
102 @Transactional(readOnly = true)
103 public List<ImportedStudyDTO> selectStudies() {
104 List<ImportedStudyDTO> table = new ArrayList<ImportedStudyDTO>();
105 Study.Properties sprop = new Study.Properties();
106 List<Study> dbStudies = getStudyDAO().getAll();
108 for (Study aStudy : dbStudies) {
111 if (selectStudiesWhere(
112 sprop.setReference(aStudy.getReference())).size() != 0) {
113 // If this study was already indexed and found in the lucene index
114 // then skip it to avoid adding to the index it again.
117 } catch (Exception error) {
120 // Add the study to the list of studies which are
121 // currently not presented in the lucene index.
122 table.add(BeanHelper.copyBean(aStudy, ImportedStudyDTO.class));
128 * Refresh lucene index for studies.
131 * list of studies id's
133 @Transactional(readOnly = true)
135 public void reindexStudies(final String[] ridlist) {
136 for (int i = 0; i < ridlist.length; i++) {
137 long index = Long.valueOf(ridlist[i].trim());
138 Study study = getStudyService().selectStudy(index);
146 * @see org.splat.service.SearchService#selectKnowledgeElementsWhere(org.splat.service.dto.KnowledgeSearchFilterDTO)
148 public List<Proxy> selectKnowledgeElementsWhere(
149 final KnowledgeSearchFilterDTO filter) {
150 List<Proxy> result = new ArrayList<Proxy>();
152 // Search matching all criteria
153 DetachedCriteria query = DetachedCriteria.forClass(
154 KnowledgeElement.class, "kelm");
155 query.createAlias("kelm.owner", "scen", Criteria.INNER_JOIN)
156 .createCriteria("scen.owner", "study", Criteria.INNER_JOIN)
157 .add(visibleStudyFilter(filter));
159 // Creation of the query
160 Junction topJunction = initQuery(filter);
162 addByWordsCriteria(topJunction, filter);
164 List<SimulationContext> context = filter.getSimContexts();
165 if (context != null && (!context.isEmpty())) {
166 // Get only studies which have given contexts
167 query.createAlias("study.contex", "ctx", Criteria.INNER_JOIN);
169 if (filter.isMatchAllContexts()) { // AND
170 critctx = Restrictions.conjunction();
172 critctx = Restrictions.disjunction();
174 for (SimulationContext seltext : context) {
175 // (simctxType = seltext.getType() AND simctxValue = seltext.getValue())
176 critctx.add(Restrictions.and(Restrictions.eq("ctx.value",
177 seltext.getValue()), Restrictions.eq("ctx.type",
178 seltext.getType())));
181 topJunction.add(critctx);
184 query.add(topJunction);
186 query.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
187 // Creation of the sort criteria
188 query.addOrder(Order.asc("title"));
190 if (LOG.isInfoEnabled()) {
191 LOG.info("Searching knowledge elements: \"" + query.toString());
195 List<KnowledgeElement> found = getKnowledgeElementDAO()
196 .getFilteredList(query);
198 // Construction of the result list
199 for (KnowledgeElement kelm : found) {
200 result.add(new StudyDTO(kelm.getIndex(), kelm.getReference(), kelm
201 .getProgressState(), kelm.getTitle(), kelm.getAuthor()
208 * Add search criteria for filtering by title contents.
211 * the search condition to be appended
215 private void addByWordsCriteria(final Junction topJunction,
216 final SearchFilterDTO filter) {
217 String title = filter.getWords(); // Title
218 if (title != null && (!title.isEmpty())) {
219 // Look for given words in titles
221 if (filter.isMatchAllCriteria()) { // AND
222 critext = Restrictions.conjunction();
224 critext = Restrictions.disjunction();
227 String[] word = title.split(" ");
228 for (int j = 0; j < word.length; j++) {
229 critext.add(Restrictions.like("title", "%" + word[j] + "%"));
231 topJunction.add(critext);
238 * @see org.splat.service.SearchService#selectStudiesWhere(org.splat.service.dto.StudySearchFilterDTO)
240 public List<Proxy> selectStudiesWhere(final StudySearchFilterDTO filter) {
241 List<Proxy> result = new ArrayList<Proxy>();
243 DetachedCriteria query = DetachedCriteria
244 .forClass(Study.class, "study").add(visibleStudyFilter(filter));
246 // Creation of the query
247 Junction topJunction = initQuery(filter);
249 addByWordsCriteria(topJunction, filter);
251 List<SimulationContext> context = filter.getSimContexts();
252 if (context != null && (!context.isEmpty())) {
253 // Get only studies which have given contexts
254 query.createAlias("contex", "ctx", Criteria.INNER_JOIN);
256 if (filter.isMatchAllContexts()) { // AND
257 critctx = Restrictions.conjunction();
259 critctx = Restrictions.disjunction();
261 for (SimulationContext seltext : context) {
262 // (simctxType = seltext.getType() AND simctxValue = seltext.getValue())
263 critctx.add(Restrictions.and(Restrictions.eq("ctx.value",
264 seltext.getValue()), Restrictions.eq("ctx.type",
265 seltext.getType())));
268 topJunction.add(critctx);
271 query.add(topJunction);
273 query.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
274 // Creation of the sort criteria
275 query.addOrder(Order.asc("title"));
277 if (LOG.isInfoEnabled()) {
278 LOG.info("Searching studies: \"" + query.toString());
282 List<Study> found = getStudyDAO().getFilteredList(query);
284 // Construction of the result list
285 for (Study std : found) {
286 result.add(new StudyDTO(std.getIndex(), std.getReference(), std
287 .getProgressState(), std.getTitle(), std.getAuthor()
294 * Initialize query with base criteria.
298 * @return top junction of the search filter
300 private Junction initQuery(final StudySearchFilterDTO filter) {
301 Junction topJunction;
302 if (filter.isMatchAllCriteria()) { // AND
303 topJunction = Restrictions.conjunction();
305 topJunction = Restrictions.disjunction();
307 if (!SearchFilterDTO.ANY_STATE.equals(filter.getState())) {
308 ProgressState state = ProgressState.valueOf(filter.getState()); // State
310 topJunction.add(Restrictions.eq("state", state));
313 String refid = filter.getReference(); // Reference
314 if (refid != null && !refid.isEmpty()) {
315 topJunction.add(Restrictions.eq("sid", refid));
318 // Filter by creation date
319 addCreationDateCriteria(topJunction, filter, "credate");
320 // Filter by modification date
321 if (filter.getUpdatedAfter() != null) {
322 topJunction.add(Restrictions
323 .gt("lasdate", filter.getUpdatedAfter()));
325 if (filter.getUpdatedBefore() != null) {
326 topJunction.add(Restrictions.lt("lasdate", filter
327 .getUpdatedBefore()));
330 // Filter by study author
331 long authorId = Long.valueOf(filter.getAuthor());
333 topJunction.add(Restrictions.eq("manager.rid", authorId));
340 * Initialize query with base criteria.
344 * @return top junction of the search filter
346 private Junction initQuery(final KnowledgeSearchFilterDTO filter) {
347 Junction topJunction;
348 if (filter.isMatchAllCriteria()) { // AND
349 topJunction = Restrictions.conjunction();
351 topJunction = Restrictions.disjunction();
354 // Filter by knowledge type
355 long ktypeId = Long.valueOf(filter.getKtype());
357 topJunction.add(Restrictions.eq("kelm.type.rid", ktypeId));
360 if (!SearchFilterDTO.ANY_STATE.equals(filter.getState())) {
361 ProgressState state = ProgressState.valueOf(filter.getState()); // State
363 topJunction.add(Restrictions.eq("kelm.state", state));
367 String refid = filter.getReference(); // Reference
368 if (refid != null && !refid.isEmpty()) {
369 long id = Long.valueOf(refid.replaceAll("^KE(0)*", ""));
371 topJunction.add(Restrictions.eq("kelm.rid", id));
375 addCreationDateCriteria(topJunction, filter, "date");
377 // Filter by knowledge author
378 long authorId = Long.valueOf(filter.getAuthor());
380 topJunction.add(Restrictions.eq("kelm.author.rid", authorId));
387 * Filter for visible studies.
391 * @return search condition for studies to get only visible studies
393 private Junction visibleStudyFilter(final SearchFilterDTO filter) {
394 Junction topJunction;
395 if (filter.isMatchAllCriteria()) { // AND
396 topJunction = Restrictions.conjunction();
398 topJunction = Restrictions.disjunction();
401 // Filter by connected user
402 long actorId = filter.getConnectedUserId(); // Contributor, Reviewer or Approver
404 // User is loggen in - show public studies and studies where he is participating
405 Disjunction orCrit = Restrictions.disjunction();
409 /* If the user is a validation cycle participant */
413 + "select vcrel.owner from cycle_rel vcrel "
414 + "inner join cycle vc on vcrel.refer = vc.rid "
415 + "where {alias}.rid = vcrel.owner "
416 + "AND (vc.publisher = ? OR vc.reviewer = ? OR vc.approver = ? OR vc.signatory = ?) "
417 + "group by vcrel.owner)",
418 new Object[] { actorId,
427 /* If the user is contributor */
431 + "select rel.owner from contributor_rel rel "
432 + "where {alias}.rid = rel.owner AND rel.refer = ?)",
433 actorId, Hibernate.LONG))
435 /* If the user is author */
436 Restrictions.eq("study.manager.rid", actorId)).add(
437 /* If the study is public */
438 Restrictions.eq("study.visibility",
439 Visibility.PUBLIC)));
441 // User is not logged in - show only public studies
442 topJunction.add(Restrictions.eq("study.visibility",
449 * Add search criteria by dates to the junction filter condition.
452 * the junction filter condition
456 * creation date property name
458 private void addCreationDateCriteria(final Junction topJunction,
459 final SearchFilterDTO filter, final String propName) {
460 // Filter by creation date
461 if (filter.getCreatedAfter() != null) {
463 .add(Restrictions.gt(propName, filter.getCreatedAfter()));
465 if (filter.getCreatedBefore() != null) {
466 topJunction.add(Restrictions
467 .lt(propName, filter.getCreatedBefore()));
474 * @see org.splat.service.SearchService#selectStudiesWhere(org.splat.dal.bo.som.Study.Properties[])
477 public List<Proxy> selectStudiesWhere(final Study.Properties... sprop) {
478 List<Proxy> result = new ArrayList<Proxy>();
482 // Creation of the Lucene query
483 File indir = getRepositoryService().getRepositoryIndexDirectory();
484 Directory index = FSDirectory.open(indir);
485 IndexSearcher searcher = new IndexSearcher(index, true);
486 BooleanQuery fulquery = new BooleanQuery();
488 for (int i = 0; i < sprop.length; i++) {
489 BooleanQuery query = new BooleanQuery();
490 Term input; // Supposed initialized below at least by the visibility
492 Visibility area = sprop[i].getVisibility(); // Visibility
494 input = new Term("area");
495 query.add(new TermQuery(input.createTerm(area.toString())),
496 BooleanClause.Occur.MUST);
498 ProgressState state = sprop[i].getProgressState(); // State
500 input = new Term("state");
501 if (state == ProgressState.inPROGRESS) {
502 BooleanQuery cristate = new BooleanQuery();
503 cristate.add(new TermQuery(input.createTerm("inWORK")),
504 BooleanClause.Occur.SHOULD);
506 new TermQuery(input.createTerm("inDRAFT")),
507 BooleanClause.Occur.SHOULD);
509 new TermQuery(input.createTerm("inCHECK")),
510 BooleanClause.Occur.SHOULD);
511 query.add(cristate, BooleanClause.Occur.MUST);
513 query.add(new TermQuery(input.createTerm(state
514 .toString())), BooleanClause.Occur.MUST);
517 String refid = sprop[i].getReference(); // Reference
519 input = new Term("ref");
520 query.add(new TermQuery(input.createTerm(refid)),
521 BooleanClause.Occur.MUST);
523 User manager = sprop[i].getManager(); // Author
524 if (manager != null) {
525 input = new Term("author");
526 query.add(new TermQuery(input
527 .createTerm(manager.toString())),
528 BooleanClause.Occur.MUST);
530 User actor = sprop[i].getActor(); // Contributor, Reviewer or Approver
532 input = new Term("actor");
534 new TermQuery(input.createTerm(actor.toString())),
535 BooleanClause.Occur.MUST);
537 String title = sprop[i].getTitle(); // Title
539 input = new Term("contents");
540 BooleanQuery critext = new BooleanQuery();
541 String operator = "AND"; // Future user input
542 BooleanClause.Occur clause = BooleanClause.Occur.MUST;
543 if (operator.equals("OR")) {
544 clause = BooleanClause.Occur.SHOULD;
546 String[] word = title.split(" ");
547 for (int j = 0; j < word.length; j++) {
548 critext.add(new TermQuery(input.createTerm(word[j])),
551 query.add(critext, BooleanClause.Occur.MUST);
553 List<SimulationContext> context = sprop[i]
554 .getSimulationContexts();
555 if (context != null && context.size() > 0) {
556 BooleanQuery critext = new BooleanQuery();
557 for (Iterator<SimulationContext> j = context.iterator(); j
559 SimulationContext seltext = j.next();
560 input = new Term(String.valueOf(seltext.getType()
562 critext.add(new TermQuery(input.createTerm(seltext
563 .getValue())), BooleanClause.Occur.MUST);
565 query.add(critext, BooleanClause.Occur.MUST);
567 fulquery.add(query, BooleanClause.Occur.SHOULD);
569 if (LOG.isInfoEnabled()) {
570 LOG.info("Searching studies by Lucene query \""
571 + fulquery.toString());
573 // Creation of the studies filter
574 BooleanFilter filter = new BooleanFilter();
575 TermsFilter select = new TermsFilter();
576 Term mytype = new Term("class");
577 select.addTerm(mytype.createTerm("Study"));
578 filter.add(new FilterClause(select, BooleanClause.Occur.SHOULD));
580 // Creation of the sort criteria
581 Sort sort = new Sort(new SortField("title", SortField.STRING));
584 TopFieldDocs found = searcher.search(fulquery, filter, hitsize,
587 if (found.totalHits < 1) {
588 return result; // No study found
591 // Construction of the result list
592 ScoreDoc[] hits = found.scoreDocs;
593 for (int i = 0; i < hits.length; i++) {
594 result.add(new IndexServiceImpl.ObjectProxy(searcher
598 } catch (Exception error) {
599 LOG.error("Error during Lucene search, reason:", error);
607 * @see org.splat.service.SearchService#indexStudy(org.splat.dal.bo.som.Study)
610 public void indexStudy(final Study study) {
611 LOG.debug("Index study: id=" + study.getRid() + "; reference="
612 + study.getReference());
614 Study.Properties sprop = new Study.Properties();
615 List<Proxy> index = selectStudiesWhere(sprop.setReference(study
618 if (index.size() != 0) {
619 LOG.debug("The given study is already indexed.");
620 return; // The given study is already indexed
623 IndexService lucin = getIndex();
624 Scenario[] scenes = study.getScenarii();
626 LOG.debug("Number of study " + study.getReference() + " actors: "
627 + study.getActor().size());
629 if (study.getProgressState() != ProgressState.inWORK) {
630 for (int i = 0; i < scenes.length; i++) {
631 List<KnowledgeElement> list = scenes[i]
632 .getAllKnowledgeElements();
633 for (Iterator<KnowledgeElement> j = list.iterator(); j
636 LOG.debug("Knowlegge added: id=" + j.next().getIndex());
640 } catch (Exception error) {
641 LOG.error("Unable to index the study '" + study.getIndex()
642 + "', reason:", error);
647 * Get lucene index handler. Create the index if it is not exist.
649 * @return IndexService
650 * @throws IOException
651 * if error when creating a new lucene index
653 private IndexService getIndex() throws IOException {
654 IndexService lucin = getIndexService();
655 if (IndexWriter.isLocked(FSDirectory.open(getRepositoryService()
656 .getRepositoryIndexDirectory()))) {
657 IndexWriter.unlock(FSDirectory.open(getRepositoryService()
658 .getRepositoryIndexDirectory()));
660 if (!lucin.exists()) {
661 lucin.create(); // Happens when re-indexing all studies
667 * Get the repositoryService.
669 * @return the repositoryService
671 public RepositoryService getRepositoryService() {
672 return _repositoryService;
676 * Set the repositoryService.
678 * @param repositoryService
679 * the repositoryService to set
681 public void setRepositoryService(final RepositoryService repositoryService) {
682 _repositoryService = repositoryService;
686 * Get the indexService.
688 * @return the indexService
690 public IndexService getIndexService() {
691 return _indexService;
695 * Set the indexService.
697 * @param indexService
698 * the indexService to set
700 public void setIndexService(final IndexService indexService) {
701 _indexService = indexService;
705 * Get the studyService.
707 * @return the studyService
709 public StudyService getStudyService() {
710 return _studyService;
714 * Set the studyService.
716 * @param studyService
717 * the studyService to set
719 public void setStudyService(final StudyService studyService) {
720 _studyService = studyService;
726 * @return the studyDAO
728 public StudyDAO getStudyDAO() {
736 * the studyDAO to set
738 public void setStudyDAO(final StudyDAO studyDAO) {
739 _studyDAO = studyDAO;
743 * Get the knowledgeElementDAO.
745 * @return the knowledgeElementDAO
747 public KnowledgeElementDAO getKnowledgeElementDAO() {
748 return _knowledgeElementDAO;
752 * Set the knowledgeElementDAO.
754 * @param knowledgeElementDAO
755 * the knowledgeElementDAO to set
757 public void setKnowledgeElementDAO(
758 final KnowledgeElementDAO knowledgeElementDAO) {
759 _knowledgeElementDAO = knowledgeElementDAO;