View Javadoc
1   /*
2    * Copyright (C) 2003-2008 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.services.wcm.search;
18  
19  import java.util.*;
20  import java.util.concurrent.CopyOnWriteArraySet;
21  
22  import javax.jcr.*;
23  import javax.jcr.nodetype.NodeType;
24  import javax.jcr.nodetype.NodeTypeManager;
25  import javax.jcr.query.*;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.exoplatform.commons.api.search.data.SearchResult;
29  import org.exoplatform.container.xml.InitParams;
30  import org.exoplatform.container.xml.ValueParam;
31  import org.exoplatform.portal.config.UserACL;
32  import org.exoplatform.services.cache.CacheService;
33  import org.exoplatform.services.cache.ExoCache;
34  import org.exoplatform.services.cms.documents.TrashService;
35  import org.exoplatform.services.cms.impl.Utils;
36  import org.exoplatform.services.cms.templates.TemplateService;
37  import org.exoplatform.services.jcr.RepositoryService;
38  import org.exoplatform.services.jcr.core.ManageableRepository;
39  import org.exoplatform.services.jcr.ext.common.SessionProvider;
40  import org.exoplatform.services.log.ExoLogger;
41  import org.exoplatform.services.log.Log;
42  import org.exoplatform.services.security.IdentityConstants;
43  import org.exoplatform.services.wcm.core.NodeLocation;
44  import org.exoplatform.services.wcm.core.NodetypeConstant;
45  import org.exoplatform.services.wcm.core.WCMConfigurationService;
46  import org.exoplatform.services.wcm.portal.LivePortalManagerService;
47  import org.exoplatform.services.wcm.publication.WCMComposer;
48  import org.exoplatform.services.wcm.search.QueryCriteria.DATE_RANGE_SELECTED;
49  import org.exoplatform.services.wcm.search.QueryCriteria.DatetimeRange;
50  import org.exoplatform.services.wcm.search.QueryCriteria.QueryProperty;
51  import org.exoplatform.services.wcm.search.base.*;
52  import org.exoplatform.services.wcm.search.connector.BaseSearchServiceConnector;
53  import org.exoplatform.services.wcm.utils.SQLQueryBuilder;
54  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
55  import org.exoplatform.services.wcm.utils.AbstractQueryBuilder.COMPARISON_TYPE;
56  import org.exoplatform.services.wcm.utils.AbstractQueryBuilder.LOGICAL;
57  import org.exoplatform.services.wcm.utils.AbstractQueryBuilder.ORDERBY;
58  import org.exoplatform.services.wcm.utils.AbstractQueryBuilder.PATH_TYPE;
59  import org.exoplatform.services.wcm.utils.AbstractQueryBuilder.QueryTermHelper;
60  /**
61   * The SiteSearchService component is used in the Search portlet that allows users
62   * to find all information matching with your given keyword.
63   */
64  public class SiteSearchServiceImpl implements SiteSearchService {
65  
66    private static final String  SITE_SEARCH_FOUND_CACHE = "ecms.SiteSearchService.found";
67  
68    private static final String  SITE_SEARCH_DROP_CACHE = "ecms.SiteSearchService.drop";
69  
70    /** Allow administrators to enable/disable the fuzzy search mechanism. */
71    private static final String IS_ENABLED_FUZZY_SEARCH = "isEnabledFuzzySearch";
72  
73    /** Allow the approximate level between the input keyword and the found key results.
74     * In case of the invalid configuration, the default value is set to 0.8. */
75    private static final String FUZZY_SEARCH_INDEX = "fuzzySearchIndex";
76    
77    /** The live portal manager service. */
78    private LivePortalManagerService livePortalManagerService;
79  
80    /** The ecm template service. */
81    private TemplateService templateService;
82  
83    /** The wcm configuration service. */
84    private WCMConfigurationService configurationService;
85  
86    /** The jcr repository service. */
87    private RepositoryService repositoryService;
88  
89    /** The exclude node types. */
90    private CopyOnWriteArraySet<String> excludeNodeTypes = new CopyOnWriteArraySet<>();
91  
92    /** The include node types. */
93    private CopyOnWriteArraySet<String> includeNodeTypes = new CopyOnWriteArraySet<>();
94  
95    /** The exclude mime types. */
96    private CopyOnWriteArraySet<String> excludeMimeTypes = new CopyOnWriteArraySet<>();
97  
98    /** The include mime types. */
99    private CopyOnWriteArraySet<String> includeMimeTypes = new CopyOnWriteArraySet<>();
100 
101   private boolean isEnabledFuzzySearch = true;
102   
103   private double fuzzySearchIndex = 0.8;
104   
105   /** The log. */
106   private static final Log LOG = ExoLogger.getLogger(SiteSearchServiceImpl.class.getName());
107   
108   private ExoCache<String, Map<?, Integer>> foundNodeCache;
109   private ExoCache<String, Map<Integer, Integer>> dropNodeCache;
110 
111   /**
112    * Instantiates a new site search service impl.
113    *
114    * @param portalManagerService the portal manager service
115    * @param templateService the template service
116    * @param configurationService the configuration service
117    * @param repositoryService the repository service
118    * @param initParams the init params
119    *
120    * @throws Exception the exception
121    */
122   public SiteSearchServiceImpl(LivePortalManagerService portalManagerService,
123                                TemplateService templateService,
124                                WCMConfigurationService configurationService,
125                                RepositoryService repositoryService,
126                                CacheService caService,
127                                InitParams initParams) throws Exception {
128     this.livePortalManagerService = portalManagerService;
129     this.templateService = templateService;
130     this.repositoryService = repositoryService;
131     this.configurationService = configurationService;
132     this.foundNodeCache = caService.getCacheInstance(SITE_SEARCH_FOUND_CACHE);
133     this.dropNodeCache = caService.getCacheInstance(SITE_SEARCH_DROP_CACHE);
134     if (initParams != null) {
135       ValueParam isEnabledFuzzySearchValue = initParams.getValueParam(IS_ENABLED_FUZZY_SEARCH);
136       if (isEnabledFuzzySearchValue != null)
137         isEnabledFuzzySearch = Boolean.parseBoolean(isEnabledFuzzySearchValue.getValue());
138       ValueParam enabledFuzzySearchValue = initParams.getValueParam(FUZZY_SEARCH_INDEX);
139       if (enabledFuzzySearchValue != null) {
140         try {
141           fuzzySearchIndex = Double.parseDouble(enabledFuzzySearchValue.getValue());
142         } catch (NumberFormatException e) {
143 //          log.warn("The current fuzzySearchIndex value is not a number, default value 0.8 will be used");
144           fuzzySearchIndex = 0.8;
145         }
146       }
147       if (fuzzySearchIndex < 0 || fuzzySearchIndex >= 1) {
148 //        log.warn("The current fuzzySearchIndex value is out of range from 0 to 1, default value 0.8 will be used");
149         fuzzySearchIndex = 0.8;
150       }
151     }
152 
153   }
154 
155   /*
156    * (non-Javadoc)
157    * @seeorg.exoplatform.services.wcm.search.SiteSearchService#
158    * addExcludeIncludeDataTypePlugin
159    * (org.exoplatform.services.wcm.search.ExcludeIncludeDataTypePlugin)
160    */
161   @Override
162   public void addExcludeIncludeDataTypePlugin(ExcludeIncludeDataTypePlugin plugin) {
163     excludeNodeTypes.addAll(plugin.getExcludeNodeTypes());
164     excludeMimeTypes.addAll(plugin.getExcludeMimeTypes());
165     includeMimeTypes.addAll(plugin.getIncludeMimeTypes());
166     includeNodeTypes.addAll(plugin.getIncludeNodeTypes());
167   }
168 
169   /*
170    * (non-Javadoc)
171    * @see
172    * org.exoplatform.services.wcm.search.SiteSearchService#searchSiteContents
173    * (org.exoplatform.services.wcm.search.QueryCriteria,
174    * org.exoplatform.services.jcr.ext.common.SessionProvider, int)
175    */
176   @Override
177   public AbstractPageList<ResultNode> searchSiteContents(SessionProvider sessionProvider,
178                                                     QueryCriteria queryCriteria,
179                                                     Locale locale,
180                                                     int pageSize,
181                                                     boolean isSearchContent) throws Exception {
182     ManageableRepository currentRepository = repositoryService.getCurrentRepository();
183     NodeLocation location = configurationService.getLivePortalsLocation();
184     Session session = sessionProvider.getSession(location.getWorkspace(),currentRepository);
185     if (queryCriteria.isSearchWebpage()) {
186       session = sessionProvider.getSession("portal-system", WCMCoreUtils.getRepository());
187     }
188     QueryManager queryManager = session.getWorkspace().getQueryManager();
189     long startTime = System.currentTimeMillis();
190     Query query = createQuery(queryCriteria, queryManager);
191     String suggestion = getSpellSuggestion(queryCriteria.getKeyword(),currentRepository);
192     AbstractPageList<ResultNode> pageList = null;
193     if (LOG.isDebugEnabled()) {
194       LOG.debug("execute query: " + query.getStatement().toLowerCase());
195     }
196     pageList = PageListFactory.createPageList(query.getStatement(),
197                                               locale,
198                                               session.getWorkspace().getName(),
199                                               query.getLanguage(),
200                                               IdentityConstants.SYSTEM.equals(session.getUserID()),
201                                               new NodeFilter(isSearchContent, queryCriteria),
202                                               new DataCreator(),
203                                               pageSize,
204                                               (int)queryCriteria.getLimit(), queryCriteria);
205     
206     long queryTime = System.currentTimeMillis() - startTime;
207     pageList.setQueryTime(queryTime);
208     pageList.setSpellSuggestion(suggestion);
209     return pageList;
210   }
211   
212   /**
213    * 
214    */
215   @Override
216   public AbstractPageList<ResultNode> searchPageContents(SessionProvider sessionProvider,
217                                                       QueryCriteria queryCriteria,
218                                                       Locale locale,
219                                                       int pageSize,
220                                                       boolean isSearchContent) throws Exception {
221     ManageableRepository currentRepository = repositoryService.getCurrentRepository();
222     Session session = sessionProvider.getSession("portal-system", currentRepository);
223     QueryManager queryManager = session.getWorkspace().getQueryManager();
224     long startTime = System.currentTimeMillis();
225     Query query = createSearchPageQuery(queryCriteria, queryManager);
226     if (query == null) {
227       return new ArrayNodePageList<>(pageSize);
228     }
229     String suggestion = getSpellSuggestion(queryCriteria.getKeyword(), currentRepository);
230     if (LOG.isDebugEnabled()) {
231       LOG.debug("execute query: " + query.getStatement().toLowerCase());
232     }
233     AbstractPageList<ResultNode> pageList = PageListFactory.createPageList(query.getStatement(),
234                                                                            locale,
235                                                                            session.getWorkspace()
236                                                                                   .getName(),
237                                                                            query.getLanguage(),
238                                                                            true,
239                                                                            new PageNodeFilter(),
240                                                                            new PageDataCreator(),
241                                                                            pageSize,
242                                                                            0);
243 
244     long queryTime = System.currentTimeMillis() - startTime;
245     pageList.setQueryTime(queryTime);
246     pageList.setSpellSuggestion(suggestion);
247     return pageList;
248   }
249 
250   @Override
251   public Map<?, Integer> getFoundNodes(String userId, String queryStatement) {
252     String key = new StringBuilder('(').append(userId).append(';').append(queryStatement).append(')').toString();
253     Map<?, Integer> ret = foundNodeCache.get(key);
254     if (ret == null) {
255       ret = new HashMap<Integer, Integer>();
256       foundNodeCache.put(key, ret);
257     }
258     return ret;
259   }
260 
261   @Override
262   public Map<Integer, Integer> getDropNodes(String userId, String queryStatement) {
263     String key = new StringBuilder('(').append(userId).append(';').append(queryStatement).append(')').toString();
264     Map<Integer, Integer> ret = dropNodeCache.get(key);
265     if (ret == null) {
266       ret = new HashMap<>();
267       dropNodeCache.put(key, ret);
268     }
269     return ret;
270   }
271 
272   @Override
273   public void clearCache(String userId, String queryStatement) {
274     String key = new StringBuilder('(').append(userId).append(';').append(queryStatement).append(')').toString();
275     foundNodeCache.remove(key);
276     dropNodeCache.remove(key);
277   }
278   
279   private Query createSearchPageQuery(QueryCriteria queryCriteria, QueryManager queryManager) throws Exception {
280     SQLQueryBuilder queryBuilder = new SQLQueryBuilder();
281     List<String> mopPages = this.searchPageByTitle(queryCriteria.getSiteName(),
282                                                    queryCriteria.getKeyword());
283     if (mopPages.size() == 0) {
284       return null;
285     }
286     List<QueryProperty> queryProps = new ArrayList<>();
287     for (String page : mopPages) {
288       QueryProperty prop = queryCriteria.new QueryProperty();
289       prop.setName("mop:page");
290       prop.setValue(page);
291       prop.setComparisonType(COMPARISON_TYPE.EQUAL);
292       queryProps.add(prop);
293     }
294     QueryProperty prop = queryCriteria.new QueryProperty();
295     prop.setName("exo:name");
296     prop.setValue("mop:" + queryCriteria.getKeyword().toLowerCase());
297     queryProps.add(prop);    
298     queryCriteria.setQueryMetadatas(queryProps.toArray(new QueryProperty[queryProps.size()]));
299     mapQueryTypes(queryCriteria, queryBuilder);
300     if (queryCriteria.isFulltextSearch()) {
301       mapQueryPath(queryCriteria, queryBuilder);
302       mapFulltextQueryTearm(queryCriteria, queryBuilder, LOGICAL.OR);
303     } else {
304       searchByNodeName(queryCriteria, queryBuilder);
305     }
306     mapCategoriesCondition(queryCriteria,queryBuilder);
307     mapDatetimeRangeSelected(queryCriteria,queryBuilder);
308     mapMetadataProperties(queryCriteria,queryBuilder, LOGICAL.OR);
309     orderBy(queryCriteria, queryBuilder);
310     String queryStatement = queryBuilder.createQueryStatement();
311     Query query = queryManager.createQuery(queryStatement, Query.SQL);
312     return query;
313   }
314 
315   /**
316    * @return return the JCR path of the mop:page nodes that have gtn:name
317    *         (page's title) containing the given specified <code>keyword</code>
318    * @throws Exception
319    */
320   private List<String> searchPageByTitle(String siteName, String keyword) throws Exception {
321     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
322     ManageableRepository currentRepository = repositoryService.getCurrentRepository();
323     Session session = sessionProvider.getSession("portal-system", currentRepository);
324     QueryManager queryManager = session.getWorkspace().getQueryManager();
325     QueryCriteria queryCriteria = new QueryCriteria();
326     queryCriteria.setSiteName(siteName);
327     queryCriteria.setKeyword(keyword);
328     queryCriteria.setSearchWebpage(true);
329     Query query = createSearchPageByTitleQuery(queryCriteria, queryManager);
330     if (LOG.isDebugEnabled()) {
331       LOG.debug("execute query: " + query.getStatement().toLowerCase());
332     }
333     List<String> pageList = PageListFactory.createPageList(query.getStatement(),
334                                                            session.getWorkspace().getName(),
335                                                            query.getLanguage(),
336                                                            true,
337                                                            new PageTitleDataCreator());
338     return pageList;
339   }
340   
341   /**
342    * 
343    * @param queryCriteria
344    * @param queryManager
345    * @return
346    * @throws Exception
347    */
348   private Query createSearchPageByTitleQuery(QueryCriteria queryCriteria, QueryManager queryManager) throws Exception {
349     SQLQueryBuilder queryBuilder = new SQLQueryBuilder();
350 
351     // select *
352     queryBuilder.selectTypes(null);
353 
354     // from mop:page node type
355     queryBuilder.fromNodeTypes(new String[] { "mop:page" });
356 
357     mapQueryPath(queryCriteria, queryBuilder);
358 
359     queryCriteria.setFulltextSearchProperty(new String[] {"gtn:name"});
360 
361     mapFulltextQueryTearm(queryCriteria, queryBuilder, LOGICAL.OR);
362 
363     String queryStatement = queryBuilder.createQueryStatement();
364     Query query = queryManager.createQuery(queryStatement, Query.SQL);
365 
366     return query;
367   }
368 
369   /**
370    * Gets the spell suggestion.
371    *
372    * @param checkingWord the checking word
373    * @param manageableRepository the manageable repository
374    *
375    * @return the spell suggestion
376    *
377    * @throws Exception the exception
378    */
379   private String getSpellSuggestion(String checkingWord, ManageableRepository manageableRepository) throws Exception {
380     //Retrieve spell suggestion in special way to avoid access denied exception
381     String suggestion = null;
382     Session session = null;
383     try{
384       session = manageableRepository.getSystemSession(manageableRepository.getConfiguration().getDefaultWorkspaceName());
385       QueryManager queryManager = session.getWorkspace().getQueryManager();
386       Query query = queryManager.createQuery("SELECT rep:spellcheck() FROM nt:base WHERE jcr:path like '/' AND SPELLCHECK('"
387                                                  + checkingWord + "')",
388                                              Query.SQL);
389       RowIterator rows = query.execute().getRows();
390       Value value = rows.nextRow().getValue("rep:spellcheck()");
391       if (value != null) {
392         suggestion = value.getString();
393       }
394     } catch (Exception e) {
395       if (LOG.isWarnEnabled()) {
396         LOG.warn(e.getMessage());
397       }
398     } finally {
399       if (session != null)
400         session.logout();
401     }
402     return suggestion;
403   }
404 
405   /**
406    * Search site content.
407    *
408    * @param queryCriteria the query criteria
409    * @param queryManager the query manager
410    *
411    * @return the query result
412    *
413    * @throws Exception the exception
414    */
415   private Query createQuery(QueryCriteria queryCriteria, QueryManager queryManager) throws Exception {
416     SQLQueryBuilder queryBuilder = new SQLQueryBuilder();
417     mapQueryTypes(queryCriteria, queryBuilder);
418     if (queryCriteria.isFulltextSearch()) {
419       mapQueryPath(queryCriteria, queryBuilder);
420       mapFulltextQueryTearm(queryCriteria, queryBuilder, LOGICAL.OR);
421     } else {
422       searchByNodeName(queryCriteria, queryBuilder);
423     }
424     mapCategoriesCondition(queryCriteria,queryBuilder);
425     mapDatetimeRangeSelected(queryCriteria, queryBuilder);
426     mapMetadataProperties(queryCriteria, queryBuilder, LOGICAL.AND);
427     orderBy(queryCriteria, queryBuilder);
428     String queryStatement = queryBuilder.createQueryStatement();
429     Query query = queryManager.createQuery(queryStatement, Query.SQL);
430 //    System.out.println(queryStatement);
431     return query;
432   }
433 
434   /**
435    * Map query path.
436    *
437    * @param queryCriteria the query criteria
438    * @param queryBuilder the query builder
439    *
440    * @throws Exception the exception
441    */
442   private void mapQueryPath(final QueryCriteria queryCriteria, final SQLQueryBuilder queryBuilder) throws Exception {
443     queryBuilder.setQueryPath(getPath(queryCriteria), PATH_TYPE.DECENDANTS);
444   }
445 
446   /**
447    * Gets the site path.
448    *
449    * @param queryCriteria the query criteria
450    *
451    * @return the site path
452    *
453    * @throws Exception the exception
454    */
455   private String getPath(final QueryCriteria queryCriteria) throws Exception {
456     String siteName = queryCriteria.getSiteName();
457     //search page path
458     if (queryCriteria.isSearchWebpage()) {
459       if ("all".equals(siteName) || siteName == null || siteName.trim().length() == 0) {
460         return PATH_PORTAL_SITES;
461       }
462       return PATH_PORTAL_SITES.concat("/mop:").concat(siteName);
463     }
464     //search document path
465     if (queryCriteria.getSearchPath() != null) {
466       return queryCriteria.getSearchPath();
467     }
468     String sitePath = null;
469     if (siteName != null) {
470       sitePath = livePortalManagerService.getPortalPathByName(siteName);
471     } else {
472       sitePath = configurationService.getLivePortalsLocation().getPath();
473     }
474     return sitePath;
475   }
476 
477   /**
478    * Map query term.
479    *
480    * @param queryCriteria the query criteria
481    * @param queryBuilder the query builder
482    */
483   private void mapFulltextQueryTearm(final QueryCriteria queryCriteria,
484                                      final SQLQueryBuilder queryBuilder, LOGICAL condition) {
485     String keyword = queryCriteria.getKeyword();
486     if (keyword == null || keyword.length() == 0)
487       return;
488 
489     keyword = Utils.escapeIllegalCharacterInQuery(keyword);
490 
491     QueryTermHelper queryTermHelper = new QueryTermHelper();
492     String queryTerm = null;
493     if (isEnabledFuzzySearch) {
494       if (keyword.contains("*") || keyword.contains("?") || keyword.contains("~") || keyword.contains("\"")) {
495         queryTerm = queryTermHelper.contains(keyword).buildTerm();
496       } else if(queryCriteria.isFuzzySearch()) {
497         queryTerm = queryTermHelper.contains(keyword).allowFuzzySearch(fuzzySearchIndex).buildTerm();
498       } else {
499         queryTerm = queryTermHelper.contains(keyword).buildTerm();
500       }
501     } else {
502       if(!queryCriteria.isFuzzySearch()) {
503         keyword = keyword.replace("~", "\\~");
504         keyword = keyword.replace("*", "\\*");
505         keyword = keyword.replace("?", "\\?");
506       }
507       queryTerm = queryTermHelper.contains(keyword).buildTerm();
508     }
509     String[] props = queryCriteria.getFulltextSearchProperty();
510     if (props == null || props.length == 0 || QueryCriteria.ALL_PROPERTY_SCOPE.equals(props[0])) {
511       queryBuilder.contains(null, queryTerm, LOGICAL.NULL);
512     } else {
513       queryBuilder.contains(props[0], queryTerm, LOGICAL.NULL);
514       for (int i = 1; i < props.length; i++) {
515         queryBuilder.contains(props[i], queryTerm, condition);
516       }
517     }
518   }
519   
520   /**
521    * Search by node name.
522    *
523    * @param queryCriteria the query criteria
524    * @param queryBuilder the query builder
525    *
526    * @throws Exception the exception
527    */
528   private void searchByNodeName(final QueryCriteria queryCriteria,
529                                 final SQLQueryBuilder queryBuilder) throws Exception {
530     queryBuilder.queryByNodeName(getPath(queryCriteria), queryCriteria.getKeyword());
531   }
532 
533   /**
534    * Map datetime range selected.
535    *
536    * @param queryCriteria the query criteria
537    * @param queryBuilder the query builder
538    */
539   private void mapDatetimeRangeSelected(final QueryCriteria queryCriteria,
540                                         final SQLQueryBuilder queryBuilder) {
541     DATE_RANGE_SELECTED selectedDateRange = queryCriteria.getDateRangeSelected();
542     if (selectedDateRange == null)
543       return;
544     if (DATE_RANGE_SELECTED.CREATED == selectedDateRange) {
545       DatetimeRange createdDateRange = queryCriteria.getCreatedDateRange();
546       queryBuilder.betweenDates("exo:dateCreated",
547                                 createdDateRange.getFromDate(),
548                                 createdDateRange.getToDate(),
549                                 LOGICAL.AND);
550     } else if (DATE_RANGE_SELECTED.MODIFIDED == selectedDateRange) {
551       DatetimeRange modifiedDateRange = queryCriteria.getLastModifiedDateRange();
552       queryBuilder.betweenDates("exo:dateModified",
553                                 modifiedDateRange.getFromDate(),
554                                 modifiedDateRange.getToDate(),
555                                 LOGICAL.AND);
556     } else if (DATE_RANGE_SELECTED.START_PUBLICATION == selectedDateRange) {
557       throw new UnsupportedOperationException();
558     } else if (DATE_RANGE_SELECTED.END_PUBLICATION == selectedDateRange) {
559       throw new UnsupportedOperationException();
560     }
561   }
562 
563   /**
564    * Map categories condition.
565    *
566    * @param queryCriteria the query criteria
567    * @param queryBuilder the query builder
568    */
569   private void mapCategoriesCondition(QueryCriteria queryCriteria, SQLQueryBuilder queryBuilder) {
570     String[] categoryUUIDs = queryCriteria.getCategoryUUIDs();
571     if (categoryUUIDs == null)
572       return;
573     queryBuilder.openGroup(LOGICAL.AND);
574     queryBuilder.like("exo:category", categoryUUIDs[0], LOGICAL.NULL);
575     if (categoryUUIDs.length > 1) {
576       for (int i = 1; i < categoryUUIDs.length; i++) {
577         queryBuilder.like("exo:category", categoryUUIDs[i], LOGICAL.OR);
578       }
579     }
580     queryBuilder.closeGroup();
581   }
582 
583   /**
584    * Map metadata properties.
585    *
586    * @param queryCriteria the query criteria
587    * @param queryBuilder the query builder
588    */
589   private void mapMetadataProperties(final QueryCriteria queryCriteria, SQLQueryBuilder queryBuilder, LOGICAL condition) {
590     QueryProperty[] queryProperty = queryCriteria.getQueryMetadatas();
591     if (queryProperty == null || queryProperty.length == 0)
592       return;
593     queryBuilder.openGroup(condition);
594     if (queryProperty[0].getComparisonType() == COMPARISON_TYPE.EQUAL) {
595       queryBuilder.equal(queryProperty[0].getName(), queryProperty[0].getValue(), LOGICAL.NULL);
596     } else {
597       queryBuilder.like(queryProperty[0].getName(), queryProperty[0].getValue(), LOGICAL.NULL);
598     }
599     if (queryProperty.length > 1) {
600       for (int i = 1; i < queryProperty.length; i++) {
601         if (queryProperty[i].getComparisonType() == COMPARISON_TYPE.EQUAL) {
602           queryBuilder.equal(queryProperty[i].getName(), queryProperty[i].getValue(), LOGICAL.OR);
603         } else {
604           queryBuilder.like(queryProperty[i].getName(), queryProperty[i].getValue(), LOGICAL.OR);
605         }
606       }
607     }
608     queryBuilder.closeGroup();
609   }
610 
611   /**
612    * Map query specific node types.
613    *
614    * @param queryCriteria the query criteria
615    * @param queryBuilder the query builder
616    * @param nodeTypeManager the node type manager
617    *
618    * @throws Exception the exception
619    */
620   private void mapQuerySpecificNodeTypes(final QueryCriteria queryCriteria,
621                                          final SQLQueryBuilder queryBuilder,
622                                          final NodeTypeManager nodeTypeManager) throws Exception {
623     String[] contentTypes = queryCriteria.getContentTypes();
624     NodeType fistType = nodeTypeManager.getNodeType(contentTypes[0]);
625     queryBuilder.openGroup(LOGICAL.AND);
626     if (fistType.isMixin()) {
627       queryBuilder.like("jcr:mixinTypes", contentTypes[0], LOGICAL.NULL);
628     } else {
629       queryBuilder.equal("jcr:primaryType", contentTypes[0], LOGICAL.NULL);
630     }
631     if(contentTypes.length>1) {
632       for (int i=1; i<contentTypes.length; i++) {
633         String type = contentTypes[i];
634         NodeType nodetype = nodeTypeManager.getNodeType(type);
635         if (nodetype.isMixin()) {
636           queryBuilder.like("jcr:mixinTypes", type, LOGICAL.OR);
637         } else {
638           queryBuilder.equal("jcr:primaryType", type, LOGICAL.OR);
639         }
640       }
641     }
642     queryBuilder.closeGroup();
643     //Remove some specific mimtype
644     queryBuilder.openGroup(LOGICAL.AND_NOT);
645     queryBuilder.like("jcr:mixinTypes", "exo:cssFile", LOGICAL.NULL);
646     queryBuilder.like("jcr:mixinTypes","exo:jsFile",LOGICAL.OR);
647     queryBuilder.closeGroup();
648   }
649 
650   /**
651    * Map query types.
652    *
653    * @param queryCriteria the query criteria
654    * @param queryBuilder the query builder
655    *
656    * @throws Exception the exception
657    */
658   private void mapQueryTypes(final QueryCriteria queryCriteria, final SQLQueryBuilder queryBuilder) throws Exception {
659     queryBuilder.selectTypes(null);
660     // Searh on nt:base
661     queryBuilder.fromNodeTypes(queryCriteria.getNodeTypes());
662     ManageableRepository currentRepository = repositoryService.getCurrentRepository();
663     NodeTypeManager manager = currentRepository.getNodeTypeManager();
664     // Query all documents for specific content types
665     String[] contentTypes = queryCriteria.getContentTypes();
666     if ((contentTypes != null && contentTypes.length > 0 && queryCriteria.getKeyword() == null)
667         || queryCriteria.isSearchWebpage()) {
668       mapQuerySpecificNodeTypes(queryCriteria, queryBuilder, manager);
669       return;
670     }
671     List<String> selectedNodeTypes =  
672       (contentTypes != null && contentTypes.length > 0) ? Arrays.asList(contentTypes) :
673                                                           templateService.getDocumentTemplates();
674     queryBuilder.openGroup(LOGICAL.AND);
675     if (selectedNodeTypes.contains("nt:file")) {
676       queryBuilder.equal("jcr:primaryType", NodetypeConstant.NT_FILE, LOGICAL.NULL);
677     } else {
678       //searching only document, not file. In this case, search nt:resource with exo:webContentChild mixin
679       queryBuilder.openGroup(null);
680       queryBuilder.equal(NodetypeConstant.JCR_PRIMARY_TYPE, NodetypeConstant.NT_RESOURCE, LOGICAL.NULL);
681       queryBuilder.equal(NodetypeConstant.JCR_MIXIN_TYPES, NodetypeConstant.EXO_WEBCONTENT_CHILD, LOGICAL.AND);
682       queryBuilder.closeGroup();
683     }
684     // query on exo:rss-enable nodetypes for title, summary field
685     //queryBuilder.equal("jcr:mixinTypes", "exo:rss-enable", LOGICAL.OR);
686     for (String type : selectedNodeTypes) {
687       NodeType nodetype = manager.getNodeType(type);
688       if (nodetype.isMixin()) {
689         if (selectedNodeTypes.contains("nt:file") || 
690             !NodetypeConstant.EXO_CSS_FILE.equals(type) &&
691             !NodetypeConstant.EXO_JS_FILE.equals(type) &&
692             !NodetypeConstant.EXO_HTML_FILE.equals(type)) {
693           queryBuilder.like("jcr:mixinTypes", type, LOGICAL.OR);
694         } else {
695           //searching only document, not file. In this case, search nt:resource with exo:webContentChild mixin
696           queryBuilder.openGroup(LOGICAL.OR);
697           queryBuilder.equal(NodetypeConstant.JCR_MIXIN_TYPES, type, LOGICAL.NULL);
698           queryBuilder.equal(NodetypeConstant.JCR_MIXIN_TYPES, NodetypeConstant.EXO_WEBCONTENT_CHILD, LOGICAL.AND);
699           queryBuilder.closeGroup();
700         }
701       } else {
702         if(!type.equals(NodetypeConstant.NT_FILE)) {
703           queryBuilder.equal("jcr:primaryType", type, LOGICAL.OR);
704         }
705       }
706     }
707     queryBuilder.closeGroup();
708     //unwanted document types: exo:cssFile, exo:jsFile
709     if(excludeMimeTypes.size()<1) return;
710     queryBuilder.openGroup(LOGICAL.AND_NOT);
711     String[] mimetypes = excludeMimeTypes.toArray(new String[]{});
712     queryBuilder.equal("jcr:mimeType",mimetypes[0],LOGICAL.NULL);
713     for(int i=1; i<mimetypes.length; i++) {
714       queryBuilder.equal("jcr:mimeType",mimetypes[i],LOGICAL.OR);
715     }
716     queryBuilder.closeGroup();
717     //Unwanted document by mixin nodetypes
718     queryBuilder.openGroup(LOGICAL.AND_NOT);
719     queryBuilder.like("jcr:mixinTypes", "exo:cssFile", LOGICAL.NULL);
720     queryBuilder.like("jcr:mixinTypes","exo:jsFile",LOGICAL.OR);
721     queryBuilder.closeGroup();
722 
723     queryBuilder.openGroup(LOGICAL.AND_NOT);
724     String[] _excludeNodeTypes = excludeNodeTypes.toArray(new String[]{});
725     for(int i=0; i < _excludeNodeTypes.length; i++) {
726       if(i==0) {
727         queryBuilder.equal("jcr:mixinTypes", _excludeNodeTypes[i], LOGICAL.NULL);
728       } else {
729         queryBuilder.equal("jcr:mixinTypes", _excludeNodeTypes[i], LOGICAL.OR);
730       }
731     }
732     queryBuilder.closeGroup();
733 
734   }
735 
736   /**
737    * Order by.
738    *
739    * @param queryCriteria the query criteria
740    * @param queryBuilder the query builder
741    */
742   private void orderBy(final QueryCriteria criteria, final SQLQueryBuilder queryBuilder) {
743     String sortBy = "jcr:score";
744     String orderBy = "desc";
745     //sort by
746     if (BaseSearchServiceConnector.sortByTitle.equals(criteria.getSortBy())) {
747       sortBy = NodetypeConstant.EXO_TITLE;
748     } else if (BaseSearchServiceConnector.sortByDate.equals(criteria.getSortBy())) {
749       sortBy = NodetypeConstant.EXO_LAST_MODIFIED_DATE;
750     }
751     if (StringUtils.isNotBlank(criteria.getOrderBy())) {
752       orderBy = criteria.getOrderBy();
753     }
754     queryBuilder.orderBy(sortBy, "desc".equals(orderBy) ? ORDERBY.DESC : ORDERBY.ASC);
755   }
756   
757   public static class NodeFilter implements NodeSearchFilter {
758 
759     private boolean isSearchContent;
760     private QueryCriteria queryCriteria;
761     private TrashService trashService = WCMCoreUtils.getService(TrashService.class);
762 
763     public NodeFilter(boolean isSearchContent, QueryCriteria queryCriteria) {
764       this.isSearchContent = isSearchContent;
765       this.queryCriteria = queryCriteria;
766     }
767     
768     @Override
769     public Node filterNodeToDisplay(Node node) {
770       try {
771         if (node == null || node.getPath().contains("/jcr:system/")) return null;
772         if(trashService.isInTrash(node)) return null;
773         Node displayNode = getNodeToCheckState(node);
774         if(displayNode == null) return null;
775         if (isSearchContent) return displayNode;
776         NodeLocation nodeLocation = NodeLocation.getNodeLocationByNode(displayNode);
777         WCMComposer wcmComposer = WCMCoreUtils.getService(WCMComposer.class);
778         HashMap<String, String> filters = new HashMap<>();
779         filters.put(WCMComposer.FILTER_MODE, queryCriteria.isLiveMode() ? WCMComposer.MODE_LIVE
780                                                                        : WCMComposer.MODE_EDIT);
781         return wcmComposer.getContent(nodeLocation.getWorkspace(),
782                                                                   nodeLocation.getPath(),
783                                                                   filters,
784                                                                   WCMCoreUtils.getSystemSessionProvider());
785       } catch (Exception e) {
786         return null;
787       }
788     }
789     
790     protected Node getNodeToCheckState(Node node)throws Exception{
791       Node displayNode = node;
792         if (displayNode.isNodeType("nt:resource")) {
793           displayNode = node.getParent();
794         }
795         //return exo:webContent when exo:htmlFile found
796         if (displayNode.isNodeType("exo:htmlFile")) {
797           Node parent = displayNode.getParent();
798           if (parent.isNodeType("exo:webContent")) {
799             displayNode = parent;
800           }
801         }
802         String[] contentTypes = queryCriteria.getContentTypes();
803         if(contentTypes != null && contentTypes.length > 0) {
804           for (String contentType : contentTypes) {
805             if (displayNode.isNodeType(contentType)) {
806               return displayNode;
807             }
808           }
809         } else {
810           return displayNode;
811         }
812         return null;
813     }
814     
815   }
816   
817   /**
818    * 
819    * @author ha_dangviet
820    *
821    */
822   public static class PageNodeFilter implements NodeSearchFilter {
823 
824     @Override
825     public Node filterNodeToDisplay(Node node) {
826       try {
827         if (!node.isNodeType("mop:navigation") && !node.isNodeType("mop:pagelink")
828             && !node.isNodeType("gtn:language")) {
829           return null;
830         } else {
831           return node;
832         }
833       } catch (RepositoryException e) {
834         return null;
835       }
836     }
837 
838   }
839   
840   public static class DataCreator implements SearchDataCreator<ResultNode> {
841 
842     @Override
843     public ResultNode createData(Node node, Row row, SearchResult searchResult) {
844       try {
845         if(row == null && searchResult != null) {
846           return new ResultNode(node, searchResult.getRelevancy(), searchResult.getExcerpt());
847         } else {
848           return new ResultNode(node, row);
849         }
850       } catch (Exception e) {
851         return null;
852       }
853     }
854     
855   }
856   
857   public static class PageDataCreator implements SearchDataCreator<ResultNode> {
858 
859     private static HashSet<String> userNavigationUriList = new HashSet<>();
860     
861     @Override
862     public ResultNode createData(Node node, Row row, SearchResult searchResult) {
863       try {
864         if (node.isNodeType("mop:pagelink")) {
865           node = node.getParent();
866         }
867         
868         if (node.isNodeType("gtn:language")) {
869           node = node.getParent().getParent();
870         }
871         String userNaviUri = "/" + WCMCoreUtils.getPortalName() + "/" + PageDataCreator.getUserNavigationURI(node).toString();
872         if (userNavigationUriList.contains(userNaviUri)) {
873           return null;
874         }
875         userNavigationUriList.add(userNaviUri);
876         return new ResultNode(node, row, userNaviUri);
877       } catch (Exception e) {
878         return null;
879       }
880     }
881     
882     /**
883      * Get user navigation URI from equivalent mop node in portal-system workspace
884      * @param node the mop node in portal-system workspace
885      * @return 
886      * @throws RepositoryException
887      */
888     public static StringBuilder getUserNavigationURI(Node node) throws RepositoryException {
889       if (!node.isNodeType("mop:portalsites")) {
890         StringBuilder builder = getUserNavigationURI(node.getParent());
891         String name = node.getName();
892         if ("mop:children".equals(name) || "mop:default".equals(name)
893             || "mop:rootnavigation".equals(name)) {
894           return builder.append("");
895         } else {
896           if (builder.length() > 0) {
897             builder.append("/");
898           }
899           return builder.append(node.getName().replaceFirst("mop:", ""));
900         }
901       } else {
902         return new StringBuilder();
903       }
904 
905     } 
906     
907   }  
908   
909   /**
910    * 
911    * @author ha_dangviet
912    *
913    */
914   public static class PageTitleDataCreator implements SearchDataCreator<String> {
915 
916     @Override
917     public String createData(Node node, Row row, SearchResult searchResult) {
918       try {
919         UserACL userACL = WCMCoreUtils.getService(UserACL.class);
920         if (node.hasProperty("gtn:access-permissions")) {
921           for (Value v : node.getProperty("gtn:access-permissions").getValues()) {
922             if (userACL.hasPermission(v.getString())) {
923               return node.getPath();
924             }
925           }
926           return null;
927         } else {
928           return node.getPath();
929         }
930       } catch (RepositoryException e) {
931         return null;
932       }
933     }
934     
935   }
936   
937 }