001    /**
002     * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.kernel.search;
016    
017    import com.liferay.portal.NoSuchModelException;
018    import com.liferay.portal.kernel.dao.orm.QueryUtil;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.PropsKeys;
023    import com.liferay.portal.kernel.util.PropsUtil;
024    import com.liferay.portal.kernel.util.SetUtil;
025    import com.liferay.portal.kernel.util.Time;
026    import com.liferay.portal.kernel.util.UnicodeProperties;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.model.Group;
029    import com.liferay.portal.security.permission.ActionKeys;
030    import com.liferay.portal.security.permission.PermissionChecker;
031    import com.liferay.portal.security.permission.PermissionThreadLocal;
032    import com.liferay.portal.service.GroupLocalServiceUtil;
033    import com.liferay.portlet.asset.service.AssetCategoryServiceUtil;
034    import com.liferay.portlet.expando.model.ExpandoBridge;
035    import com.liferay.portlet.expando.util.ExpandoBridgeFactoryUtil;
036    import com.liferay.portlet.expando.util.ExpandoBridgeIndexer;
037    import com.liferay.portlet.expando.util.ExpandoBridgeIndexerUtil;
038    
039    import java.util.ArrayList;
040    import java.util.List;
041    import java.util.Set;
042    
043    /**
044     * @author Brian Wing Shun Chan
045     * @author Hugo Huijser
046     */
047    public abstract class BaseIndexer implements Indexer {
048    
049            public void delete(Object obj) throws SearchException {
050                    try {
051                            doDelete(obj);
052                    }
053                    catch (SearchException se) {
054                            throw se;
055                    }
056                    catch (Exception e) {
057                            throw new SearchException(e);
058                    }
059            }
060    
061            public Document getDocument(Object obj) throws SearchException {
062                    try {
063                            return doGetDocument(obj);
064                    }
065                    catch (SearchException se) {
066                            throw se;
067                    }
068                    catch (Exception e) {
069                            throw new SearchException(e);
070                    }
071            }
072    
073            public void reindex(Object obj) throws SearchException {
074                    try {
075                            if (SearchEngineUtil.isIndexReadOnly()) {
076                                    return;
077                            }
078    
079                            doReindex(obj);
080                    }
081                    catch (SearchException se) {
082                            throw se;
083                    }
084                    catch (Exception e) {
085                            throw new SearchException(e);
086                    }
087            }
088    
089            public void reindex(String className, long classPK) throws SearchException {
090                    try {
091                            if (SearchEngineUtil.isIndexReadOnly()) {
092                                    return;
093                            }
094    
095                            doReindex(className, classPK);
096                    }
097                    catch (NoSuchModelException nsme) {
098                            if (_log.isWarnEnabled()) {
099                                    _log.warn("Unable to index " + className + " " + classPK);
100                            }
101                    }
102                    catch (SearchException se) {
103                            throw se;
104                    }
105                    catch (Exception e) {
106                            throw new SearchException(e);
107                    }
108            }
109    
110            public void reindex(String[] ids) throws SearchException {
111                    try {
112                            if (SearchEngineUtil.isIndexReadOnly()) {
113                                    return;
114                            }
115    
116                            doReindex(ids);
117                    }
118                    catch (SearchException se) {
119                            throw se;
120                    }
121                    catch (Exception e) {
122                            throw new SearchException(e);
123                    }
124            }
125    
126            public Hits search(SearchContext searchContext) throws SearchException {
127                    try {
128                            String className = getClassName(searchContext);
129    
130                            BooleanQuery contextQuery = BooleanQueryFactoryUtil.create();
131    
132                            addSearchAssetCategoryIds(contextQuery, searchContext);
133                            addSearchAssetTagNames(contextQuery, searchContext);
134                            addSearchGroupId(contextQuery, searchContext);
135                            addSearchOwnerUserId(contextQuery, searchContext);
136                            addSearchCategoryIds(contextQuery, searchContext);
137                            addSearchNodeIds(contextQuery, searchContext);
138                            addSearchFolderIds(contextQuery, searchContext);
139                            addSearchPortletIds(contextQuery, searchContext);
140    
141                            BooleanQuery fullQuery = createFullQuery(
142                                    contextQuery, searchContext);
143    
144                            PermissionChecker permissionChecker =
145                                    PermissionThreadLocal.getPermissionChecker();
146    
147                            int start = searchContext.getStart();
148                            int end = searchContext.getEnd();
149    
150                            if (isFilterSearch() && (permissionChecker != null)) {
151                                    start = 0;
152                                    end = end + INDEX_FILTER_SEARCH_LIMIT;
153                            }
154    
155                            Hits hits = SearchEngineUtil.search(
156                                    searchContext.getCompanyId(), searchContext.getGroupIds(),
157                                    searchContext.getUserId(), className, fullQuery,
158                                    searchContext.getSorts(), start, end);
159    
160                            if (isFilterSearch() && (permissionChecker != null)) {
161                                    hits = filterSearch(hits, permissionChecker, searchContext);
162                            }
163    
164                            return hits;
165                    }
166                    catch (SearchException se) {
167                            throw se;
168                    }
169                    catch (Exception e) {
170                            throw new SearchException(e);
171                    }
172            }
173    
174            protected void addSearchAssetCategoryIds(
175                            BooleanQuery contextQuery, SearchContext searchContext)
176                    throws Exception {
177    
178                    long[] assetCategoryIds = searchContext.getAssetCategoryIds();
179    
180                    if ((assetCategoryIds == null) || (assetCategoryIds.length == 0)) {
181                            return;
182                    }
183    
184                    BooleanQuery assetCategoryIdsQuery = BooleanQueryFactoryUtil.create();
185    
186                    for (long assetCategoryId : assetCategoryIds) {
187                            if (searchContext.getUserId() > 0) {
188                                    try {
189                                            AssetCategoryServiceUtil.getCategory(assetCategoryId);
190                                    }
191                                    catch (Exception e) {
192                                            continue;
193                                    }
194                            }
195    
196                            TermQuery termQuery = TermQueryFactoryUtil.create(
197                                    Field.ASSET_CATEGORY_IDS, assetCategoryId);
198    
199                             assetCategoryIdsQuery.add(termQuery, BooleanClauseOccur.MUST);
200                    }
201    
202                    if (!assetCategoryIdsQuery.clauses().isEmpty()) {
203                            contextQuery.add(assetCategoryIdsQuery, BooleanClauseOccur.MUST);
204                    }
205            }
206    
207            protected void addSearchAssetTagNames(
208                            BooleanQuery contextQuery, SearchContext searchContext)
209                    throws Exception {
210    
211                    String[] assetTagNames = searchContext.getAssetTagNames();
212    
213                    if ((assetTagNames == null) || (assetTagNames.length == 0)) {
214                            return;
215                    }
216    
217                    BooleanQuery assetTagNamesQuery = BooleanQueryFactoryUtil.create();
218    
219                    for (String assetTagName : assetTagNames) {
220                            TermQuery termQuery = TermQueryFactoryUtil.create(
221                                    Field.ASSET_TAG_NAMES, assetTagName);
222    
223                            assetTagNamesQuery.add(termQuery, BooleanClauseOccur.MUST);
224                    }
225    
226                    if (!assetTagNamesQuery.clauses().isEmpty()) {
227                            contextQuery.add(assetTagNamesQuery, BooleanClauseOccur.MUST);
228                    }
229            }
230    
231            protected void addSearchCategoryIds(
232                            BooleanQuery contextQuery, SearchContext searchContext)
233                    throws Exception {
234    
235                    long[] categoryIds = searchContext.getCategoryIds();
236    
237                    if ((categoryIds == null) || (categoryIds.length == 0)) {
238                            return;
239                    }
240    
241                    BooleanQuery categoryIdsQuery = BooleanQueryFactoryUtil.create();
242    
243                    for (long categoryId : categoryIds) {
244                            if (searchContext.getUserId() > 0) {
245                                    try {
246                                            checkSearchCategoryId(categoryId, searchContext);
247                                    }
248                                    catch (Exception e) {
249                                            continue;
250                                    }
251                            }
252    
253                            TermQuery termQuery = TermQueryFactoryUtil.create(
254                                    Field.CATEGORY_ID, categoryId);
255    
256                            categoryIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
257                    }
258    
259                    if (!categoryIdsQuery.clauses().isEmpty()) {
260                            contextQuery.add(categoryIdsQuery, BooleanClauseOccur.MUST);
261                    }
262            }
263    
264            protected void addSearchExpando(
265                            BooleanQuery searchQuery, SearchContext searchContext,
266                            String keywords)
267                    throws Exception {
268    
269                    ExpandoBridge expandoBridge =
270                            ExpandoBridgeFactoryUtil.getExpandoBridge(
271                                    searchContext.getCompanyId(), getClassName(searchContext));
272    
273                    Set<String> attributeNames = SetUtil.fromEnumeration(
274                            expandoBridge.getAttributeNames());
275    
276                    for (String attributeName : attributeNames) {
277                            UnicodeProperties properties = expandoBridge.getAttributeProperties(
278                                    attributeName);
279    
280                            if (GetterUtil.getBoolean(
281                                            properties.getProperty(ExpandoBridgeIndexer.INDEXABLE))) {
282    
283                                    String fieldName = ExpandoBridgeIndexerUtil.encodeFieldName(
284                                            attributeName);
285    
286                                    if (Validator.isNotNull(keywords)) {
287                                            if (searchContext.isAndSearch()) {
288                                                    searchQuery.addRequiredTerm(fieldName, keywords, true);
289                                            }
290                                            else {
291                                                    searchQuery.addTerm(fieldName, keywords, true);
292                                            }
293                                    }
294                            }
295                    }
296            }
297    
298            protected void addSearchFolderIds(
299                            BooleanQuery contextQuery, SearchContext searchContext)
300                    throws Exception {
301    
302                    long[] folderIds = searchContext.getFolderIds();
303    
304                    if ((folderIds == null) || (folderIds.length == 0)) {
305                            return;
306                    }
307    
308                    BooleanQuery folderIdsQuery = BooleanQueryFactoryUtil.create();
309    
310                    for (long folderId : folderIds) {
311                            if (searchContext.getUserId() > 0) {
312                                    try {
313                                            checkSearchFolderId(folderId, searchContext);
314                                    }
315                                    catch (Exception e) {
316                                            continue;
317                                    }
318                            }
319    
320                            TermQuery termQuery = TermQueryFactoryUtil.create(
321                                    Field.FOLDER_ID, folderId);
322    
323                            folderIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
324                    }
325    
326                    if (!folderIdsQuery.clauses().isEmpty()) {
327                            contextQuery.add(folderIdsQuery, BooleanClauseOccur.MUST);
328                    }
329            }
330    
331            protected void addSearchGroupId(
332                            BooleanQuery contextQuery, SearchContext searchContext)
333                    throws Exception {
334    
335                    long[] groupIds = searchContext.getGroupIds();
336    
337                    if ((groupIds == null) || (groupIds.length == 0) ||
338                            ((groupIds.length == 1) && (groupIds[0] == 0))){
339    
340                            return;
341                    }
342    
343                    BooleanQuery groupIdsQuery = BooleanQueryFactoryUtil.create();
344    
345                    for (int i = 0; i < groupIds.length; i ++) {
346                            long groupId = groupIds[i];
347    
348                            if (groupId <= 0) {
349                                    continue;
350                            }
351    
352                            try {
353                                    Group group = GroupLocalServiceUtil.getGroup(groupId);
354    
355                                    long parentGroupId = groupId;
356    
357                                    if (group.isLayout() || searchContext.isScopeStrict()) {
358                                            contextQuery.addRequiredTerm(
359                                                    Field.SCOPE_GROUP_ID, groupId);
360                                    }
361    
362                                    if (group.isLayout()) {
363                                            parentGroupId = group.getParentGroupId();
364                                    }
365    
366                                    groupIdsQuery.addTerm(Field.GROUP_ID, parentGroupId);
367    
368                                    groupIds[i] = parentGroupId;
369                            }
370                            catch (Exception e) {
371                                    continue;
372                            }
373                    }
374    
375                    searchContext.setGroupIds(groupIds);
376    
377                    if (!groupIdsQuery.clauses().isEmpty()) {
378                            contextQuery.add(groupIdsQuery, BooleanClauseOccur.MUST);
379                    }
380            }
381    
382            protected void addSearchKeywords(
383                            BooleanQuery searchQuery, SearchContext searchContext)
384                    throws Exception {
385    
386                    String keywords = searchContext.getKeywords();
387    
388                    if (Validator.isNull(keywords)) {
389                            return;
390                    }
391    
392                    searchQuery.addTerms(_KEYWORDS_FIELDS, keywords);
393    
394                    searchQuery.addExactTerm(Field.ASSET_CATEGORY_NAMES, keywords);
395                    searchQuery.addExactTerm(Field.ASSET_TAG_NAMES, keywords);
396    
397                    addSearchExpando(searchQuery, searchContext, keywords);
398            }
399    
400            protected void addSearchNodeIds(
401                            BooleanQuery contextQuery, SearchContext searchContext)
402                    throws Exception {
403    
404                    long[] nodeIds = searchContext.getNodeIds();
405    
406                    if ((nodeIds == null) || (nodeIds.length == 0)) {
407                            return;
408                    }
409    
410                    BooleanQuery nodeIdsQuery = BooleanQueryFactoryUtil.create();
411    
412                    for (long nodeId : nodeIds) {
413                            if (searchContext.getUserId() > 0) {
414                                    try {
415                                            checkSearchNodeId(nodeId, searchContext);
416                                    }
417                                    catch (Exception e) {
418                                            continue;
419                                    }
420                            }
421    
422                            TermQuery termQuery = TermQueryFactoryUtil.create(
423                                    Field.NODE_ID, nodeId);
424    
425                            nodeIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
426                    }
427    
428                    if (!nodeIdsQuery.clauses().isEmpty()) {
429                            contextQuery.add(nodeIdsQuery, BooleanClauseOccur.MUST);
430                    }
431            }
432    
433            protected void addSearchOwnerUserId(
434                    BooleanQuery contextQuery, SearchContext searchContext) {
435    
436                    long ownerUserId = searchContext.getOwnerUserId();
437    
438                    if (ownerUserId > 0) {
439                            contextQuery.addRequiredTerm(Field.USER_ID, ownerUserId);
440                    }
441            }
442    
443            protected void addSearchPortletIds(
444                            BooleanQuery contextQuery, SearchContext searchContext)
445                    throws Exception {
446    
447                    String[] portletIds = searchContext.getPortletIds();
448    
449                    if ((portletIds == null) || (portletIds.length == 0)) {
450                            contextQuery.addRequiredTerm(
451                                    Field.PORTLET_ID, getPortletId(searchContext));
452                    }
453                    else {
454                            BooleanQuery portletIdsQuery = BooleanQueryFactoryUtil.create();
455    
456                            for (String portletId : portletIds) {
457                                    if (Validator.isNull(portletId)) {
458                                            continue;
459                                    }
460    
461                                    TermQuery termQuery = TermQueryFactoryUtil.create(
462                                            Field.PORTLET_ID, portletId);
463    
464                                    portletIdsQuery.add(termQuery, BooleanClauseOccur.SHOULD);
465                            }
466    
467                            if (!portletIdsQuery.clauses().isEmpty()) {
468                                    contextQuery.add(portletIdsQuery, BooleanClauseOccur.MUST);
469                            }
470                    }
471            }
472    
473            protected void checkSearchCategoryId(
474                            long categoryId, SearchContext searchContext)
475                    throws Exception {
476            }
477    
478            protected void checkSearchFolderId(
479                            long folderId, SearchContext searchContext)
480                    throws Exception {
481            }
482    
483            protected void checkSearchNodeId(
484                            long nodeId, SearchContext searchContext)
485                    throws Exception {
486            }
487    
488            protected BooleanQuery createFullQuery(
489                            BooleanQuery contextQuery, SearchContext searchContext)
490                    throws Exception {
491    
492                    postProcessContextQuery(contextQuery, searchContext);
493    
494                    BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
495    
496                    addSearchKeywords(searchQuery, searchContext);
497                    postProcessSearchQuery(searchQuery, searchContext);
498    
499                    BooleanQuery fullQuery = BooleanQueryFactoryUtil.create();
500    
501                    fullQuery.add(contextQuery, BooleanClauseOccur.MUST);
502    
503                    if (!searchQuery.clauses().isEmpty()) {
504                            fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
505                    }
506    
507                    BooleanClause[] booleanClauses = searchContext.getBooleanClauses();
508    
509                    if (booleanClauses != null) {
510                            for (BooleanClause booleanClause : booleanClauses) {
511                                    fullQuery.add(
512                                            booleanClause.getQuery(),
513                                            booleanClause.getBooleanClauseOccur());
514                            }
515                    }
516    
517                    postProcessFullQuery(fullQuery, searchContext);
518    
519                    return fullQuery;
520            }
521    
522            protected abstract void doDelete(Object obj) throws Exception;
523    
524            protected abstract Document doGetDocument(Object obj) throws Exception;
525    
526            protected abstract void doReindex(Object obj) throws Exception;
527    
528            protected abstract void doReindex(String className, long classPK)
529                    throws Exception;
530    
531            protected abstract void doReindex(String[] ids) throws Exception;
532    
533            protected Hits filterSearch(
534                    Hits hits, PermissionChecker permissionChecker,
535                    SearchContext searchContext) {
536    
537                    List<Document> docs = new ArrayList<Document>();
538                    List<Float> scores = new ArrayList<Float>();
539    
540                    for (int i = 0; i < hits.getLength(); i++) {
541                            Document doc = hits.doc(i);
542    
543                            long entryClassPK = GetterUtil.getLong(
544                                    doc.get(Field.ENTRY_CLASS_PK));
545    
546                            try {
547                                    if (hasPermission(
548                                                    permissionChecker, entryClassPK, ActionKeys.VIEW)) {
549    
550                                            docs.add(hits.doc(i));
551                                            scores.add(hits.score(i));
552                                    }
553                            }
554                            catch (Exception e) {
555                            }
556                    }
557    
558                    int length = docs.size();
559    
560                    hits.setLength(length);
561    
562                    int start = searchContext.getStart();
563                    int end = searchContext.getEnd();
564    
565                    if ((start != QueryUtil.ALL_POS) && (end != QueryUtil.ALL_POS)) {
566                            if (end > length) {
567                                    end = length;
568                            }
569    
570                            docs = docs.subList(start, end);
571                    }
572    
573                    hits.setDocs(docs.toArray(new Document[docs.size()]));
574                    hits.setScores(scores.toArray(new Float[docs.size()]));
575    
576                    hits.setSearchTime(
577                            (float)(System.currentTimeMillis() - hits.getStart()) /
578                                    Time.SECOND);
579    
580                    return hits;
581            }
582    
583            protected String getClassName(SearchContext searchContext) {
584                    String[] classNames = getClassNames();
585    
586                    if (classNames.length != 1) {
587                            throw new UnsupportedOperationException(
588                                    "Search method needs to be manually implemented for " +
589                                            "indexers with more than one class name");
590                    }
591    
592                    return classNames[0];
593            }
594    
595            protected long getParentGroupId(long groupId) {
596                    long parentGroupId = groupId;
597    
598                    try {
599                            Group group = GroupLocalServiceUtil.getGroup(groupId);
600    
601                            if (group.isLayout()) {
602                                    parentGroupId = group.getParentGroupId();
603                            }
604                    }
605                    catch (Exception e) {
606                    }
607    
608                    return parentGroupId;
609            }
610    
611            protected abstract String getPortletId(SearchContext searchContext);
612    
613            protected boolean hasPermission(
614                            PermissionChecker permissionChecker, long entryClassPK,
615                            String actionId)
616                    throws Exception {
617    
618                    return true;
619            }
620    
621            protected boolean isFilterSearch() {
622                    return _FILTER_SEARCH;
623            }
624    
625            protected void postProcessContextQuery(
626                            BooleanQuery contextQuery, SearchContext searchContext)
627                    throws Exception {
628            }
629    
630            protected void postProcessFullQuery(
631                            BooleanQuery fullQuery, SearchContext searchContext)
632                    throws Exception {
633            }
634    
635            protected void postProcessSearchQuery(
636                            BooleanQuery searchQuery, SearchContext searchContext)
637                    throws Exception {
638            }
639    
640            private static final boolean _FILTER_SEARCH = false;
641    
642            public static final int INDEX_FILTER_SEARCH_LIMIT = GetterUtil.getInteger(
643                    PropsUtil.get(PropsKeys.INDEX_FILTER_SEARCH_LIMIT));
644    
645            private static final String[] _KEYWORDS_FIELDS = {
646                    Field.COMMENTS, Field.CONTENT, Field.DESCRIPTION, Field.PROPERTIES,
647                    Field.TITLE, Field.URL, Field.USER_NAME
648            };
649    
650            private static Log _log = LogFactoryUtil.getLog(BaseIndexer.class);
651    
652    }