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                            (mbMessage.isDiscussion() && mbMessage.isRoot())) {
301    
302                            return;
303                    }
304    
305                    Document document = getDocument(mbMessage);
306    
307                    SearchEngineUtil.updateDocument(
308                            getSearchEngineId(), mbMessage.getCompanyId(), document,
309                            isCommitImmediately());
310            }
311    
312            @Override
313            protected void doReindex(String className, long classPK) throws Exception {
314                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
315    
316                    doReindex(message);
317    
318                    if (message.isRoot()) {
319                            List<MBMessage> messages =
320                                    MBMessageLocalServiceUtil.getThreadMessages(
321                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
322    
323                            for (MBMessage curMessage : messages) {
324                                    reindex(curMessage);
325                            }
326                    }
327                    else {
328                            reindex(message);
329                    }
330            }
331    
332            @Override
333            protected void doReindex(String[] ids) throws Exception {
334                    long companyId = GetterUtil.getLong(ids[0]);
335    
336                    reindexCategories(companyId);
337                    reindexDiscussions(companyId);
338                    reindexRoot(companyId);
339            }
340    
341            protected String processContent(MBMessage message) {
342                    String content = message.getBody();
343    
344                    try {
345                            if (message.isFormatBBCode()) {
346                                    content = BBCodeTranslatorUtil.getHTML(content);
347                            }
348                    }
349                    catch (Exception e) {
350                            _log.error(
351                                    "Could not parse message " + message.getMessageId() + ": " +
352                                            e.getMessage());
353                    }
354    
355                    content = HtmlUtil.extractText(content);
356    
357                    return content;
358            }
359    
360            protected void reindexCategories(final long companyId)
361                    throws PortalException {
362    
363                    ActionableDynamicQuery actionableDynamicQuery =
364                            MBCategoryLocalServiceUtil.getActionableDynamicQuery();
365    
366                    actionableDynamicQuery.setCompanyId(companyId);
367                    actionableDynamicQuery.setPerformActionMethod(
368                            new ActionableDynamicQuery.PerformActionMethod<MBCategory>() {
369    
370                                    @Override
371                                    public void performAction(MBCategory category)
372                                            throws PortalException {
373    
374                                            reindexMessages(
375                                                    companyId, category.getGroupId(),
376                                                    category.getCategoryId());
377                                    }
378    
379                            });
380    
381                    actionableDynamicQuery.performActions();
382            }
383    
384            protected void reindexDiscussions(final long companyId)
385                    throws PortalException {
386    
387                    ActionableDynamicQuery actionableDynamicQuery =
388                            GroupLocalServiceUtil.getActionableDynamicQuery();
389    
390                    actionableDynamicQuery.setCompanyId(companyId);
391                    actionableDynamicQuery.setPerformActionMethod(
392                            new ActionableDynamicQuery.PerformActionMethod<Group>() {
393    
394                                    @Override
395                                    public void performAction(Group group) throws PortalException {
396                                            reindexMessages(
397                                                    companyId, group.getGroupId(),
398                                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID);
399                                    }
400    
401                            });
402    
403                    actionableDynamicQuery.performActions();
404            }
405    
406            protected void reindexMessages(
407                            long companyId, long groupId, final long categoryId)
408                    throws PortalException {
409    
410                    final IndexableActionableDynamicQuery indexableActionableDynamicQuery =
411                            MBMessageLocalServiceUtil.getIndexableActionableDynamicQuery();
412    
413                    indexableActionableDynamicQuery.setAddCriteriaMethod(
414                            new ActionableDynamicQuery.AddCriteriaMethod() {
415    
416                                    @Override
417                                    public void addCriteria(DynamicQuery dynamicQuery) {
418                                            Property categoryIdProperty = PropertyFactoryUtil.forName(
419                                                    "categoryId");
420    
421                                            dynamicQuery.add(categoryIdProperty.eq(categoryId));
422    
423                                            Property statusProperty = PropertyFactoryUtil.forName(
424                                                    "status");
425    
426                                            Integer[] statuses = {
427                                                    WorkflowConstants.STATUS_APPROVED,
428                                                    WorkflowConstants.STATUS_IN_TRASH
429                                            };
430    
431                                            dynamicQuery.add(statusProperty.in(statuses));
432                                    }
433    
434                            });
435                    indexableActionableDynamicQuery.setCompanyId(companyId);
436                    indexableActionableDynamicQuery.setGroupId(groupId);
437                    indexableActionableDynamicQuery.setPerformActionMethod(
438                            new ActionableDynamicQuery.PerformActionMethod<MBMessage>() {
439    
440                                    @Override
441                                    public void performAction(MBMessage message) {
442                                            if (message.isDiscussion() && message.isRoot()) {
443                                                    return;
444                                            }
445    
446                                            try {
447                                                    Document document = getDocument(message);
448    
449                                                    indexableActionableDynamicQuery.addDocuments(document);
450                                            }
451                                            catch (PortalException pe) {
452                                                    if (_log.isWarnEnabled()) {
453                                                            _log.warn(
454                                                                    "Unable to index message boards message " +
455                                                                            message.getMessageId(),
456                                                                    pe);
457                                                    }
458                                            }
459                                    }
460    
461                            });
462                    indexableActionableDynamicQuery.setSearchEngineId(getSearchEngineId());
463    
464                    indexableActionableDynamicQuery.performActions();
465            }
466    
467            protected void reindexRoot(final long companyId) throws PortalException {
468                    ActionableDynamicQuery actionableDynamicQuery =
469                            GroupLocalServiceUtil.getActionableDynamicQuery();
470    
471                    actionableDynamicQuery.setCompanyId(companyId);
472                    actionableDynamicQuery.setPerformActionMethod(
473                            new ActionableDynamicQuery.PerformActionMethod<Group>() {
474    
475                                    @Override
476                                    public void performAction(Group group) throws PortalException {
477                                            reindexMessages(
478                                                    companyId, group.getGroupId(),
479                                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID);
480                                    }
481    
482                            });
483    
484                    actionableDynamicQuery.performActions();
485            }
486    
487            private static final Log _log = LogFactoryUtil.getLog(
488                    MBMessageIndexer.class);
489    
490            private final RelatedEntryIndexer _relatedEntryIndexer =
491                    new BaseRelatedEntryIndexer();
492    
493    }