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);
76 * "title" property name.
78 private final static String PROP_TITLE = "title";
81 * Injected repository service.
83 private RepositoryService _repositoryService;
85 * Injected index service.
87 private IndexService _indexService;
89 * Injected study service.
91 private StudyService _studyService;
95 private StudyDAO _studyDAO;
97 * Injected knowledge element DAO.
99 private KnowledgeElementDAO _knowledgeElementDAO;
102 * Get a list of studies which are currently not presented in the lucene index.
104 * @return list of ImportedStudy DTO
106 @Transactional(readOnly = true)
107 public List<ImportedStudyDTO> selectStudies() {
108 List<ImportedStudyDTO> table = new ArrayList<ImportedStudyDTO>();
109 Study.Properties sprop = new Study.Properties();
110 List<Study> dbStudies = getStudyDAO().getAll();
112 for (Study aStudy : dbStudies) {
115 if (selectStudiesWhere(
116 sprop.setReference(aStudy.getReference())).size() != 0) {
117 // If this study was already indexed and found in the lucene index
118 // then skip it to avoid adding to the index it again.
121 } catch (Exception error) {
124 // Add the study to the list of studies which are
125 // currently not presented in the lucene index.
126 table.add(BeanHelper.copyBean(aStudy, ImportedStudyDTO.class));
132 * Refresh lucene index for studies.
135 * list of studies id's
137 @Transactional(readOnly = true)
139 public void reindexStudies(final String[] ridlist) {
140 for (int i = 0; i < ridlist.length; i++) {
141 long index = Long.valueOf(ridlist[i].trim());
142 Study study = getStudyService().selectStudy(index);
150 * @see org.splat.service.SearchService#selectKnowledgeElementsWhere(org.splat.service.dto.KnowledgeSearchFilterDTO)
152 public List<Proxy> selectKnowledgeElementsWhere(
153 final KnowledgeSearchFilterDTO filter) {
154 List<Proxy> result = new ArrayList<Proxy>();
156 // Search matching all criteria
157 DetachedCriteria query = DetachedCriteria.forClass(
158 KnowledgeElement.class, "kelm");
159 query.createAlias("kelm.owner", "scen", Criteria.INNER_JOIN)
160 .createCriteria("scen.owner", "study", Criteria.INNER_JOIN)
161 .add(visibleStudyFilter(filter));
163 // Creation of the query
164 Junction topJunction = initQuery(filter);
166 addByWordsCriteria(topJunction, filter);
168 List<SimulationContext> context = filter.getSimContexts();
169 if (context != null && (!context.isEmpty())) {
170 // Get only studies which have given contexts
171 query.createAlias("study.contex", "ctx", Criteria.INNER_JOIN);
173 if (filter.isMatchAllContexts()) { // AND
174 critctx = Restrictions.conjunction();
176 critctx = Restrictions.disjunction();
178 for (SimulationContext seltext : context) {
179 // (simctxType = seltext.getType() AND simctxValue = seltext.getValue())
180 critctx.add(Restrictions.and(Restrictions.eq("ctx.value",
181 seltext.getValue()), Restrictions.eq("ctx.type",
182 seltext.getType())));
185 topJunction.add(critctx);
188 query.add(topJunction);
190 query.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
191 // Creation of the sort criteria
192 query.addOrder(Order.asc(PROP_TITLE));
194 if (LOG.isInfoEnabled()) {
195 LOG.info("Searching knowledge elements: \"" + query.toString());
199 List<KnowledgeElement> found = getKnowledgeElementDAO()
200 .getFilteredList(query);
202 // Construction of the result list
203 for (KnowledgeElement kelm : found) {
204 result.add(new StudyDTO(kelm.getIndex(), kelm.getReference(), kelm
205 .getProgressState(), kelm.getTitle(), kelm.getAuthor()
212 * Add search criteria for filtering by title contents.
215 * the search condition to be appended
219 private void addByWordsCriteria(final Junction topJunction,
220 final SearchFilterDTO filter) {
221 String title = filter.getWords(); // Title
222 if (title != null && (!title.isEmpty())) {
223 // Look for given words in titles
225 if (filter.isMatchAllCriteria()) { // AND
226 critext = Restrictions.conjunction();
228 critext = Restrictions.disjunction();
231 String[] word = title.split(" ");
232 for (int j = 0; j < word.length; j++) {
233 critext.add(Restrictions.like(PROP_TITLE, "%"
234 + processWildcards(word[j]) + "%"));
236 topJunction.add(critext);
241 * Escape wildcard characters and insert them instead of ? and *.
244 * the original string
245 * @return the transformed string
247 private String processWildcards(final String str) {
248 if (LOG.isDebugEnabled()) {
249 LOG.debug(" Original search string: " + str);
250 LOG.debug("Transformed search string: "
251 + str.replaceAll("\\\\", "\\\\\\\\").replaceAll("%",
252 "\\\\%").replaceAll("_", "\\\\_").replaceAll("\\?",
253 "_").replaceAll("\\*", "%"));
255 return str.replaceAll("\\\\", "\\\\\\\\").replaceAll("%", "\\\\%")
256 .replaceAll("_", "\\\\_").replaceAll("\\?", "_").replaceAll(
263 * @see org.splat.service.SearchService#selectStudiesWhere(org.splat.service.dto.StudySearchFilterDTO)
265 public List<Proxy> selectStudiesWhere(final StudySearchFilterDTO filter) {
266 List<Proxy> result = new ArrayList<Proxy>();
268 DetachedCriteria query = DetachedCriteria
269 .forClass(Study.class, "study").add(visibleStudyFilter(filter));
271 // Creation of the query
272 Junction topJunction = initQuery(filter);
274 addByWordsCriteria(topJunction, filter);
276 List<SimulationContext> context = filter.getSimContexts();
277 if (context != null && (!context.isEmpty())) {
278 // Get only studies which have given contexts
279 query.createAlias("contex", "ctx", Criteria.INNER_JOIN);
281 if (filter.isMatchAllContexts()) { // AND
282 critctx = Restrictions.conjunction();
284 critctx = Restrictions.disjunction();
286 for (SimulationContext seltext : context) {
287 // (simctxType = seltext.getType() AND simctxValue = seltext.getValue())
288 critctx.add(Restrictions.and(Restrictions.eq("ctx.value",
289 seltext.getValue()), Restrictions.eq("ctx.type",
290 seltext.getType())));
293 topJunction.add(critctx);
296 query.add(topJunction);
298 query.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
299 // Creation of the sort criteria
300 query.addOrder(Order.asc(PROP_TITLE));
302 if (LOG.isInfoEnabled()) {
303 LOG.info("Searching studies: \"" + query.toString());
307 List<Study> found = getStudyDAO().getFilteredList(query);
309 // Construction of the result list
310 for (Study std : found) {
311 result.add(new StudyDTO(std.getIndex(), std.getReference(), std
312 .getProgressState(), std.getTitle(), std.getAuthor()
319 * Initialize query with base criteria.
323 * @return top junction of the search filter
325 private Junction initQuery(final StudySearchFilterDTO filter) {
326 Junction topJunction;
327 if (filter.isMatchAllCriteria()) { // AND
328 topJunction = Restrictions.conjunction();
330 topJunction = Restrictions.disjunction();
332 if (!SearchFilterDTO.ANY_STATE.equals(filter.getState())) {
333 ProgressState state = ProgressState.valueOf(filter.getState()); // State
335 topJunction.add(Restrictions.eq("state", state));
338 String refid = filter.getReference(); // Reference
339 if (refid != null && !refid.isEmpty()) {
340 topJunction.add(Restrictions.like("sid", processWildcards(refid)));
343 // Filter by creation date
344 addCreationDateCriteria(topJunction, filter, "credate");
345 // Filter by modification date
346 if (filter.getUpdatedAfter() != null) {
347 topJunction.add(Restrictions
348 .gt("lasdate", filter.getUpdatedAfter()));
350 if (filter.getUpdatedBefore() != null) {
351 topJunction.add(Restrictions.lt("lasdate", filter
352 .getUpdatedBefore()));
355 // Filter by study author
356 long authorId = Long.valueOf(filter.getAuthor());
358 topJunction.add(Restrictions.eq("manager.rid", authorId));
365 * Initialize query with base criteria.
369 * @return top junction of the search filter
371 private Junction initQuery(final KnowledgeSearchFilterDTO filter) {
372 Junction topJunction;
373 if (filter.isMatchAllCriteria()) { // AND
374 topJunction = Restrictions.conjunction();
376 topJunction = Restrictions.disjunction();
379 // Filter by knowledge type
380 long ktypeId = Long.valueOf(filter.getKtype());
382 topJunction.add(Restrictions.eq("kelm.type.rid", ktypeId));
385 if (!SearchFilterDTO.ANY_STATE.equals(filter.getState())) {
386 ProgressState state = ProgressState.valueOf(filter.getState()); // State
388 topJunction.add(Restrictions.eq("kelm.state", state));
392 String refid = filter.getReference(); // Reference
393 if (refid != null && !refid.isEmpty()) {
394 long id = Long.valueOf(refid.replaceAll("^KE(0)*", ""));
396 topJunction.add(Restrictions.eq("kelm.rid", id));
400 addCreationDateCriteria(topJunction, filter, "date");
402 // Filter by knowledge author
403 long authorId = Long.valueOf(filter.getAuthor());
405 topJunction.add(Restrictions.eq("kelm.author.rid", authorId));
412 * Filter for visible studies.
416 * @return search condition for studies to get only visible studies
418 private Junction visibleStudyFilter(final SearchFilterDTO filter) {
419 Junction topJunction;
420 if (filter.isMatchAllCriteria()) { // AND
421 topJunction = Restrictions.conjunction();
423 topJunction = Restrictions.disjunction();
426 // Filter by connected user
427 long actorId = filter.getConnectedUserId(); // Contributor, Reviewer or Approver
429 // User is loggen in - show public studies and studies where he is participating
430 Disjunction orCrit = Restrictions.disjunction();
434 /* If the user is a validation cycle participant */
438 + "select vcrel.owner from cycle_rel vcrel "
439 + "inner join cycle vc on vcrel.refer = vc.rid "
440 + "where {alias}.rid = vcrel.owner "
441 + "AND (vc.publisher = ? OR vc.reviewer = ? OR vc.approver = ? OR vc.signatory = ?) "
442 + "group by vcrel.owner)",
443 new Object[] { actorId,
452 /* If the user is contributor */
456 + "select rel.owner from contributor_rel rel "
457 + "where {alias}.rid = rel.owner AND rel.refer = ?)",
458 actorId, Hibernate.LONG))
460 /* If the user is reader */
464 + "select rel.owner from reader_rel rel "
465 + "where {alias}.rid = rel.owner AND rel.refer = ?)",
466 actorId, Hibernate.LONG))
468 /* If the user is author */
469 Restrictions.eq("study.manager.rid", actorId)).add(
470 /* If the study is public */
471 Restrictions.eq("study.visibility",
472 Visibility.PUBLIC)));
474 // User is not logged in - show only public studies
475 topJunction.add(Restrictions.eq("study.visibility",
482 * Add search criteria by dates to the junction filter condition.
485 * the junction filter condition
489 * creation date property name
491 private void addCreationDateCriteria(final Junction topJunction,
492 final SearchFilterDTO filter, final String propName) {
493 // Filter by creation date
494 if (filter.getCreatedAfter() != null) {
496 .add(Restrictions.gt(propName, filter.getCreatedAfter()));
498 if (filter.getCreatedBefore() != null) {
499 topJunction.add(Restrictions
500 .lt(propName, filter.getCreatedBefore()));
507 * @see org.splat.service.SearchService#selectStudiesWhere(org.splat.dal.bo.som.Study.Properties[])
510 public List<Proxy> selectStudiesWhere(final Study.Properties... sprop) {
511 List<Proxy> result = new ArrayList<Proxy>();
515 // Creation of the Lucene query
516 File indir = getRepositoryService().getRepositoryIndexDirectory();
517 Directory index = FSDirectory.open(indir);
518 IndexSearcher searcher = new IndexSearcher(index, true);
519 BooleanQuery fulquery = new BooleanQuery();
521 for (int i = 0; i < sprop.length; i++) {
522 BooleanQuery query = new BooleanQuery();
523 Term input; // Supposed initialized below at least by the visibility
525 Visibility area = sprop[i].getVisibility(); // Visibility
527 input = new Term("area");
528 query.add(new TermQuery(input.createTerm(area.toString())),
529 BooleanClause.Occur.MUST);
531 ProgressState state = sprop[i].getProgressState(); // State
533 input = new Term("state");
534 if (state == ProgressState.inPROGRESS) {
535 BooleanQuery cristate = new BooleanQuery();
536 cristate.add(new TermQuery(input.createTerm("inWORK")),
537 BooleanClause.Occur.SHOULD);
539 new TermQuery(input.createTerm("inDRAFT")),
540 BooleanClause.Occur.SHOULD);
542 new TermQuery(input.createTerm("inCHECK")),
543 BooleanClause.Occur.SHOULD);
544 query.add(cristate, BooleanClause.Occur.MUST);
546 query.add(new TermQuery(input.createTerm(state
547 .toString())), BooleanClause.Occur.MUST);
550 String refid = sprop[i].getReference(); // Reference
552 input = new Term("ref");
553 query.add(new TermQuery(input.createTerm(refid)),
554 BooleanClause.Occur.MUST);
556 User manager = sprop[i].getManager(); // Author
557 if (manager != null) {
558 input = new Term("author");
559 query.add(new TermQuery(input
560 .createTerm(manager.toString())),
561 BooleanClause.Occur.MUST);
563 User actor = sprop[i].getActor(); // Contributor, Reviewer or Approver
565 input = new Term("actor");
567 new TermQuery(input.createTerm(actor.toString())),
568 BooleanClause.Occur.MUST);
570 String title = sprop[i].getTitle(); // Title
572 input = new Term("contents");
573 BooleanQuery critext = new BooleanQuery();
574 String operator = "AND"; // Future user input
575 BooleanClause.Occur clause = BooleanClause.Occur.MUST;
576 if (operator.equals("OR")) {
577 clause = BooleanClause.Occur.SHOULD;
579 String[] word = title.split(" ");
580 for (int j = 0; j < word.length; j++) {
581 critext.add(new TermQuery(input.createTerm(word[j])),
584 query.add(critext, BooleanClause.Occur.MUST);
586 List<SimulationContext> context = sprop[i]
587 .getSimulationContexts();
588 if (context != null && context.size() > 0) {
589 BooleanQuery critext = new BooleanQuery();
590 for (Iterator<SimulationContext> j = context.iterator(); j
592 SimulationContext seltext = j.next();
593 input = new Term(String.valueOf(seltext.getType()
595 critext.add(new TermQuery(input.createTerm(seltext
596 .getValue())), BooleanClause.Occur.MUST);
598 query.add(critext, BooleanClause.Occur.MUST);
600 fulquery.add(query, BooleanClause.Occur.SHOULD);
602 if (LOG.isInfoEnabled()) {
603 LOG.info("Searching studies by Lucene query \""
604 + fulquery.toString());
606 // Creation of the studies filter
607 BooleanFilter filter = new BooleanFilter();
608 TermsFilter select = new TermsFilter();
609 Term mytype = new Term("class");
610 select.addTerm(mytype.createTerm("Study"));
611 filter.add(new FilterClause(select, BooleanClause.Occur.SHOULD));
613 // Creation of the sort criteria
614 Sort sort = new Sort(new SortField(PROP_TITLE, SortField.STRING));
617 TopFieldDocs found = searcher.search(fulquery, filter, hitsize,
620 if (found.totalHits < 1) {
621 return result; // No study found
624 // Construction of the result list
625 ScoreDoc[] hits = found.scoreDocs;
626 for (int i = 0; i < hits.length; i++) {
627 result.add(new IndexServiceImpl.ObjectProxy(searcher
631 } catch (Exception error) {
632 LOG.error("Error during Lucene search, reason:", error);
640 * @see org.splat.service.SearchService#indexStudy(org.splat.dal.bo.som.Study)
643 public void indexStudy(final Study study) {
644 LOG.debug("Index study: id=" + study.getRid() + "; reference="
645 + study.getReference());
647 Study.Properties sprop = new Study.Properties();
648 List<Proxy> index = selectStudiesWhere(sprop.setReference(study
651 if (index.size() != 0) {
652 LOG.debug("The given study is already indexed.");
653 return; // The given study is already indexed
656 IndexService lucin = getIndex();
657 Scenario[] scenes = study.getScenarii();
659 LOG.debug("Number of study " + study.getReference() + " actors: "
660 + study.getActor().size());
662 if (study.getProgressState() != ProgressState.inWORK) {
663 for (int i = 0; i < scenes.length; i++) {
664 List<KnowledgeElement> list = scenes[i]
665 .getAllKnowledgeElements();
666 for (Iterator<KnowledgeElement> j = list.iterator(); j
669 LOG.debug("Knowlegge added: id=" + j.next().getIndex());
673 } catch (Exception error) {
674 LOG.error("Unable to index the study '" + study.getIndex()
675 + "', reason:", error);
680 * Get lucene index handler. Create the index if it is not exist.
682 * @return IndexService
683 * @throws IOException
684 * if error when creating a new lucene index
686 private IndexService getIndex() throws IOException {
687 IndexService lucin = getIndexService();
688 if (IndexWriter.isLocked(FSDirectory.open(getRepositoryService()
689 .getRepositoryIndexDirectory()))) {
690 IndexWriter.unlock(FSDirectory.open(getRepositoryService()
691 .getRepositoryIndexDirectory()));
693 if (!lucin.exists()) {
694 lucin.create(); // Happens when re-indexing all studies
700 * Get the repositoryService.
702 * @return the repositoryService
704 public RepositoryService getRepositoryService() {
705 return _repositoryService;
709 * Set the repositoryService.
711 * @param repositoryService
712 * the repositoryService to set
714 public void setRepositoryService(final RepositoryService repositoryService) {
715 _repositoryService = repositoryService;
719 * Get the indexService.
721 * @return the indexService
723 public IndexService getIndexService() {
724 return _indexService;
728 * Set the indexService.
730 * @param indexService
731 * the indexService to set
733 public void setIndexService(final IndexService indexService) {
734 _indexService = indexService;
738 * Get the studyService.
740 * @return the studyService
742 public StudyService getStudyService() {
743 return _studyService;
747 * Set the studyService.
749 * @param studyService
750 * the studyService to set
752 public void setStudyService(final StudyService studyService) {
753 _studyService = studyService;
759 * @return the studyDAO
761 public StudyDAO getStudyDAO() {
769 * the studyDAO to set
771 public void setStudyDAO(final StudyDAO studyDAO) {
772 _studyDAO = studyDAO;
776 * Get the knowledgeElementDAO.
778 * @return the knowledgeElementDAO
780 public KnowledgeElementDAO getKnowledgeElementDAO() {
781 return _knowledgeElementDAO;
785 * Set the knowledgeElementDAO.
787 * @param knowledgeElementDAO
788 * the knowledgeElementDAO to set
790 public void setKnowledgeElementDAO(
791 final KnowledgeElementDAO knowledgeElementDAO) {
792 _knowledgeElementDAO = knowledgeElementDAO;