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