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