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