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