001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.search;
016    
017    import com.liferay.portal.NoSuchCountryException;
018    import com.liferay.portal.NoSuchModelException;
019    import com.liferay.portal.NoSuchRegionException;
020    import com.liferay.portal.kernel.configuration.Filter;
021    import com.liferay.portal.kernel.dao.orm.QueryUtil;
022    import com.liferay.portal.kernel.exception.PortalException;
023    import com.liferay.portal.kernel.exception.SystemException;
024    import com.liferay.portal.kernel.language.LanguageUtil;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.search.facet.AssetEntriesFacet;
028    import com.liferay.portal.kernel.search.facet.Facet;
029    import com.liferay.portal.kernel.search.facet.MultiValueFacet;
030    import com.liferay.portal.kernel.search.facet.ScopeFacet;
031    import com.liferay.portal.kernel.trash.TrashHandler;
032    import com.liferay.portal.kernel.trash.TrashHandlerRegistryUtil;
033    import com.liferay.portal.kernel.trash.TrashRenderer;
034    import com.liferay.portal.kernel.util.GetterUtil;
035    import com.liferay.portal.kernel.util.ListUtil;
036    import com.liferay.portal.kernel.util.LocaleUtil;
037    import com.liferay.portal.kernel.util.PropsKeys;
038    import com.liferay.portal.kernel.util.PropsUtil;
039    import com.liferay.portal.kernel.util.SetUtil;
040    import com.liferay.portal.kernel.util.StringPool;
041    import com.liferay.portal.kernel.util.StringUtil;
042    import com.liferay.portal.kernel.util.Time;
043    import com.liferay.portal.kernel.util.UnicodeProperties;
044    import com.liferay.portal.kernel.util.Validator;
045    import com.liferay.portal.model.Address;
046    import com.liferay.portal.model.AttachedModel;
047    import com.liferay.portal.model.AuditedModel;
048    import com.liferay.portal.model.BaseModel;
049    import com.liferay.portal.model.Country;
050    import com.liferay.portal.model.Group;
051    import com.liferay.portal.model.GroupedModel;
052    import com.liferay.portal.model.Region;
053    import com.liferay.portal.model.ResourcedModel;
054    import com.liferay.portal.model.User;
055    import com.liferay.portal.model.WorkflowedModel;
056    import com.liferay.portal.security.permission.ActionKeys;
057    import com.liferay.portal.security.permission.PermissionChecker;
058    import com.liferay.portal.security.permission.PermissionThreadLocal;
059    import com.liferay.portal.service.CountryServiceUtil;
060    import com.liferay.portal.service.GroupLocalServiceUtil;
061    import com.liferay.portal.service.RegionServiceUtil;
062    import com.liferay.portal.service.ServiceContext;
063    import com.liferay.portal.service.ServiceContextThreadLocal;
064    import com.liferay.portal.service.UserLocalServiceUtil;
065    import com.liferay.portal.util.PortalUtil;
066    import com.liferay.portlet.asset.model.AssetCategory;
067    import com.liferay.portlet.asset.service.AssetCategoryLocalServiceUtil;
068    import com.liferay.portlet.asset.service.AssetTagLocalServiceUtil;
069    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
070    import com.liferay.portlet.dynamicdatamapping.util.DDMIndexerUtil;
071    import com.liferay.portlet.expando.model.ExpandoBridge;
072    import com.liferay.portlet.expando.model.ExpandoColumnConstants;
073    import com.liferay.portlet.expando.util.ExpandoBridgeFactoryUtil;
074    import com.liferay.portlet.expando.util.ExpandoBridgeIndexerUtil;
075    import com.liferay.portlet.trash.model.TrashEntry;
076    import com.liferay.portlet.trash.service.TrashEntryLocalServiceUtil;
077    
078    import java.util.ArrayList;
079    import java.util.Date;
080    import java.util.HashMap;
081    import java.util.HashSet;
082    import java.util.List;
083    import java.util.Locale;
084    import java.util.Map;
085    import java.util.Set;
086    
087    import javax.portlet.PortletURL;
088    
089    /**
090     * @author Brian Wing Shun Chan
091     * @author Hugo Huijser
092     * @author Ryan Park
093     * @author Raymond Augé
094     */
095    public abstract class BaseIndexer implements Indexer {
096    
097            public static final int INDEX_FILTER_SEARCH_LIMIT = GetterUtil.getInteger(
098                    PropsUtil.get(PropsKeys.INDEX_FILTER_SEARCH_LIMIT));
099    
100            public void delete(long companyId, String uid) throws SearchException {
101                    try {
102                            SearchEngineUtil.deleteDocument(
103                                    getSearchEngineId(), companyId, uid);
104                    }
105                    catch (SearchException se) {
106                            throw se;
107                    }
108                    catch (Exception e) {
109                            throw new SearchException(e);
110                    }
111            }
112    
113            public void delete(Object obj) throws SearchException {
114                    try {
115                            doDelete(obj);
116                    }
117                    catch (SearchException se) {
118                            throw se;
119                    }
120                    catch (Exception e) {
121                            throw new SearchException(e);
122                    }
123            }
124    
125            public Document getDocument(Object obj) throws SearchException {
126                    try {
127                            Document document = doGetDocument(obj);
128    
129                            for (IndexerPostProcessor indexerPostProcessor :
130                                            _indexerPostProcessors) {
131    
132                                    indexerPostProcessor.postProcessDocument(document, obj);
133                            }
134    
135                            if (document == null) {
136                                    return null;
137                            }
138    
139                            Map<String, Field> fields = document.getFields();
140    
141                            Field groupIdField = fields.get(Field.GROUP_ID);
142    
143                            if (groupIdField != null) {
144                                    long groupId = GetterUtil.getLong(groupIdField.getValue());
145    
146                                    addStagingGroupKeyword(document, groupId);
147                            }
148    
149                            return document;
150                    }
151                    catch (SearchException se) {
152                            throw se;
153                    }
154                    catch (Exception e) {
155                            throw new SearchException(e);
156                    }
157            }
158    
159            public BooleanQuery getFacetQuery(
160                            String className, SearchContext searchContext)
161                    throws Exception {
162    
163                    BooleanQuery facetQuery = BooleanQueryFactoryUtil.create(searchContext);
164    
165                    facetQuery.addExactTerm(Field.ENTRY_CLASS_NAME, className);
166    
167                    if (searchContext.getUserId() > 0) {
168                            SearchPermissionChecker searchPermissionChecker =
169                                    SearchEngineUtil.getSearchPermissionChecker();
170    
171                            facetQuery =
172                                    (BooleanQuery)searchPermissionChecker.getPermissionQuery(
173                                            searchContext.getCompanyId(), searchContext.getGroupIds(),
174                                            searchContext.getUserId(), className, facetQuery,
175                                            searchContext);
176                    }
177    
178                    return facetQuery;
179            }
180    
181            public BooleanQuery getFullQuery(SearchContext searchContext)
182                    throws SearchException {
183    
184                    try {
185                            searchContext.setSearchEngineId(getSearchEngineId());
186    
187                            searchContext.setEntryClassNames(
188                                    new String[] {getClassName(searchContext)});
189    
190                            BooleanQuery contextQuery = BooleanQueryFactoryUtil.create(
191                                    searchContext);
192    
193                            addSearchAssetCategoryIds(contextQuery, searchContext);
194                            addSearchAssetTagNames(contextQuery, searchContext);
195                            addSearchEntryClassNames(contextQuery, searchContext);
196                            addSearchGroupId(contextQuery, searchContext);
197    
198                            BooleanQuery fullQuery = createFullQuery(
199                                    contextQuery, searchContext);
200    
201                            fullQuery.setQueryConfig(searchContext.getQueryConfig());
202    
203                            return fullQuery;
204                    }
205                    catch (SearchException se) {
206                            throw se;
207                    }
208                    catch (Exception e) {
209                            throw new SearchException(e);
210                    }
211            }
212    
213            public IndexerPostProcessor[] getIndexerPostProcessors() {
214                    return _indexerPostProcessors;
215            }
216    
217            public String getSearchEngineId() {
218                    if (_searchEngineId != null) {
219                            return _searchEngineId;
220                    }
221    
222                    Class<?> clazz = getClass();
223    
224                    String searchEngineId = GetterUtil.getString(
225                            PropsUtil.get(
226                                    PropsKeys.INDEX_SEARCH_ENGINE_ID, new Filter(clazz.getName())));
227    
228                    if (Validator.isNotNull(searchEngineId)) {
229                            SearchEngine searchEngine = SearchEngineUtil.getSearchEngine(
230                                    searchEngineId);
231    
232                            if (searchEngine != null) {
233                                    _searchEngineId = searchEngineId;
234                            }
235                    }
236    
237                    if (_searchEngineId == null) {
238                            _searchEngineId = SearchEngineUtil.getDefaultSearchEngineId();
239                    }
240    
241                    if (_log.isDebugEnabled()) {
242                            _log.debug(
243                                    "Search engine ID for " + clazz.getName() + " is " +
244                                            searchEngineId);
245                    }
246    
247                    return _searchEngineId;
248            }
249    
250            public String getSortField(String orderByCol) {
251                    String sortField = doGetSortField(orderByCol);
252    
253                    if (DocumentImpl.isSortableTextField(sortField)) {
254                            return DocumentImpl.getSortableFieldName(sortField);
255                    }
256    
257                    return sortField;
258            }
259    
260            public Summary getSummary(
261                            Document document, Locale locale, String snippet,
262                            PortletURL portletURL)
263                    throws SearchException {
264    
265                    try {
266                            Summary summary = doGetSummary(
267                                    document, locale, snippet, portletURL);
268    
269                            for (IndexerPostProcessor indexerPostProcessor :
270                                            _indexerPostProcessors) {
271    
272                                    indexerPostProcessor.postProcessSummary(
273                                            summary, document, locale, snippet, portletURL);
274                            }
275    
276                            return summary;
277                    }
278                    catch (SearchException se) {
279                            throw se;
280                    }
281                    catch (Exception e) {
282                            throw new SearchException(e);
283                    }
284            }
285    
286            public boolean hasPermission(
287                            PermissionChecker permissionChecker, String entryClassName,
288                            long entryClassPK, String actionId)
289                    throws Exception {
290    
291                    return true;
292            }
293    
294            public boolean isFilterSearch() {
295                    return _filterSearch;
296            }
297    
298            public boolean isIndexerEnabled() {
299                    return _indexerEnabled;
300            }
301    
302            public boolean isPermissionAware() {
303                    return _permissionAware;
304            }
305    
306            public boolean isStagingAware() {
307                    return _stagingAware;
308            }
309    
310            public void postProcessContextQuery(
311                            BooleanQuery contextQuery, SearchContext searchContext)
312                    throws Exception {
313            }
314    
315            public void postProcessSearchQuery(
316                            BooleanQuery searchQuery, SearchContext searchContext)
317                    throws Exception {
318    
319                    String keywords = searchContext.getKeywords();
320    
321                    if (Validator.isNull(keywords)) {
322                            addSearchTerm(searchQuery, searchContext, Field.DESCRIPTION, false);
323                            addSearchTerm(searchQuery, searchContext, Field.TITLE, false);
324                            addSearchTerm(searchQuery, searchContext, Field.USER_NAME, false);
325                    }
326            }
327    
328            public void registerIndexerPostProcessor(
329                    IndexerPostProcessor indexerPostProcessor) {
330    
331                    List<IndexerPostProcessor> indexerPostProcessorsList =
332                            ListUtil.fromArray(_indexerPostProcessors);
333    
334                    indexerPostProcessorsList.add(indexerPostProcessor);
335    
336                    _indexerPostProcessors = indexerPostProcessorsList.toArray(
337                            new IndexerPostProcessor[indexerPostProcessorsList.size()]);
338            }
339    
340            public void reindex(Object obj) throws SearchException {
341                    try {
342                            if (SearchEngineUtil.isIndexReadOnly() || !isIndexerEnabled()) {
343                                    return;
344                            }
345    
346                            doReindex(obj);
347                    }
348                    catch (SearchException se) {
349                            throw se;
350                    }
351                    catch (Exception e) {
352                            throw new SearchException(e);
353                    }
354            }
355    
356            public void reindex(String className, long classPK) throws SearchException {
357                    try {
358                            if (SearchEngineUtil.isIndexReadOnly() || !isIndexerEnabled()) {
359                                    return;
360                            }
361    
362                            doReindex(className, classPK);
363                    }
364                    catch (NoSuchModelException nsme) {
365                            if (_log.isWarnEnabled()) {
366                                    _log.warn("Unable to index " + className + " " + classPK);
367                            }
368                    }
369                    catch (SearchException se) {
370                            throw se;
371                    }
372                    catch (Exception e) {
373                            throw new SearchException(e);
374                    }
375            }
376    
377            public void reindex(String[] ids) throws SearchException {
378                    try {
379                            if (SearchEngineUtil.isIndexReadOnly() || !isIndexerEnabled()) {
380                                    return;
381                            }
382    
383                            doReindex(ids);
384                    }
385                    catch (SearchException se) {
386                            throw se;
387                    }
388                    catch (Exception e) {
389                            throw new SearchException(e);
390                    }
391            }
392    
393            public Hits search(SearchContext searchContext) throws SearchException {
394                    try {
395                            searchContext.setSearchEngineId(getSearchEngineId());
396    
397                            BooleanQuery fullQuery = getFullQuery(searchContext);
398    
399                            fullQuery.setQueryConfig(searchContext.getQueryConfig());
400    
401                            PermissionChecker permissionChecker =
402                                    PermissionThreadLocal.getPermissionChecker();
403    
404                            int end = searchContext.getEnd();
405                            int start = searchContext.getStart();
406    
407                            if (isFilterSearch() && (permissionChecker != null)) {
408                                    searchContext.setEnd(end + INDEX_FILTER_SEARCH_LIMIT);
409                                    searchContext.setStart(0);
410                            }
411    
412                            Hits hits = SearchEngineUtil.search(searchContext, fullQuery);
413    
414                            searchContext.setEnd(end);
415                            searchContext.setStart(start);
416    
417                            if (isFilterSearch() && (permissionChecker != null)) {
418                                    hits = filterSearch(hits, permissionChecker, searchContext);
419                            }
420    
421                            return hits;
422                    }
423                    catch (SearchException se) {
424                            throw se;
425                    }
426                    catch (Exception e) {
427                            throw new SearchException(e);
428                    }
429            }
430    
431            public void unregisterIndexerPostProcessor(
432                    IndexerPostProcessor indexerPostProcessor) {
433    
434                    List<IndexerPostProcessor> indexerPostProcessorsList =
435                            ListUtil.fromArray(_indexerPostProcessors);
436    
437                    indexerPostProcessorsList.remove(indexerPostProcessor);
438    
439                    _indexerPostProcessors = indexerPostProcessorsList.toArray(
440                            new IndexerPostProcessor[indexerPostProcessorsList.size()]);
441            }
442    
443            /**
444             * @deprecated {@link #addSearchLocalizedTerm(BooleanQuery, SearchContext,
445             *             String, boolean)}
446             */
447            protected void addLocalizedSearchTerm(
448                            BooleanQuery searchQuery, SearchContext searchContext, String field,
449                            boolean like)
450                    throws Exception {
451    
452                    addSearchLocalizedTerm(searchQuery, searchContext, field, like);
453            }
454    
455            protected void addSearchArrayQuery(
456                            BooleanQuery searchQuery, SearchContext searchContext, String field)
457                    throws Exception {
458    
459                    if (Validator.isNull(field)) {
460                            return;
461                    }
462    
463                    Object fieldValues = searchContext.getAttribute(field);
464    
465                    if (fieldValues == null) {
466                            return;
467                    }
468    
469                    BooleanQuery fieldQuery = null;
470    
471                    if (fieldValues instanceof int[]) {
472                            int[] fieldValuesArray = (int[])fieldValues;
473    
474                            if (fieldValuesArray.length == 0) {
475                                    return;
476                            }
477    
478                            fieldQuery = BooleanQueryFactoryUtil.create(searchContext);
479    
480                            for (int fieldValue : fieldValuesArray) {
481                                    fieldQuery.addTerm(field, fieldValue);
482                            }
483                    }
484                    else if (fieldValues instanceof Integer[]) {
485                            Integer[] fieldValuesArray = (Integer[])fieldValues;
486    
487                            if (fieldValuesArray.length == 0) {
488                                    return;
489                            }
490    
491                            fieldQuery = BooleanQueryFactoryUtil.create(searchContext);
492    
493                            for (Integer fieldValue : fieldValuesArray) {
494                                    fieldQuery.addTerm(field, fieldValue);
495                            }
496                    }
497                    else if (fieldValues instanceof long[]) {
498                            long[] fieldValuesArray = (long[])fieldValues;
499    
500                            if (fieldValuesArray.length == 0) {
501                                    return;
502                            }
503    
504                            fieldQuery = BooleanQueryFactoryUtil.create(searchContext);
505    
506                            for (long fieldValue : fieldValuesArray) {
507                                    fieldQuery.addTerm(field, fieldValue);
508                            }
509                    }
510                    else if (fieldValues instanceof Long[]) {
511                            Long[] fieldValuesArray = (Long[])fieldValues;
512    
513                            if (fieldValuesArray.length == 0) {
514                                    return;
515                            }
516    
517                            fieldQuery = BooleanQueryFactoryUtil.create(searchContext);
518    
519                            for (Long fieldValue : fieldValuesArray) {
520                                    fieldQuery.addTerm(field, fieldValue);
521                            }
522                    }
523                    else if (fieldValues instanceof short[]) {
524                            short[] fieldValuesArray = (short[])fieldValues;
525    
526                            if (fieldValuesArray.length == 0) {
527                                    return;
528                            }
529    
530                            fieldQuery = BooleanQueryFactoryUtil.create(searchContext);
531    
532                            for (short fieldValue : fieldValuesArray) {
533                                    fieldQuery.addTerm(field, fieldValue);
534                            }
535                    }
536                    else if (fieldValues instanceof Short[]) {
537                            Short[] fieldValuesArray = (Short[])fieldValues;
538    
539                            if (fieldValuesArray.length == 0) {
540                                    return;
541                            }
542    
543                            fieldQuery = BooleanQueryFactoryUtil.create(searchContext);
544    
545                            for (Short fieldValue : fieldValuesArray) {
546                                    fieldQuery.addTerm(field, fieldValue);
547                            }
548                    }
549    
550                    if (fieldQuery != null) {
551                            if (searchContext.isAndSearch()) {
552                                    searchQuery.add(fieldQuery, BooleanClauseOccur.MUST);
553                            }
554                            else {
555                                    searchQuery.add(fieldQuery, BooleanClauseOccur.SHOULD);
556                            }
557                    }
558            }
559    
560            protected void addSearchAssetCategoryIds(
561                            BooleanQuery contextQuery, SearchContext searchContext)
562                    throws Exception {
563    
564                    MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);
565    
566                    multiValueFacet.setFieldName(Field.ASSET_CATEGORY_IDS);
567                    multiValueFacet.setStatic(true);
568    
569                    searchContext.addFacet(multiValueFacet);
570            }
571    
572            protected void addSearchAssetCategoryTitles(
573                    Document document, String field, List<AssetCategory> assetCategories) {
574    
575                    Map<Locale, List<String>> assetCategoryTitles =
576                            new HashMap<Locale, List<String>>();
577    
578                    Locale defaultLocale = LocaleUtil.getDefault();
579    
580                    for (AssetCategory assetCategory : assetCategories) {
581                            Map<Locale, String> titleMap = assetCategory.getTitleMap();
582    
583                            for (Map.Entry<Locale, String> entry : titleMap.entrySet()) {
584                                    Locale locale = entry.getKey();
585                                    String title = entry.getValue();
586    
587                                    if (Validator.isNull(title)) {
588                                            continue;
589                                    }
590    
591                                    List<String> titles = assetCategoryTitles.get(locale);
592    
593                                    if (titles == null) {
594                                            titles = new ArrayList<String>();
595    
596                                            assetCategoryTitles.put(locale, titles);
597                                    }
598    
599                                    titles.add(title);
600                            }
601                    }
602    
603                    for (Map.Entry<Locale, List<String>> entry :
604                                    assetCategoryTitles.entrySet()) {
605    
606                            Locale locale = entry.getKey();
607                            List<String> titles = entry.getValue();
608    
609                            String[] titlesArray = titles.toArray(new String[0]);
610    
611                            if (locale.equals(defaultLocale)) {
612                                    document.addKeyword(field, titlesArray);
613                            }
614    
615                            document.addKeyword(
616                                    field.concat(StringPool.UNDERLINE).concat(locale.toString()),
617                                    titlesArray);
618                    }
619            }
620    
621            protected void addSearchAssetTagNames(
622                            BooleanQuery contextQuery, SearchContext searchContext)
623                    throws Exception {
624    
625                    MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);
626    
627                    multiValueFacet.setFieldName(Field.ASSET_TAG_NAMES);
628                    multiValueFacet.setStatic(true);
629    
630                    searchContext.addFacet(multiValueFacet);
631            }
632    
633            protected void addSearchDDMStruture(
634                            BooleanQuery searchQuery, SearchContext searchContext,
635                            DDMStructure ddmStructure)
636                    throws Exception {
637    
638                    Set<String> fieldNames = ddmStructure.getFieldNames();
639    
640                    for (String fieldName : fieldNames) {
641                            String name = DDMIndexerUtil.encodeName(
642                                    ddmStructure.getStructureId(), fieldName);
643    
644                            addSearchTerm(searchQuery, searchContext, name, false);
645                    }
646            }
647    
648            protected void addSearchEntryClassNames(
649                            BooleanQuery contextQuery, SearchContext searchContext)
650                    throws Exception {
651    
652                    Facet facet = new AssetEntriesFacet(searchContext);
653    
654                    facet.setStatic(true);
655    
656                    searchContext.addFacet(facet);
657            }
658    
659            protected void addSearchExpando(
660                            BooleanQuery searchQuery, SearchContext searchContext,
661                            String keywords)
662                    throws Exception {
663    
664                    ExpandoBridge expandoBridge = ExpandoBridgeFactoryUtil.getExpandoBridge(
665                            searchContext.getCompanyId(), getClassName(searchContext));
666    
667                    Set<String> attributeNames = SetUtil.fromEnumeration(
668                            expandoBridge.getAttributeNames());
669    
670                    for (String attributeName : attributeNames) {
671                            UnicodeProperties properties = expandoBridge.getAttributeProperties(
672                                    attributeName);
673    
674                            int indexType = GetterUtil.getInteger(
675                                    properties.getProperty(ExpandoColumnConstants.INDEX_TYPE));
676    
677                            if (indexType != ExpandoColumnConstants.INDEX_TYPE_NONE) {
678                                    String fieldName = ExpandoBridgeIndexerUtil.encodeFieldName(
679                                            attributeName);
680    
681                                    if (Validator.isNotNull(keywords)) {
682                                            if (searchContext.isAndSearch()) {
683                                                    searchQuery.addRequiredTerm(fieldName, keywords);
684                                            }
685                                            else {
686                                                    searchQuery.addTerm(fieldName, keywords);
687                                            }
688                                    }
689                            }
690                    }
691            }
692    
693            protected void addSearchGroupId(
694                            BooleanQuery contextQuery, SearchContext searchContext)
695                    throws Exception {
696    
697                    Facet facet = new ScopeFacet(searchContext);
698    
699                    facet.setStatic(true);
700    
701                    searchContext.addFacet(facet);
702            }
703    
704            protected void addSearchKeywords(
705                            BooleanQuery searchQuery, SearchContext searchContext)
706                    throws Exception {
707    
708                    String keywords = searchContext.getKeywords();
709    
710                    if (Validator.isNull(keywords)) {
711                            return;
712                    }
713    
714                    searchQuery.addTerms(Field.KEYWORDS, keywords);
715    
716                    addSearchExpando(searchQuery, searchContext, keywords);
717            }
718    
719            protected void addSearchLocalizedTerm(
720                            BooleanQuery searchQuery, SearchContext searchContext, String field,
721                            boolean like)
722                    throws Exception {
723    
724                    addSearchTerm(searchQuery, searchContext, field, like);
725                    addSearchTerm(
726                            searchQuery, searchContext,
727                            DocumentImpl.getLocalizedName(searchContext.getLocale(), field),
728                            like);
729            }
730    
731            protected void addSearchTerm(
732                            BooleanQuery searchQuery, SearchContext searchContext, String field,
733                            boolean like)
734                    throws Exception {
735    
736                    if (Validator.isNull(field)) {
737                            return;
738                    }
739    
740                    String value = String.valueOf(searchContext.getAttribute(field));
741    
742                    if (Validator.isNull(value)) {
743                            value = searchContext.getKeywords();
744                    }
745    
746                    if (Validator.isNull(value)) {
747                            return;
748                    }
749    
750                    if (searchContext.isAndSearch()) {
751                            searchQuery.addRequiredTerm(field, value, like);
752                    }
753                    else {
754                            searchQuery.addTerm(field, value, like);
755                    }
756            }
757    
758            protected void addStagingGroupKeyword(Document document, long groupId)
759                    throws Exception {
760    
761                    if (!isStagingAware()) {
762                            return;
763                    }
764    
765                    boolean stagingGroup = false;
766    
767                    Group group = GroupLocalServiceUtil.getGroup(groupId);
768    
769                    if (group.isLayout()) {
770                            group = GroupLocalServiceUtil.getGroup(group.getParentGroupId());
771                    }
772    
773                    if (group.isStagingGroup()) {
774                            stagingGroup = true;
775                    }
776    
777                    document.addKeyword(Field.STAGING_GROUP, stagingGroup);
778            }
779    
780            protected void addTrashFields(
781                            Document document, String className, long classPK, Date removedDate,
782                            String removedByUserName, String type)
783                    throws SystemException {
784    
785                    TrashEntry trashEntry = TrashEntryLocalServiceUtil.fetchEntry(
786                            className, classPK);
787    
788                    if (removedDate == null) {
789                            if (trashEntry != null) {
790                                    removedDate = trashEntry.getCreateDate();
791                            }
792                            else {
793                                    removedDate = new Date();
794                            }
795                    }
796    
797                    document.addDate(Field.REMOVED_DATE, removedDate);
798    
799                    if (removedByUserName == null) {
800                            if (trashEntry != null) {
801                                    removedByUserName = trashEntry.getUserName();
802                            }
803                            else {
804                                    ServiceContext serviceContext =
805                                            ServiceContextThreadLocal.getServiceContext();
806    
807                                    if (serviceContext != null) {
808                                            try {
809                                                    User user = UserLocalServiceUtil.getUser(
810                                                            serviceContext.getUserId());
811    
812                                                    removedByUserName = user.getFullName();
813                                            }
814                                            catch (PortalException pe) {
815                                            }
816                                    }
817                            }
818                    }
819    
820                    if (Validator.isNotNull(removedByUserName)) {
821                            document.addKeyword(
822                                    Field.REMOVED_BY_USER_NAME, removedByUserName, true);
823                    }
824    
825                    if (type == null) {
826                            if (trashEntry != null) {
827                                    TrashHandler trashHandler =
828                                            TrashHandlerRegistryUtil.getTrashHandler(
829                                                    trashEntry.getClassName());
830    
831                                    try {
832                                            TrashRenderer trashRenderer = trashHandler.getTrashRenderer(
833                                                    trashEntry.getClassPK());
834    
835                                            type = trashRenderer.getType();
836                                    }
837                                    catch (PortalException pe) {
838                                    }
839                            }
840                    }
841    
842                    if (Validator.isNotNull(type)) {
843                            document.addKeyword(Field.TYPE, type, true);
844                    }
845            }
846    
847            protected BooleanQuery createFullQuery(
848                            BooleanQuery contextQuery, SearchContext searchContext)
849                    throws Exception {
850    
851                    BooleanQuery searchQuery = BooleanQueryFactoryUtil.create(
852                            searchContext);
853    
854                    addSearchKeywords(searchQuery, searchContext);
855                    postProcessSearchQuery(searchQuery, searchContext);
856    
857                    for (IndexerPostProcessor indexerPostProcessor :
858                                    _indexerPostProcessors) {
859    
860                            indexerPostProcessor.postProcessSearchQuery(
861                                    searchQuery, searchContext);
862                    }
863    
864                    Map<String, Facet> facets = searchContext.getFacets();
865    
866                    for (Facet facet : facets.values()) {
867                            BooleanClause facetClause = facet.getFacetClause();
868    
869                            if (facetClause != null) {
870                                    contextQuery.add(
871                                            facetClause.getQuery(),
872                                            facetClause.getBooleanClauseOccur());
873                            }
874                    }
875    
876                    BooleanQuery fullQuery = BooleanQueryFactoryUtil.create(searchContext);
877    
878                    fullQuery.add(contextQuery, BooleanClauseOccur.MUST);
879    
880                    if (searchQuery.hasClauses()) {
881                            fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
882                    }
883    
884                    BooleanClause[] booleanClauses = searchContext.getBooleanClauses();
885    
886                    if (booleanClauses != null) {
887                            for (BooleanClause booleanClause : booleanClauses) {
888                                    fullQuery.add(
889                                            booleanClause.getQuery(),
890                                            booleanClause.getBooleanClauseOccur());
891                            }
892                    }
893    
894                    postProcessFullQuery(fullQuery, searchContext);
895    
896                    for (IndexerPostProcessor indexerPostProcessor :
897                                    _indexerPostProcessors) {
898    
899                            indexerPostProcessor.postProcessFullQuery(fullQuery, searchContext);
900                    }
901    
902                    return fullQuery;
903            }
904    
905            protected Summary createLocalizedSummary(Document document, Locale locale) {
906                    return createLocalizedSummary(
907                            document, locale, Field.TITLE, Field.CONTENT);
908            }
909    
910            protected Summary createLocalizedSummary(
911                    Document document, Locale locale, String titleField,
912                    String contentField) {
913    
914                    Locale snippetLocale = getSnippetLocale(document, locale);
915    
916                    String prefix = Field.SNIPPET + StringPool.UNDERLINE;
917    
918                    String title = document.get(
919                            snippetLocale, prefix + titleField, titleField);
920    
921                    String content = document.get(
922                            snippetLocale, prefix + contentField, contentField);
923    
924                    return new Summary(snippetLocale, title, content, null);
925            }
926    
927            protected Summary createSummary(Document document) {
928                    return createSummary(document, Field.TITLE, Field.CONTENT);
929            }
930    
931            protected Summary createSummary(
932                    Document document, String titleField, String contentField) {
933    
934                    String prefix = Field.SNIPPET + StringPool.UNDERLINE;
935    
936                    String title = document.get(prefix + titleField, titleField);
937                    String content = document.get(prefix + contentField, contentField);
938    
939                    return new Summary(title, content, null);
940            }
941    
942            protected void deleteDocument(long companyId, long field1)
943                    throws Exception {
944    
945                    deleteDocument(companyId, String.valueOf(field1));
946            }
947    
948            protected void deleteDocument(long companyId, long field1, String field2)
949                    throws Exception {
950    
951                    deleteDocument(companyId, String.valueOf(field1), field2);
952            }
953    
954            protected void deleteDocument(long companyId, String field1)
955                    throws Exception {
956    
957                    Document document = new DocumentImpl();
958    
959                    document.addUID(getPortletId(), field1);
960    
961                    SearchEngineUtil.deleteDocument(
962                            getSearchEngineId(), companyId, document.get(Field.UID));
963            }
964    
965            protected void deleteDocument(long companyId, String field1, String field2)
966                    throws Exception {
967    
968                    Document document = new DocumentImpl();
969    
970                    document.addUID(getPortletId(), field1, field2);
971    
972                    SearchEngineUtil.deleteDocument(
973                            getSearchEngineId(), companyId, document.get(Field.UID));
974            }
975    
976            protected abstract void doDelete(Object obj) throws Exception;
977    
978            protected abstract Document doGetDocument(Object obj) throws Exception;
979    
980            protected String doGetSortField(String orderByCol) {
981                    return orderByCol;
982            }
983    
984            protected abstract Summary doGetSummary(
985                            Document document, Locale locale, String snippet,
986                            PortletURL portletURL)
987                    throws Exception;
988    
989            protected abstract void doReindex(Object obj) throws Exception;
990    
991            protected abstract void doReindex(String className, long classPK)
992                    throws Exception;
993    
994            protected abstract void doReindex(String[] ids) throws Exception;
995    
996            protected Hits filterSearch(
997                    Hits hits, PermissionChecker permissionChecker,
998                    SearchContext searchContext) {
999    
1000                    List<Document> docs = new ArrayList<Document>();
1001                    List<Float> scores = new ArrayList<Float>();
1002    
1003                    int start = searchContext.getStart();
1004                    int end = searchContext.getEnd();
1005    
1006                    String paginationType = GetterUtil.getString(
1007                            searchContext.getAttribute("paginationType"), "more");
1008    
1009                    boolean hasMore = false;
1010    
1011                    Document[] documents = hits.getDocs();
1012    
1013                    for (int i = 0; i < documents.length; i++) {
1014                            try {
1015                                    Document document = documents[i];
1016    
1017                                    String entryClassName = document.get(Field.ENTRY_CLASS_NAME);
1018                                    long entryClassPK = GetterUtil.getLong(
1019                                            document.get(Field.ENTRY_CLASS_PK));
1020    
1021                                    Indexer indexer = IndexerRegistryUtil.getIndexer(
1022                                            entryClassName);
1023    
1024                                    if ((indexer.isFilterSearch() &&
1025                                             indexer.hasPermission(
1026                                                     permissionChecker, entryClassName, entryClassPK,
1027                                                     ActionKeys.VIEW)) ||
1028                                            !indexer.isFilterSearch() ||
1029                                            !indexer.isPermissionAware()) {
1030    
1031                                            docs.add(document);
1032                                            scores.add(hits.score(i));
1033                                    }
1034                            }
1035                            catch (Exception e) {
1036                            }
1037    
1038                            if (paginationType.equals("more") && (docs.size() > end)) {
1039                                    hasMore = true;
1040    
1041                                    break;
1042                            }
1043                    }
1044    
1045                    int length = docs.size();
1046    
1047                    if (hasMore) {
1048                            length = length + (end - start);
1049                    }
1050    
1051                    hits.setLength(length);
1052    
1053                    if ((start != QueryUtil.ALL_POS) && (end != QueryUtil.ALL_POS)) {
1054                            if (end > length) {
1055                                    end = length;
1056                            }
1057    
1058                            docs = docs.subList(start, end);
1059                    }
1060    
1061                    hits.setDocs(docs.toArray(new Document[docs.size()]));
1062                    hits.setScores(scores.toArray(new Float[docs.size()]));
1063    
1064                    hits.setSearchTime(
1065                            (float)(System.currentTimeMillis() - hits.getStart()) /
1066                                    Time.SECOND);
1067    
1068                    return hits;
1069            }
1070    
1071            protected Document getBaseModelDocument(
1072                            String portletId, BaseModel<?> baseModel)
1073                    throws SystemException {
1074    
1075                    return getBaseModelDocument(portletId, baseModel, baseModel);
1076            }
1077    
1078            protected Document getBaseModelDocument(
1079                            String portletId, BaseModel<?> baseModel,
1080                            BaseModel<?> workflowedBaseModel)
1081                    throws SystemException {
1082    
1083                    Document document = new DocumentImpl();
1084    
1085                    String className = baseModel.getModelClassName();
1086    
1087                    long classPK = 0;
1088                    long resourcePrimKey = 0;
1089    
1090                    if (baseModel instanceof ResourcedModel) {
1091                            ResourcedModel resourcedModel = (ResourcedModel)baseModel;
1092    
1093                            classPK = resourcedModel.getResourcePrimKey();
1094                            resourcePrimKey = resourcedModel.getResourcePrimKey();
1095                    }
1096                    else {
1097                            classPK = (Long)baseModel.getPrimaryKeyObj();
1098                    }
1099    
1100                    document.addUID(portletId, classPK);
1101    
1102                    List<AssetCategory> assetCategories =
1103                            AssetCategoryLocalServiceUtil.getCategories(className, classPK);
1104    
1105                    long[] assetCategoryIds = StringUtil.split(
1106                            ListUtil.toString(
1107                                    assetCategories, AssetCategory.CATEGORY_ID_ACCESSOR),
1108                            0L);
1109    
1110                    document.addKeyword(Field.ASSET_CATEGORY_IDS, assetCategoryIds);
1111    
1112                    addSearchAssetCategoryTitles(
1113                            document, Field.ASSET_CATEGORY_TITLES, assetCategories);
1114    
1115                    String[] assetTagNames = AssetTagLocalServiceUtil.getTagNames(
1116                            className, classPK);
1117    
1118                    document.addText(Field.ASSET_TAG_NAMES, assetTagNames);
1119    
1120                    document.addKeyword(Field.ENTRY_CLASS_NAME, className);
1121                    document.addKeyword(Field.ENTRY_CLASS_PK, classPK);
1122                    document.addKeyword(Field.PORTLET_ID, portletId);
1123    
1124                    if (resourcePrimKey > 0) {
1125                            document.addKeyword(Field.ROOT_ENTRY_CLASS_PK, resourcePrimKey);
1126                    }
1127    
1128                    if (baseModel instanceof AttachedModel) {
1129                            AttachedModel attachedModel = (AttachedModel)baseModel;
1130    
1131                            document.addKeyword(
1132                                    Field.CLASS_NAME_ID, attachedModel.getClassNameId());
1133                            document.addKeyword(Field.CLASS_PK, attachedModel.getClassPK());
1134                    }
1135    
1136                    if (baseModel instanceof AuditedModel) {
1137                            AuditedModel auditedModel = (AuditedModel)baseModel;
1138    
1139                            document.addKeyword(Field.COMPANY_ID, auditedModel.getCompanyId());
1140                            document.addDate(Field.CREATE_DATE, auditedModel.getCreateDate());
1141                            document.addDate(
1142                                    Field.MODIFIED_DATE, auditedModel.getModifiedDate());
1143                            document.addKeyword(Field.USER_ID, auditedModel.getUserId());
1144    
1145                            String userName = PortalUtil.getUserName(
1146                                    auditedModel.getUserId(), auditedModel.getUserName());
1147    
1148                            document.addKeyword(Field.USER_NAME, userName, true);
1149                    }
1150    
1151                    GroupedModel groupedModel = null;
1152    
1153                    if (baseModel instanceof GroupedModel) {
1154                            groupedModel = (GroupedModel)baseModel;
1155    
1156                            document.addKeyword(
1157                                    Field.GROUP_ID, getParentGroupId(groupedModel.getGroupId()));
1158                            document.addKeyword(
1159                                    Field.SCOPE_GROUP_ID, groupedModel.getGroupId());
1160                    }
1161    
1162                    if (workflowedBaseModel instanceof WorkflowedModel) {
1163                            WorkflowedModel workflowedModel =
1164                                    (WorkflowedModel)workflowedBaseModel;
1165    
1166                            document.addKeyword(Field.STATUS, workflowedModel.getStatus());
1167    
1168                            if ((groupedModel != null) && workflowedModel.isInTrash()) {
1169                                    addTrashFields(document, className, classPK, null, null, null);
1170                            }
1171                    }
1172    
1173                    ExpandoBridgeIndexerUtil.addAttributes(
1174                            document, baseModel.getExpandoBridge());
1175    
1176                    return document;
1177            }
1178    
1179            protected String getClassName(SearchContext searchContext) {
1180                    String[] classNames = getClassNames();
1181    
1182                    if (classNames.length != 1) {
1183                            throw new UnsupportedOperationException(
1184                                    "Search method needs to be manually implemented for " +
1185                                            "indexers with more than one class name");
1186                    }
1187    
1188                    return classNames[0];
1189            }
1190    
1191            protected Set<String> getLocalizedCountryNames(Country country) {
1192                    Set<String> countryNames = new HashSet<String>();
1193    
1194                    Locale[] locales = LanguageUtil.getAvailableLocales();
1195    
1196                    for (Locale locale : locales) {
1197                            String countryName = country.getName(locale);
1198    
1199                            countryName = countryName.toLowerCase();
1200    
1201                            countryNames.add(countryName);
1202                    }
1203    
1204                    return countryNames;
1205            }
1206    
1207            protected long getParentGroupId(long groupId) {
1208                    long parentGroupId = groupId;
1209    
1210                    try {
1211                            Group group = GroupLocalServiceUtil.getGroup(groupId);
1212    
1213                            if (group.isLayout()) {
1214                                    parentGroupId = group.getParentGroupId();
1215                            }
1216                    }
1217                    catch (Exception e) {
1218                    }
1219    
1220                    return parentGroupId;
1221            }
1222    
1223            protected abstract String getPortletId(SearchContext searchContext);
1224    
1225            protected Locale getSnippetLocale(Document document, Locale locale) {
1226                    String prefix = Field.SNIPPET + StringPool.UNDERLINE;
1227    
1228                    String localizedContentName =
1229                            prefix + DocumentImpl.getLocalizedName(locale, Field.CONTENT);
1230                    String localizedDescriptionName =
1231                            prefix + DocumentImpl.getLocalizedName(locale, Field.DESCRIPTION);
1232                    String localizedTitleName =
1233                            prefix + DocumentImpl.getLocalizedName(locale, Field.TITLE);
1234    
1235                    if ((document.getField(localizedContentName) != null) ||
1236                            (document.getField(localizedDescriptionName) != null) ||
1237                            (document.getField(localizedTitleName) != null)) {
1238    
1239                            return locale;
1240                    }
1241    
1242                    return null;
1243            }
1244    
1245            protected void populateAddresses(
1246                            Document document, List<Address> addresses, long regionId,
1247                            long countryId)
1248                    throws PortalException, SystemException {
1249    
1250                    List<String> cities = new ArrayList<String>();
1251    
1252                    List<String> countries = new ArrayList<String>();
1253    
1254                    if (countryId > 0) {
1255                            try {
1256                                    Country country = CountryServiceUtil.getCountry(countryId);
1257    
1258                                    countries.addAll(getLocalizedCountryNames(country));
1259                            }
1260                            catch (NoSuchCountryException nsce) {
1261                                    if (_log.isWarnEnabled()) {
1262                                            _log.warn(nsce.getMessage());
1263                                    }
1264                            }
1265                    }
1266    
1267                    List<String> regions = new ArrayList<String>();
1268    
1269                    if (regionId > 0) {
1270                            try {
1271                                    Region region = RegionServiceUtil.getRegion(regionId);
1272    
1273                                    regions.add(region.getName().toLowerCase());
1274                            }
1275                            catch (NoSuchRegionException nsre) {
1276                                    if (_log.isWarnEnabled()) {
1277                                            _log.warn(nsre.getMessage());
1278                                    }
1279                            }
1280                    }
1281    
1282                    List<String> streets = new ArrayList<String>();
1283                    List<String> zips = new ArrayList<String>();
1284    
1285                    for (Address address : addresses) {
1286                            cities.add(address.getCity().toLowerCase());
1287                            countries.addAll(getLocalizedCountryNames(address.getCountry()));
1288                            regions.add(address.getRegion().getName().toLowerCase());
1289                            streets.add(address.getStreet1().toLowerCase());
1290                            streets.add(address.getStreet2().toLowerCase());
1291                            streets.add(address.getStreet3().toLowerCase());
1292                            zips.add(address.getZip().toLowerCase());
1293                    }
1294    
1295                    document.addText("city", cities.toArray(new String[cities.size()]));
1296                    document.addText(
1297                            "country", countries.toArray(new String[countries.size()]));
1298                    document.addText("region", regions.toArray(new String[regions.size()]));
1299                    document.addText("street", streets.toArray(new String[streets.size()]));
1300                    document.addText("zip", zips.toArray(new String[zips.size()]));
1301            }
1302    
1303            protected void postProcessFullQuery(
1304                            BooleanQuery fullQuery, SearchContext searchContext)
1305                    throws Exception {
1306            }
1307    
1308            protected void setFilterSearch(boolean filterSearch) {
1309                    _filterSearch = filterSearch;
1310            }
1311    
1312            protected void setIndexerEnabled(boolean indexerEnabled) {
1313                    _indexerEnabled = indexerEnabled;
1314            }
1315    
1316            protected void setPermissionAware(boolean permissionAware) {
1317                    _permissionAware = permissionAware;
1318            }
1319    
1320            protected void setStagingAware(boolean stagingAware) {
1321                    _stagingAware = stagingAware;
1322            }
1323    
1324            private static Log _log = LogFactoryUtil.getLog(BaseIndexer.class);
1325    
1326            private boolean _filterSearch;
1327            private boolean _indexerEnabled = true;
1328            private IndexerPostProcessor[] _indexerPostProcessors =
1329                    new IndexerPostProcessor[0];
1330            private boolean _permissionAware;
1331            private String _searchEngineId;
1332            private boolean _stagingAware = true;
1333    
1334    }