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.dao.orm.ActionableDynamicQuery;
019    import com.liferay.portal.kernel.dao.orm.DynamicQuery;
020    import com.liferay.portal.kernel.dao.orm.Property;
021    import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
022    import com.liferay.portal.kernel.exception.PortalException;
023    import com.liferay.portal.kernel.log.Log;
024    import com.liferay.portal.kernel.log.LogFactoryUtil;
025    import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslatorUtil;
026    import com.liferay.portal.kernel.repository.model.FileEntry;
027    import com.liferay.portal.kernel.search.BaseIndexer;
028    import com.liferay.portal.kernel.search.BaseRelatedEntryIndexer;
029    import com.liferay.portal.kernel.search.BooleanClauseOccur;
030    import com.liferay.portal.kernel.search.Document;
031    import com.liferay.portal.kernel.search.Field;
032    import com.liferay.portal.kernel.search.Indexer;
033    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
034    import com.liferay.portal.kernel.search.RelatedEntryIndexer;
035    import com.liferay.portal.kernel.search.SearchContext;
036    import com.liferay.portal.kernel.search.SearchEngineUtil;
037    import com.liferay.portal.kernel.search.Summary;
038    import com.liferay.portal.kernel.search.filter.BooleanFilter;
039    import com.liferay.portal.kernel.search.filter.TermsFilter;
040    import com.liferay.portal.kernel.spring.osgi.OSGiBeanProperties;
041    import com.liferay.portal.kernel.util.GetterUtil;
042    import com.liferay.portal.kernel.util.HtmlUtil;
043    import com.liferay.portal.kernel.workflow.WorkflowConstants;
044    import com.liferay.portal.model.Group;
045    import com.liferay.portal.security.permission.ActionKeys;
046    import com.liferay.portal.security.permission.PermissionChecker;
047    import com.liferay.portal.service.GroupLocalServiceUtil;
048    import com.liferay.portlet.messageboards.comment.MBCommentImpl;
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 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(Object obj) throws Exception {
231                    MBMessage message = (MBMessage)obj;
232    
233                    deleteDocument(message.getCompanyId(), message.getMessageId());
234            }
235    
236            @Override
237            protected Document doGetDocument(Object obj) throws Exception {
238                    MBMessage message = (MBMessage)obj;
239    
240                    Document document = getBaseModelDocument(CLASS_NAME, message);
241    
242                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
243                    document.addText(Field.CONTENT, processContent(message));
244                    document.addKeyword(
245                            Field.ROOT_ENTRY_CLASS_PK, message.getRootMessageId());
246                    document.addText(Field.TITLE, message.getSubject());
247    
248                    if (message.isAnonymous()) {
249                            document.remove(Field.USER_NAME);
250                    }
251    
252                    MBDiscussion discussion =
253                            MBDiscussionLocalServiceUtil.fetchThreadDiscussion(
254                                    message.getThreadId());
255    
256                    if (discussion == null) {
257                            document.addKeyword("discussion", false);
258                    }
259                    else {
260                            document.addKeyword("discussion", true);
261                    }
262    
263                    document.addKeyword("threadId", message.getThreadId());
264    
265                    if (message.isDiscussion()) {
266                            Indexer indexer = IndexerRegistryUtil.getIndexer(
267                                    message.getClassName());
268    
269                            if ((indexer != null) && (indexer instanceof RelatedEntryIndexer)) {
270                                    RelatedEntryIndexer relatedEntryIndexer =
271                                            (RelatedEntryIndexer)indexer;
272    
273                                    Comment comment = new MBCommentImpl(message);
274    
275                                    relatedEntryIndexer.addRelatedEntryFields(document, comment);
276    
277                                    document.addKeyword(Field.RELATED_ENTRY, true);
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(Object obj) throws Exception {
298                    MBMessage message = (MBMessage)obj;
299    
300                    if (!message.isApproved() && !message.isInTrash()) {
301                            return;
302                    }
303    
304                    if (message.isDiscussion() && message.isRoot()) {
305                            return;
306                    }
307    
308                    Document document = getDocument(message);
309    
310                    SearchEngineUtil.updateDocument(
311                            getSearchEngineId(), message.getCompanyId(), document,
312                            isCommitImmediately());
313            }
314    
315            @Override
316            protected void doReindex(String className, long classPK) throws Exception {
317                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
318    
319                    doReindex(message);
320    
321                    if (message.isRoot()) {
322                            List<MBMessage> messages =
323                                    MBMessageLocalServiceUtil.getThreadMessages(
324                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
325    
326                            for (MBMessage curMessage : messages) {
327                                    reindex(curMessage);
328                            }
329                    }
330                    else {
331                            reindex(message);
332                    }
333            }
334    
335            @Override
336            protected void doReindex(String[] ids) throws Exception {
337                    long companyId = GetterUtil.getLong(ids[0]);
338    
339                    reindexCategories(companyId);
340                    reindexDiscussions(companyId);
341                    reindexRoot(companyId);
342            }
343    
344            protected String processContent(MBMessage message) {
345                    String content = message.getBody();
346    
347                    try {
348                            if (message.isFormatBBCode()) {
349                                    content = BBCodeTranslatorUtil.getHTML(content);
350                            }
351                    }
352                    catch (Exception e) {
353                            _log.error(
354                                    "Could not parse message " + message.getMessageId() + ": " +
355                                            e.getMessage());
356                    }
357    
358                    content = HtmlUtil.extractText(content);
359    
360                    return content;
361            }
362    
363            protected void reindexCategories(final long companyId)
364                    throws PortalException {
365    
366                    ActionableDynamicQuery actionableDynamicQuery =
367                            MBCategoryLocalServiceUtil.getActionableDynamicQuery();
368    
369                    actionableDynamicQuery.setCompanyId(companyId);
370                    actionableDynamicQuery.setPerformActionMethod(
371                            new ActionableDynamicQuery.PerformActionMethod() {
372    
373                                    @Override
374                                    public void performAction(Object object)
375                                            throws PortalException {
376    
377                                            MBCategory category = (MBCategory)object;
378    
379                                            reindexMessages(
380                                                    companyId, category.getGroupId(),
381                                                    category.getCategoryId());
382                                    }
383    
384                            });
385    
386                    actionableDynamicQuery.performActions();
387            }
388    
389            protected void reindexDiscussions(final long companyId)
390                    throws PortalException {
391    
392                    ActionableDynamicQuery actionableDynamicQuery =
393                            GroupLocalServiceUtil.getActionableDynamicQuery();
394    
395                    actionableDynamicQuery.setCompanyId(companyId);
396                    actionableDynamicQuery.setPerformActionMethod(
397                            new ActionableDynamicQuery.PerformActionMethod() {
398    
399                                    @Override
400                                    public void performAction(Object object)
401                                            throws PortalException {
402    
403                                            Group group = (Group)object;
404    
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 ActionableDynamicQuery actionableDynamicQuery =
420                            MBMessageLocalServiceUtil.getActionableDynamicQuery();
421    
422                    actionableDynamicQuery.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                    actionableDynamicQuery.setCompanyId(companyId);
445                    actionableDynamicQuery.setGroupId(groupId);
446                    actionableDynamicQuery.setPerformActionMethod(
447                            new ActionableDynamicQuery.PerformActionMethod() {
448    
449                                    @Override
450                                    public void performAction(Object object) {
451                                            MBMessage message = (MBMessage)object;
452    
453                                            if (message.isDiscussion() && message.isRoot()) {
454                                                    return;
455                                            }
456    
457                                            try {
458                                                    Document document = getDocument(message);
459    
460                                                    actionableDynamicQuery.addDocument(document);
461                                            }
462                                            catch (PortalException pe) {
463                                                    if (_log.isWarnEnabled()) {
464                                                            _log.warn(
465                                                                    "Unable to index message boards message " +
466                                                                            message.getMessageId(),
467                                                                    pe);
468                                                    }
469                                            }
470                                    }
471    
472                            });
473                    actionableDynamicQuery.setSearchEngineId(getSearchEngineId());
474    
475                    actionableDynamicQuery.performActions();
476            }
477    
478            protected void reindexRoot(final long companyId) throws PortalException {
479                    ActionableDynamicQuery actionableDynamicQuery =
480                            GroupLocalServiceUtil.getActionableDynamicQuery();
481    
482                    actionableDynamicQuery.setCompanyId(companyId);
483                    actionableDynamicQuery.setPerformActionMethod(
484                            new ActionableDynamicQuery.PerformActionMethod() {
485    
486                                    @Override
487                                    public void performAction(Object object)
488                                            throws PortalException {
489    
490                                            Group group = (Group)object;
491    
492                                            reindexMessages(
493                                                    companyId, group.getGroupId(),
494                                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID);
495                                    }
496    
497                            });
498    
499                    actionableDynamicQuery.performActions();
500            }
501    
502            private static final Log _log = LogFactoryUtil.getLog(
503                    MBMessageIndexer.class);
504    
505            private final RelatedEntryIndexer _relatedEntryIndexer =
506                    new BaseRelatedEntryIndexer();
507    
508    }