001    /**
002     * Copyright (c) 2000-2013 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.dao.orm.ActionableDynamicQuery;
018    import com.liferay.portal.kernel.dao.orm.DynamicQuery;
019    import com.liferay.portal.kernel.dao.orm.Property;
020    import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
021    import com.liferay.portal.kernel.exception.PortalException;
022    import com.liferay.portal.kernel.exception.SystemException;
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.search.BaseIndexer;
027    import com.liferay.portal.kernel.search.BooleanClauseOccur;
028    import com.liferay.portal.kernel.search.BooleanQuery;
029    import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
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.SearchContext;
035    import com.liferay.portal.kernel.search.SearchEngineUtil;
036    import com.liferay.portal.kernel.search.Summary;
037    import com.liferay.portal.kernel.util.GetterUtil;
038    import com.liferay.portal.kernel.util.HtmlUtil;
039    import com.liferay.portal.kernel.workflow.WorkflowConstants;
040    import com.liferay.portal.model.Group;
041    import com.liferay.portal.security.permission.ActionKeys;
042    import com.liferay.portal.security.permission.PermissionChecker;
043    import com.liferay.portal.service.persistence.GroupActionableDynamicQuery;
044    import com.liferay.portal.util.PortalUtil;
045    import com.liferay.portal.util.PortletKeys;
046    import com.liferay.portlet.documentlibrary.model.DLFileEntry;
047    import com.liferay.portlet.messageboards.NoSuchDiscussionException;
048    import com.liferay.portlet.messageboards.model.MBCategory;
049    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
050    import com.liferay.portlet.messageboards.model.MBMessage;
051    import com.liferay.portlet.messageboards.service.MBCategoryServiceUtil;
052    import com.liferay.portlet.messageboards.service.MBDiscussionLocalServiceUtil;
053    import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;
054    import com.liferay.portlet.messageboards.service.permission.MBMessagePermission;
055    import com.liferay.portlet.messageboards.service.persistence.MBCategoryActionableDynamicQuery;
056    import com.liferay.portlet.messageboards.service.persistence.MBMessageActionableDynamicQuery;
057    
058    import javax.portlet.PortletURL;
059    import java.util.List;
060    import java.util.Locale;
061    
062    /**
063     * @author Brian Wing Shun Chan
064     * @author Harry Mark
065     * @author Bruno Farache
066     * @author Raymond Aug??
067     */
068    public class MBMessageIndexer extends BaseIndexer {
069    
070            public static final String[] CLASS_NAMES = {MBMessage.class.getName()};
071    
072            public static final String PORTLET_ID = PortletKeys.MESSAGE_BOARDS;
073    
074            public MBMessageIndexer() {
075                    setFilterSearch(true);
076                    setPermissionAware(true);
077            }
078    
079            @Override
080            public void addRelatedEntryFields(Document document, Object obj)
081                    throws Exception {
082    
083                    DLFileEntry dlFileEntry = (DLFileEntry)obj;
084    
085                    MBMessage message = null;
086    
087                    try {
088                            message = MBMessageAttachmentsUtil.getMessage(
089                                    dlFileEntry.getFileEntryId());
090                    }
091                    catch (Exception e) {
092                            return;
093                    }
094    
095                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
096                    document.addKeyword(
097                            Field.CLASS_NAME_ID,
098                            PortalUtil.getClassNameId(MBMessage.class.getName()));
099                    document.addKeyword(Field.CLASS_PK, message.getMessageId());
100                    document.addKeyword(Field.RELATED_ENTRY, true);
101    
102                    document.addKeyword("discussion", false);
103                    document.addKeyword("threadId", message.getThreadId());
104            }
105    
106            @Override
107            public String[] getClassNames() {
108                    return CLASS_NAMES;
109            }
110    
111            @Override
112            public String getPortletId() {
113                    return PORTLET_ID;
114            }
115    
116            @Override
117            public boolean hasPermission(
118                            PermissionChecker permissionChecker, String entryClassName,
119                            long entryClassPK, String actionId)
120                    throws Exception {
121    
122                    return MBMessagePermission.contains(
123                            permissionChecker, entryClassPK, ActionKeys.VIEW);
124            }
125    
126            @Override
127            public void postProcessContextQuery(
128                            BooleanQuery contextQuery, SearchContext searchContext)
129                    throws Exception {
130    
131                    addStatus(contextQuery, searchContext);
132    
133                    boolean discussion = GetterUtil.getBoolean(
134                            searchContext.getAttribute("discussion"), false);
135    
136                    contextQuery.addRequiredTerm("discussion", discussion);
137    
138                    if (searchContext.isIncludeDiscussions()) {
139                            addRelatedClassNames(contextQuery, searchContext);
140                    }
141    
142                    long threadId = GetterUtil.getLong(
143                            (String)searchContext.getAttribute("threadId"));
144    
145                    if (threadId > 0) {
146                            contextQuery.addRequiredTerm("threadId", threadId);
147                    }
148    
149                    long[] categoryIds = searchContext.getCategoryIds();
150    
151                    if ((categoryIds != null) && (categoryIds.length > 0) &&
152                            (categoryIds[0] !=
153                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID)) {
154    
155                            BooleanQuery categoriesQuery = BooleanQueryFactoryUtil.create(
156                                    searchContext);
157    
158                            for (long categoryId : categoryIds) {
159                                    try {
160                                            MBCategoryServiceUtil.getCategory(categoryId);
161                                    }
162                                    catch (Exception e) {
163                                            continue;
164                                    }
165    
166                                    categoriesQuery.addTerm(Field.CATEGORY_ID, categoryId);
167                            }
168    
169                            contextQuery.add(categoriesQuery, BooleanClauseOccur.MUST);
170                    }
171            }
172    
173            @Override
174            protected void doDelete(Object obj) throws Exception {
175                    MBMessage message = (MBMessage)obj;
176    
177                    deleteDocument(message.getCompanyId(), message.getMessageId());
178            }
179    
180            @Override
181            protected Document doGetDocument(Object obj) throws Exception {
182                    MBMessage message = (MBMessage)obj;
183    
184                    Document document = getBaseModelDocument(PORTLET_ID, message);
185    
186                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
187                    document.addText(Field.CONTENT, processContent(message));
188                    document.addKeyword(
189                            Field.ROOT_ENTRY_CLASS_PK, message.getRootMessageId());
190                    document.addText(Field.TITLE, message.getSubject());
191    
192                    if (message.isAnonymous()) {
193                            document.remove(Field.USER_NAME);
194                    }
195    
196                    try {
197                            MBDiscussionLocalServiceUtil.getThreadDiscussion(
198                                    message.getThreadId());
199    
200                            document.addKeyword("discussion", true);
201                    }
202                    catch (NoSuchDiscussionException nsde) {
203                            document.addKeyword("discussion", false);
204                    }
205    
206                    document.addKeyword("threadId", message.getThreadId());
207    
208                    if (message.isDiscussion()) {
209                            Indexer indexer = IndexerRegistryUtil.getIndexer(
210                                    message.getClassName());
211    
212                            if (indexer != null) {
213                                    indexer.addRelatedEntryFields(document, obj);
214                            }
215                    }
216    
217                    return document;
218            }
219    
220            @Override
221            protected Summary doGetSummary(
222                    Document document, Locale locale, String snippet,
223                    PortletURL portletURL) {
224    
225                    String messageId = document.get(Field.ENTRY_CLASS_PK);
226    
227                    portletURL.setParameter(
228                            "struts_action", "/message_boards/view_message");
229                    portletURL.setParameter("messageId", messageId);
230    
231                    Summary summary = createSummary(document, Field.TITLE, Field.CONTENT);
232    
233                    summary.setMaxContentLength(200);
234                    summary.setPortletURL(portletURL);
235    
236                    return summary;
237            }
238    
239            @Override
240            protected void doReindex(Object obj) throws Exception {
241                    MBMessage message = (MBMessage)obj;
242    
243                    if (!message.isApproved() && !message.isInTrash()) {
244                            return;
245                    }
246    
247                    if (message.isDiscussion() && message.isRoot()) {
248                            return;
249                    }
250    
251                    Document document = getDocument(message);
252    
253                    SearchEngineUtil.updateDocument(
254                            getSearchEngineId(), message.getCompanyId(), document);
255            }
256    
257            @Override
258            protected void doReindex(String className, long classPK) throws Exception {
259                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
260    
261                    doReindex(message);
262    
263                    if (message.isRoot()) {
264                            List<MBMessage> messages =
265                                    MBMessageLocalServiceUtil.getThreadMessages(
266                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
267    
268                            for (MBMessage curMessage : messages) {
269                                    reindex(curMessage);
270                            }
271                    }
272                    else {
273                            reindex(message);
274                    }
275            }
276    
277            @Override
278            protected void doReindex(String[] ids) throws Exception {
279                    long companyId = GetterUtil.getLong(ids[0]);
280    
281                    reindexCategories(companyId);
282                    reindexDiscussions(companyId);
283                    reindexRoot(companyId);
284            }
285    
286            @Override
287            protected String getPortletId(SearchContext searchContext) {
288                    return PORTLET_ID;
289            }
290    
291            protected String processContent(MBMessage message) {
292                    String content = message.getBody();
293    
294                    try {
295                            if (message.isFormatBBCode()) {
296                                    content = BBCodeTranslatorUtil.getHTML(content);
297                            }
298                    }
299                    catch (Exception e) {
300                            _log.error(
301                                    "Could not parse message " + message.getMessageId() + ": " +
302                                            e.getMessage());
303                    }
304    
305                    content = HtmlUtil.extractText(content);
306    
307                    return content;
308            }
309    
310            protected void reindexCategories(final long companyId)
311                    throws PortalException, SystemException {
312    
313                    ActionableDynamicQuery actionableDynamicQuery =
314                            new MBCategoryActionableDynamicQuery() {
315    
316                            @Override
317                            protected void performAction(Object object)
318                                    throws PortalException, SystemException {
319    
320                                    MBCategory category = (MBCategory)object;
321    
322                                    reindexMessages(
323                                            companyId, category.getGroupId(), category.getCategoryId());
324                            }
325    
326                    };
327    
328                    actionableDynamicQuery.setCompanyId(companyId);
329    
330                    actionableDynamicQuery.performActions();
331            }
332    
333            protected void reindexDiscussions(final long companyId)
334                    throws PortalException, SystemException {
335    
336                    ActionableDynamicQuery actionableDynamicQuery =
337                            new GroupActionableDynamicQuery() {
338    
339                            @Override
340                            protected void performAction(Object object)
341                                    throws PortalException, SystemException {
342    
343                                    Group group = (Group)object;
344    
345                                    reindexMessages(
346                                            companyId, group.getGroupId(),
347                                            MBCategoryConstants.DISCUSSION_CATEGORY_ID);
348                            }
349    
350                    };
351    
352                    actionableDynamicQuery.setCompanyId(companyId);
353    
354                    actionableDynamicQuery.performActions();
355            }
356    
357            protected void reindexMessages(
358                            long companyId, long groupId, final long categoryId)
359                    throws PortalException, SystemException {
360    
361                    ActionableDynamicQuery actionableDynamicQuery =
362                            new MBMessageActionableDynamicQuery() {
363    
364                            @Override
365                            protected void addCriteria(DynamicQuery dynamicQuery) {
366                                    Property categoryIdProperty = PropertyFactoryUtil.forName(
367                                            "categoryId");
368    
369                                    dynamicQuery.add(categoryIdProperty.eq(categoryId));
370    
371                                    Property statusProperty = PropertyFactoryUtil.forName("status");
372    
373                                    Integer[] statuses = {
374                                            WorkflowConstants.STATUS_APPROVED,
375                                            WorkflowConstants.STATUS_IN_TRASH
376                                    };
377    
378                                    dynamicQuery.add(statusProperty.in(statuses));
379                            }
380    
381                            @Override
382                            protected void performAction(Object object) throws PortalException {
383                                    MBMessage message = (MBMessage)object;
384    
385                                    if (message.isDiscussion() && message.isRoot()) {
386                                            return;
387                                    }
388    
389                                    Document document = getDocument(message);
390    
391                                    addDocument(document);
392                            }
393    
394                    };
395    
396                    actionableDynamicQuery.setCompanyId(companyId);
397                    actionableDynamicQuery.setGroupId(groupId);
398                    actionableDynamicQuery.setSearchEngineId(getSearchEngineId());
399    
400                    actionableDynamicQuery.performActions();
401            }
402    
403            protected void reindexRoot(final long companyId)
404                    throws PortalException, SystemException {
405    
406                    ActionableDynamicQuery actionableDynamicQuery =
407                            new GroupActionableDynamicQuery() {
408    
409                            @Override
410                            protected void performAction(Object object)
411                                    throws PortalException, SystemException {
412    
413                                    Group group = (Group)object;
414    
415                                    reindexMessages(
416                                            companyId, group.getGroupId(),
417                                            MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID);
418                            }
419    
420                    };
421    
422                    actionableDynamicQuery.setCompanyId(companyId);
423    
424                    actionableDynamicQuery.performActions();
425            }
426    
427            private static Log _log = LogFactoryUtil.getLog(MBMessageIndexer.class);
428    
429    }