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.DynamicQuery;
018    import com.liferay.portal.kernel.dao.orm.DynamicQueryFactoryUtil;
019    import com.liferay.portal.kernel.dao.orm.Projection;
020    import com.liferay.portal.kernel.dao.orm.ProjectionFactoryUtil;
021    import com.liferay.portal.kernel.dao.orm.ProjectionList;
022    import com.liferay.portal.kernel.dao.orm.Property;
023    import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
024    import com.liferay.portal.kernel.dao.orm.QueryUtil;
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.search.BaseIndexer;
029    import com.liferay.portal.kernel.search.BooleanClauseOccur;
030    import com.liferay.portal.kernel.search.BooleanQuery;
031    import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
032    import com.liferay.portal.kernel.search.Document;
033    import com.liferay.portal.kernel.search.DocumentImpl;
034    import com.liferay.portal.kernel.search.Field;
035    import com.liferay.portal.kernel.search.Hits;
036    import com.liferay.portal.kernel.search.SearchContext;
037    import com.liferay.portal.kernel.search.SearchEngineUtil;
038    import com.liferay.portal.kernel.search.Summary;
039    import com.liferay.portal.kernel.util.GetterUtil;
040    import com.liferay.portal.kernel.util.HtmlUtil;
041    import com.liferay.portal.kernel.workflow.WorkflowConstants;
042    import com.liferay.portal.model.Group;
043    import com.liferay.portal.security.pacl.PACLClassLoaderUtil;
044    import com.liferay.portal.security.permission.ActionKeys;
045    import com.liferay.portal.security.permission.PermissionChecker;
046    import com.liferay.portal.service.GroupLocalServiceUtil;
047    import com.liferay.portal.util.PortletKeys;
048    import com.liferay.portlet.messageboards.NoSuchDiscussionException;
049    import com.liferay.portlet.messageboards.model.MBCategory;
050    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
051    import com.liferay.portlet.messageboards.model.MBMessage;
052    import com.liferay.portlet.messageboards.model.MBThread;
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.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_ANY);
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            protected void addReindexCriteria(
154                    DynamicQuery dynamicQuery, long companyId) {
155    
156                    Property property = PropertyFactoryUtil.forName("companyId");
157    
158                    dynamicQuery.add(property.eq(companyId));
159            }
160    
161            protected void addReindexCriteria(
162                    DynamicQuery dynamicQuery, long groupId, long categoryId) {
163    
164                    Property groupIdProperty = PropertyFactoryUtil.forName("groupId");
165    
166                    dynamicQuery.add(groupIdProperty.eq(groupId));
167    
168                    Property categoryIdProperty = PropertyFactoryUtil.forName("categoryId");
169    
170                    dynamicQuery.add(categoryIdProperty.eq(categoryId));
171    
172                    Property statusProperty = PropertyFactoryUtil.forName("status");
173    
174                    dynamicQuery.add(statusProperty.eq(WorkflowConstants.STATUS_APPROVED));
175            }
176    
177            @Override
178            protected void doDelete(Object obj) throws Exception {
179                    SearchContext searchContext = new SearchContext();
180    
181                    searchContext.setSearchEngineId(getSearchEngineId());
182    
183                    if (obj instanceof MBCategory) {
184                            MBCategory category = (MBCategory)obj;
185    
186                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
187                                    searchContext);
188    
189                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
190    
191                            booleanQuery.addRequiredTerm(
192                                    "categoryId", category.getCategoryId());
193    
194                            Hits hits = SearchEngineUtil.search(
195                                    getSearchEngineId(), category.getCompanyId(), booleanQuery,
196                                    QueryUtil.ALL_POS, QueryUtil.ALL_POS);
197    
198                            for (int i = 0; i < hits.getLength(); i++) {
199                                    Document document = hits.doc(i);
200    
201                                    SearchEngineUtil.deleteDocument(
202                                            getSearchEngineId(), category.getCompanyId(),
203                                            document.get(Field.UID));
204                            }
205                    }
206                    else if (obj instanceof MBMessage) {
207                            MBMessage message = (MBMessage)obj;
208    
209                            Document document = new DocumentImpl();
210    
211                            document.addUID(PORTLET_ID, message.getMessageId());
212    
213                            SearchEngineUtil.deleteDocument(
214                                    getSearchEngineId(), message.getCompanyId(),
215                                    document.get(Field.UID));
216                    }
217                    else if (obj instanceof MBThread) {
218                            MBThread thread = (MBThread)obj;
219    
220                            MBMessage message = MBMessageLocalServiceUtil.getMessage(
221                                    thread.getRootMessageId());
222    
223                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
224                                    searchContext);
225    
226                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
227    
228                            booleanQuery.addRequiredTerm("threadId", thread.getThreadId());
229    
230                            Hits hits = SearchEngineUtil.search(
231                                    getSearchEngineId(), message.getCompanyId(), booleanQuery,
232                                    QueryUtil.ALL_POS, QueryUtil.ALL_POS);
233    
234                            for (int i = 0; i < hits.getLength(); i++) {
235                                    Document document = hits.doc(i);
236    
237                                    SearchEngineUtil.deleteDocument(
238                                            getSearchEngineId(), message.getCompanyId(),
239                                            document.get(Field.UID));
240                            }
241                    }
242            }
243    
244            @Override
245            protected Document doGetDocument(Object obj) throws Exception {
246                    MBMessage message = (MBMessage)obj;
247    
248                    Document document = getBaseModelDocument(PORTLET_ID, message);
249    
250                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
251                    document.addText(Field.CONTENT, processContent(message));
252                    document.addKeyword(
253                            Field.ROOT_ENTRY_CLASS_PK, message.getRootMessageId());
254                    document.addText(Field.TITLE, message.getSubject());
255    
256                    if (message.isAnonymous()) {
257                            document.remove(Field.USER_NAME);
258                    }
259    
260                    try {
261                            MBDiscussionLocalServiceUtil.getThreadDiscussion(
262                                    message.getThreadId());
263    
264                            document.addKeyword("discussion", true);
265                    }
266                    catch (NoSuchDiscussionException nsde) {
267                            document.addKeyword("discussion", false);
268                    }
269    
270                    document.addKeyword("threadId", message.getThreadId());
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                            content = BBCodeTranslatorUtil.getHTML(content);
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(long companyId) throws Exception {
361                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
362                            MBCategory.class, PACLClassLoaderUtil.getPortalClassLoader());
363    
364                    Projection minCategoryIdProjection = ProjectionFactoryUtil.min(
365                            "categoryId");
366                    Projection maxCategoryIdProjection = ProjectionFactoryUtil.max(
367                            "categoryId");
368    
369                    ProjectionList projectionList = ProjectionFactoryUtil.projectionList();
370    
371                    projectionList.add(minCategoryIdProjection);
372                    projectionList.add(maxCategoryIdProjection);
373    
374                    dynamicQuery.setProjection(projectionList);
375    
376                    addReindexCriteria(dynamicQuery, companyId);
377    
378                    List<Object[]> results = MBCategoryLocalServiceUtil.dynamicQuery(
379                            dynamicQuery);
380    
381                    Object[] minAndMaxCategoryIds = results.get(0);
382    
383                    if ((minAndMaxCategoryIds[0] == null) ||
384                            (minAndMaxCategoryIds[1] == null)) {
385    
386                            return;
387                    }
388    
389                    long minCategoryId = (Long)minAndMaxCategoryIds[0];
390                    long maxCategoryId = (Long)minAndMaxCategoryIds[1];
391    
392                    long startCategoryId = minCategoryId;
393                    long endCategoryId = startCategoryId + DEFAULT_INTERVAL;
394    
395                    while (startCategoryId <= maxCategoryId) {
396                            reindexCategories(companyId, startCategoryId, endCategoryId);
397    
398                            startCategoryId = endCategoryId;
399                            endCategoryId += DEFAULT_INTERVAL;
400                    }
401            }
402    
403            protected void reindexCategories(
404                            long companyId, long startCategoryId, long endCategoryId)
405                    throws Exception {
406    
407                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
408                            MBCategory.class, PACLClassLoaderUtil.getPortalClassLoader());
409    
410                    Property property = PropertyFactoryUtil.forName("categoryId");
411    
412                    dynamicQuery.add(property.ge(startCategoryId));
413                    dynamicQuery.add(property.lt(endCategoryId));
414    
415                    addReindexCriteria(dynamicQuery, companyId);
416    
417                    List<MBCategory> categories = MBCategoryLocalServiceUtil.dynamicQuery(
418                            dynamicQuery);
419    
420                    for (MBCategory category : categories) {
421                            long groupId = category.getGroupId();
422                            long categoryId = category.getCategoryId();
423    
424                            reindexMessages(companyId, groupId, categoryId);
425                    }
426            }
427    
428            protected void reindexMessages(
429                            long companyId, long groupId, long categoryId)
430                    throws Exception {
431    
432                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
433                            MBMessage.class, PACLClassLoaderUtil.getPortalClassLoader());
434    
435                    Projection minMessageIdProjection = ProjectionFactoryUtil.min(
436                            "messageId");
437                    Projection maxMessageIdProjection = ProjectionFactoryUtil.max(
438                            "messageId");
439    
440                    ProjectionList projectionList = ProjectionFactoryUtil.projectionList();
441    
442                    projectionList.add(minMessageIdProjection);
443                    projectionList.add(maxMessageIdProjection);
444    
445                    dynamicQuery.setProjection(projectionList);
446    
447                    addReindexCriteria(dynamicQuery, groupId, categoryId);
448    
449                    List<Object[]> results = MBMessageLocalServiceUtil.dynamicQuery(
450                            dynamicQuery);
451    
452                    Object[] minAndMaxMessageIds = results.get(0);
453    
454                    if ((minAndMaxMessageIds[0] == null) ||
455                            (minAndMaxMessageIds[1] == null)) {
456    
457                            return;
458                    }
459    
460                    long minMessageId = (Long)minAndMaxMessageIds[0];
461                    long maxMessageId = (Long)minAndMaxMessageIds[1];
462    
463                    long startMessageId = minMessageId;
464                    long endMessageId = startMessageId + DEFAULT_INTERVAL;
465    
466                    while (startMessageId <= maxMessageId) {
467                            reindexMessages(
468                                    companyId, groupId, categoryId, startMessageId, endMessageId);
469    
470                            startMessageId = endMessageId;
471                            endMessageId += DEFAULT_INTERVAL;
472                    }
473            }
474    
475            protected void reindexMessages(
476                            long companyId, long groupId, long categoryId, long startMessageId,
477                            long endMessageId)
478                    throws Exception {
479    
480                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
481                            MBMessage.class, PACLClassLoaderUtil.getPortalClassLoader());
482    
483                    Property property = PropertyFactoryUtil.forName("messageId");
484    
485                    dynamicQuery.add(property.ge(startMessageId));
486                    dynamicQuery.add(property.lt(endMessageId));
487    
488                    addReindexCriteria(dynamicQuery, groupId, categoryId);
489    
490                    List<MBMessage> messages = MBMessageLocalServiceUtil.dynamicQuery(
491                            dynamicQuery);
492    
493                    if (messages.isEmpty()) {
494                            return;
495                    }
496    
497                    Collection<Document> documents = new ArrayList<Document>(
498                            messages.size());
499    
500                    for (MBMessage message : messages) {
501                            Document document = getDocument(message);
502    
503                            documents.add(document);
504                    }
505    
506                    SearchEngineUtil.updateDocuments(
507                            getSearchEngineId(), companyId, documents);
508            }
509    
510            protected void reindexRoot(long companyId) throws Exception {
511                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
512                            Group.class, PACLClassLoaderUtil.getPortalClassLoader());
513    
514                    Projection minGroupIdProjection = ProjectionFactoryUtil.min("groupId");
515                    Projection maxGroupIdProjection = ProjectionFactoryUtil.max("groupId");
516    
517                    ProjectionList projectionList = ProjectionFactoryUtil.projectionList();
518    
519                    projectionList.add(minGroupIdProjection);
520                    projectionList.add(maxGroupIdProjection);
521    
522                    dynamicQuery.setProjection(projectionList);
523    
524                    addReindexCriteria(dynamicQuery, companyId);
525    
526                    List<Object[]> results = GroupLocalServiceUtil.dynamicQuery(
527                            dynamicQuery);
528    
529                    Object[] minAndMaxGroupIds = results.get(0);
530    
531                    if ((minAndMaxGroupIds[0] == null) || (minAndMaxGroupIds[1] == null)) {
532                            return;
533                    }
534    
535                    long minGroupId = (Long)minAndMaxGroupIds[0];
536                    long maxGroupId = (Long)minAndMaxGroupIds[1];
537    
538                    long startGroupId = minGroupId;
539                    long endGroupId = startGroupId + DEFAULT_INTERVAL;
540    
541                    while (startGroupId <= maxGroupId) {
542                            reindexRoot(companyId, startGroupId, endGroupId);
543    
544                            startGroupId = endGroupId;
545                            endGroupId += DEFAULT_INTERVAL;
546                    }
547            }
548    
549            protected void reindexRoot(
550                            long companyId, long startGroupId, long endGroupId)
551                    throws Exception {
552    
553                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
554                            Group.class, PACLClassLoaderUtil.getPortalClassLoader());
555    
556                    Property property = PropertyFactoryUtil.forName("groupId");
557    
558                    dynamicQuery.add(property.ge(startGroupId));
559                    dynamicQuery.add(property.lt(endGroupId));
560    
561                    addReindexCriteria(dynamicQuery, companyId);
562    
563                    List<Group> groups = GroupLocalServiceUtil.dynamicQuery(dynamicQuery);
564    
565                    for (Group group : groups) {
566                            long groupId = group.getGroupId();
567                            long categoryId = MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID;
568    
569                            reindexMessages(companyId, groupId, categoryId);
570                    }
571            }
572    
573            private static Log _log = LogFactoryUtil.getLog(MBMessageIndexer.class);
574    
575    }