001    /**
002     * Copyright (c) 2000-present 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.portlet.messageboards.util;
016    
017    import com.liferay.portal.kernel.comment.Comment;
018    import com.liferay.portal.kernel.comment.CommentManagerUtil;
019    import com.liferay.portal.kernel.dao.orm.ActionableDynamicQuery;
020    import com.liferay.portal.kernel.dao.orm.DynamicQuery;
021    import com.liferay.portal.kernel.dao.orm.Property;
022    import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
023    import com.liferay.portal.kernel.exception.PortalException;
024    import com.liferay.portal.kernel.log.Log;
025    import com.liferay.portal.kernel.log.LogFactoryUtil;
026    import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslatorUtil;
027    import com.liferay.portal.kernel.repository.model.FileEntry;
028    import com.liferay.portal.kernel.search.BaseIndexer;
029    import com.liferay.portal.kernel.search.BaseRelatedEntryIndexer;
030    import com.liferay.portal.kernel.search.BooleanClauseOccur;
031    import com.liferay.portal.kernel.search.Document;
032    import com.liferay.portal.kernel.search.Field;
033    import com.liferay.portal.kernel.search.Indexer;
034    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
035    import com.liferay.portal.kernel.search.RelatedEntryIndexer;
036    import com.liferay.portal.kernel.search.SearchContext;
037    import com.liferay.portal.kernel.search.SearchEngineUtil;
038    import com.liferay.portal.kernel.search.Summary;
039    import com.liferay.portal.kernel.search.filter.BooleanFilter;
040    import com.liferay.portal.kernel.search.filter.TermsFilter;
041    import com.liferay.portal.kernel.spring.osgi.OSGiBeanProperties;
042    import com.liferay.portal.kernel.util.GetterUtil;
043    import com.liferay.portal.kernel.util.HtmlUtil;
044    import com.liferay.portal.kernel.workflow.WorkflowConstants;
045    import com.liferay.portal.model.Group;
046    import com.liferay.portal.security.permission.ActionKeys;
047    import com.liferay.portal.security.permission.PermissionChecker;
048    import com.liferay.portal.service.GroupLocalServiceUtil;
049    import com.liferay.portlet.messageboards.model.MBCategory;
050    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
051    import com.liferay.portlet.messageboards.model.MBDiscussion;
052    import com.liferay.portlet.messageboards.model.MBMessage;
053    import com.liferay.portlet.messageboards.service.MBCategoryLocalServiceUtil;
054    import com.liferay.portlet.messageboards.service.MBCategoryServiceUtil;
055    import com.liferay.portlet.messageboards.service.MBDiscussionLocalServiceUtil;
056    import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;
057    import com.liferay.portlet.messageboards.service.permission.MBMessagePermission;
058    
059    import java.util.List;
060    import java.util.Locale;
061    
062    import javax.portlet.PortletRequest;
063    import javax.portlet.PortletResponse;
064    
065    /**
066     * @author Brian Wing Shun Chan
067     * @author Harry Mark
068     * @author Bruno Farache
069     * @author Raymond Aug??
070     */
071    @OSGiBeanProperties
072    public class MBMessageIndexer
073            extends BaseIndexer<MBMessage> implements RelatedEntryIndexer {
074    
075            public static final String CLASS_NAME = MBMessage.class.getName();
076    
077            public MBMessageIndexer() {
078                    setDefaultSelectedFieldNames(
079                            Field.ASSET_TAG_NAMES, Field.CLASS_NAME_ID, Field.CLASS_PK,
080                            Field.COMPANY_ID, Field.CONTENT, Field.ENTRY_CLASS_NAME,
081                            Field.ENTRY_CLASS_PK, Field.GROUP_ID, Field.MODIFIED_DATE,
082                            Field.SCOPE_GROUP_ID, Field.TITLE, Field.UID);
083                    setFilterSearch(true);
084                    setPermissionAware(true);
085            }
086    
087            @Override
088            public void addRelatedClassNames(
089                            BooleanFilter contextFilter, SearchContext searchContext)
090                    throws Exception {
091    
092                    _relatedEntryIndexer.addRelatedClassNames(contextFilter, searchContext);
093            }
094    
095            @Override
096            public void addRelatedEntryFields(Document document, Object obj)
097                    throws Exception {
098    
099                    FileEntry fileEntry = (FileEntry)obj;
100    
101                    MBMessage message = MBMessageAttachmentsUtil.fetchMessage(
102                            fileEntry.getFileEntryId());
103    
104                    if (message == null) {
105                            return;
106                    }
107    
108                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
109    
110                    document.addKeyword("discussion", false);
111                    document.addKeyword("threadId", message.getThreadId());
112            }
113    
114            @Override
115            public String getClassName() {
116                    return CLASS_NAME;
117            }
118    
119            @Override
120            public boolean hasPermission(
121                            PermissionChecker permissionChecker, String entryClassName,
122                            long entryClassPK, String actionId)
123                    throws Exception {
124    
125                    MBMessage message = MBMessageLocalServiceUtil.getMessage(entryClassPK);
126    
127                    if (message.isDiscussion()) {
128                            Indexer<?> indexer = IndexerRegistryUtil.getIndexer(
129                                    message.getClassName());
130    
131                            return indexer.hasPermission(
132                                    permissionChecker, message.getClassName(), message.getClassPK(),
133                                    ActionKeys.VIEW);
134                    }
135    
136                    return MBMessagePermission.contains(
137                            permissionChecker, entryClassPK, ActionKeys.VIEW);
138            }
139    
140            @Override
141            public boolean isVisible(long classPK, int status) throws Exception {
142                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
143    
144                    return isVisible(message.getStatus(), status);
145            }
146    
147            @Override
148            public boolean isVisibleRelatedEntry(long classPK, int status)
149                    throws Exception {
150    
151                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
152    
153                    if (message.isDiscussion()) {
154                            Indexer<?> indexer = IndexerRegistryUtil.getIndexer(
155                                    message.getClassName());
156    
157                            return indexer.isVisible(message.getClassPK(), status);
158                    }
159    
160                    return true;
161            }
162    
163            @Override
164            public void postProcessContextBooleanFilter(
165                            BooleanFilter contextBooleanFilter, SearchContext searchContext)
166                    throws Exception {
167    
168                    addStatus(contextBooleanFilter, searchContext);
169    
170                    boolean discussion = GetterUtil.getBoolean(
171                            searchContext.getAttribute("discussion"), false);
172    
173                    contextBooleanFilter.addRequiredTerm("discussion", discussion);
174    
175                    if (searchContext.isIncludeDiscussions()) {
176                            addRelatedClassNames(contextBooleanFilter, searchContext);
177                    }
178    
179                    long threadId = GetterUtil.getLong(
180                            (String)searchContext.getAttribute("threadId"));
181    
182                    if (threadId > 0) {
183                            contextBooleanFilter.addRequiredTerm("threadId", threadId);
184                    }
185    
186                    long[] categoryIds = searchContext.getCategoryIds();
187    
188                    if ((categoryIds != null) && (categoryIds.length > 0) &&
189                            (categoryIds[0] !=
190                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID)) {
191    
192                            TermsFilter categoriesTermsFilter = new TermsFilter(
193                                    Field.CATEGORY_ID);
194    
195                            for (long categoryId : categoryIds) {
196                                    try {
197                                            MBCategoryServiceUtil.getCategory(categoryId);
198                                    }
199                                    catch (PortalException pe) {
200                                            if (_log.isDebugEnabled()) {
201                                                    _log.debug(
202                                                            "Unable to get message boards category " +
203                                                                    categoryId,
204                                                            pe);
205                                            }
206    
207                                            continue;
208                                    }
209    
210                                    categoriesTermsFilter.addValue(String.valueOf(categoryId));
211                            }
212    
213                            if (!categoriesTermsFilter.isEmpty()) {
214                                    contextBooleanFilter.add(
215                                            categoriesTermsFilter, BooleanClauseOccur.MUST);
216                            }
217                    }
218            }
219    
220            @Override
221            public void updateFullQuery(SearchContext searchContext) {
222                    if (searchContext.isIncludeDiscussions()) {
223                            searchContext.addFullQueryEntryClassName(MBMessage.class.getName());
224    
225                            searchContext.setAttribute("discussion", Boolean.TRUE);
226                    }
227            }
228    
229            @Override
230            protected void doDelete(MBMessage mbMessage) throws Exception {
231                    deleteDocument(mbMessage.getCompanyId(), mbMessage.getMessageId());
232            }
233    
234            @Override
235            protected Document doGetDocument(MBMessage mbMessage) throws Exception {
236                    Document document = getBaseModelDocument(CLASS_NAME, mbMessage);
237    
238                    document.addKeyword(Field.CATEGORY_ID, mbMessage.getCategoryId());
239                    document.addText(Field.CONTENT, processContent(mbMessage));
240                    document.addKeyword(
241                            Field.ROOT_ENTRY_CLASS_PK, mbMessage.getRootMessageId());
242                    document.addText(Field.TITLE, mbMessage.getSubject());
243    
244                    if (mbMessage.isAnonymous()) {
245                            document.remove(Field.USER_NAME);
246                    }
247    
248                    MBDiscussion discussion =
249                            MBDiscussionLocalServiceUtil.fetchThreadDiscussion(
250                                    mbMessage.getThreadId());
251    
252                    if (discussion == null) {
253                            document.addKeyword("discussion", false);
254                    }
255                    else {
256                            document.addKeyword("discussion", true);
257                    }
258    
259                    document.addKeyword("threadId", mbMessage.getThreadId());
260    
261                    if (mbMessage.isDiscussion()) {
262                            Indexer<?> indexer = IndexerRegistryUtil.getIndexer(
263                                    mbMessage.getClassName());
264    
265                            if ((indexer != null) && (indexer instanceof RelatedEntryIndexer)) {
266                                    RelatedEntryIndexer relatedEntryIndexer =
267                                            (RelatedEntryIndexer)indexer;
268    
269                                    Comment comment = CommentManagerUtil.fetchComment(
270                                            mbMessage.getMessageId());
271    
272                                    if (comment != null) {
273                                            relatedEntryIndexer.addRelatedEntryFields(
274                                                    document, comment);
275    
276                                            document.addKeyword(Field.RELATED_ENTRY, true);
277                                    }
278                            }
279                    }
280    
281                    return document;
282            }
283    
284            @Override
285            protected Summary doGetSummary(
286                    Document document, Locale locale, String snippet,
287                    PortletRequest portletRequest, PortletResponse portletResponse) {
288    
289                    Summary summary = createSummary(document, Field.TITLE, Field.CONTENT);
290    
291                    summary.setMaxContentLength(200);
292    
293                    return summary;
294            }
295    
296            @Override
297            protected void doReindex(MBMessage mbMessage) throws Exception {
298                    if (!mbMessage.isApproved() && !mbMessage.isInTrash()) {
299                            return;
300                    }
301    
302                    if (mbMessage.isDiscussion() && mbMessage.isRoot()) {
303                            return;
304                    }
305    
306                    Document document = getDocument(mbMessage);
307    
308                    SearchEngineUtil.updateDocument(
309                            getSearchEngineId(), mbMessage.getCompanyId(), document,
310                            isCommitImmediately());
311            }
312    
313            @Override
314            protected void doReindex(String className, long classPK) throws Exception {
315                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
316    
317                    doReindex(message);
318    
319                    if (message.isRoot()) {
320                            List<MBMessage> messages =
321                                    MBMessageLocalServiceUtil.getThreadMessages(
322                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
323    
324                            for (MBMessage curMessage : messages) {
325                                    reindex(curMessage);
326                            }
327                    }
328                    else {
329                            reindex(message);
330                    }
331            }
332    
333            @Override
334            protected void doReindex(String[] ids) throws Exception {
335                    long companyId = GetterUtil.getLong(ids[0]);
336    
337                    reindexCategories(companyId);
338                    reindexDiscussions(companyId);
339                    reindexRoot(companyId);
340            }
341    
342            protected String processContent(MBMessage message) {
343                    String content = message.getBody();
344    
345                    try {
346                            if (message.isFormatBBCode()) {
347                                    content = BBCodeTranslatorUtil.getHTML(content);
348                            }
349                    }
350                    catch (Exception e) {
351                            _log.error(
352                                    "Could not parse message " + message.getMessageId() + ": " +
353                                            e.getMessage());
354                    }
355    
356                    content = HtmlUtil.extractText(content);
357    
358                    return content;
359            }
360    
361            protected void reindexCategories(final long companyId)
362                    throws PortalException {
363    
364                    ActionableDynamicQuery actionableDynamicQuery =
365                            MBCategoryLocalServiceUtil.getActionableDynamicQuery();
366    
367                    actionableDynamicQuery.setCompanyId(companyId);
368                    actionableDynamicQuery.setPerformActionMethod(
369                            new ActionableDynamicQuery.PerformActionMethod<MBCategory>() {
370    
371                                    @Override
372                                    public void performAction(MBCategory category)
373                                            throws PortalException {
374    
375                                            reindexMessages(
376                                                    companyId, category.getGroupId(),
377                                                    category.getCategoryId());
378                                    }
379    
380                            });
381    
382                    actionableDynamicQuery.performActions();
383            }
384    
385            protected void reindexDiscussions(final long companyId)
386                    throws PortalException {
387    
388                    ActionableDynamicQuery actionableDynamicQuery =
389                            GroupLocalServiceUtil.getActionableDynamicQuery();
390    
391                    actionableDynamicQuery.setCompanyId(companyId);
392                    actionableDynamicQuery.setPerformActionMethod(
393                            new ActionableDynamicQuery.PerformActionMethod<Group>() {
394    
395                                    @Override
396                                    public void performAction(Group group) throws PortalException {
397                                            reindexMessages(
398                                                    companyId, group.getGroupId(),
399                                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID);
400                                    }
401    
402                            });
403    
404                    actionableDynamicQuery.performActions();
405            }
406    
407            protected void reindexMessages(
408                            long companyId, long groupId, final long categoryId)
409                    throws PortalException {
410    
411                    final ActionableDynamicQuery actionableDynamicQuery =
412                            MBMessageLocalServiceUtil.getActionableDynamicQuery();
413    
414                    actionableDynamicQuery.setAddCriteriaMethod(
415                            new ActionableDynamicQuery.AddCriteriaMethod() {
416    
417                                    @Override
418                                    public void addCriteria(DynamicQuery dynamicQuery) {
419                                            Property categoryIdProperty = PropertyFactoryUtil.forName(
420                                                    "categoryId");
421    
422                                            dynamicQuery.add(categoryIdProperty.eq(categoryId));
423    
424                                            Property statusProperty = PropertyFactoryUtil.forName(
425                                                    "status");
426    
427                                            Integer[] statuses = {
428                                                    WorkflowConstants.STATUS_APPROVED,
429                                                    WorkflowConstants.STATUS_IN_TRASH
430                                            };
431    
432                                            dynamicQuery.add(statusProperty.in(statuses));
433                                    }
434    
435                            });
436                    actionableDynamicQuery.setCompanyId(companyId);
437                    actionableDynamicQuery.setGroupId(groupId);
438                    actionableDynamicQuery.setPerformActionMethod(
439                            new ActionableDynamicQuery.PerformActionMethod<MBMessage>() {
440    
441                                    @Override
442                                    public void performAction(MBMessage message) {
443                                            if (message.isDiscussion() && message.isRoot()) {
444                                                    return;
445                                            }
446    
447                                            try {
448                                                    Document document = getDocument(message);
449    
450                                                    actionableDynamicQuery.addDocument(document);
451                                            }
452                                            catch (PortalException pe) {
453                                                    if (_log.isWarnEnabled()) {
454                                                            _log.warn(
455                                                                    "Unable to index message boards message " +
456                                                                            message.getMessageId(),
457                                                                    pe);
458                                                    }
459                                            }
460                                    }
461    
462                            });
463                    actionableDynamicQuery.setSearchEngineId(getSearchEngineId());
464    
465                    actionableDynamicQuery.performActions();
466            }
467    
468            protected void reindexRoot(final long companyId) throws PortalException {
469                    ActionableDynamicQuery actionableDynamicQuery =
470                            GroupLocalServiceUtil.getActionableDynamicQuery();
471    
472                    actionableDynamicQuery.setCompanyId(companyId);
473                    actionableDynamicQuery.setPerformActionMethod(
474                            new ActionableDynamicQuery.PerformActionMethod<Group>() {
475    
476                                    @Override
477                                    public void performAction(Group group) throws PortalException {
478                                            reindexMessages(
479                                                    companyId, group.getGroupId(),
480                                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID);
481                                    }
482    
483                            });
484    
485                    actionableDynamicQuery.performActions();
486            }
487    
488            private static final Log _log = LogFactoryUtil.getLog(
489                    MBMessageIndexer.class);
490    
491            private final RelatedEntryIndexer _relatedEntryIndexer =
492                    new BaseRelatedEntryIndexer();
493    
494    }