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