001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
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.util.StringUtil;
042    import com.liferay.portal.kernel.util.Validator;
043    import com.liferay.portal.kernel.workflow.WorkflowConstants;
044    import com.liferay.portal.model.Group;
045    import com.liferay.portal.security.pacl.PACLClassLoaderUtil;
046    import com.liferay.portal.security.permission.ActionKeys;
047    import com.liferay.portal.security.permission.PermissionChecker;
048    import com.liferay.portal.service.GroupLocalServiceUtil;
049    import com.liferay.portal.util.PortletKeys;
050    import com.liferay.portlet.messageboards.NoSuchDiscussionException;
051    import com.liferay.portlet.messageboards.model.MBCategory;
052    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
053    import com.liferay.portlet.messageboards.model.MBMessage;
054    import com.liferay.portlet.messageboards.model.MBThread;
055    import com.liferay.portlet.messageboards.service.MBCategoryLocalServiceUtil;
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    
061    import java.util.ArrayList;
062    import java.util.Collection;
063    import java.util.List;
064    import java.util.Locale;
065    
066    import javax.portlet.PortletURL;
067    
068    /**
069     * @author Brian Wing Shun Chan
070     * @author Harry Mark
071     * @author Bruno Farache
072     * @author Raymond Augé
073     */
074    public class MBIndexer extends BaseIndexer {
075    
076            public static final String[] CLASS_NAMES = {MBMessage.class.getName()};
077    
078            public static final String PORTLET_ID = PortletKeys.MESSAGE_BOARDS;
079    
080            public String[] getClassNames() {
081                    return CLASS_NAMES;
082            }
083    
084            public String getPortletId() {
085                    return PORTLET_ID;
086            }
087    
088            @Override
089            public boolean hasPermission(
090                            PermissionChecker permissionChecker, long entryClassPK,
091                            String actionId)
092                    throws Exception {
093    
094                    return MBMessagePermission.contains(
095                            permissionChecker, entryClassPK, ActionKeys.VIEW);
096            }
097    
098            @Override
099            public boolean isFilterSearch() {
100                    return _FILTER_SEARCH;
101            }
102    
103            @Override
104            public boolean isPermissionAware() {
105                    return _PERMISSION_AWARE;
106            }
107    
108            @Override
109            public void postProcessContextQuery(
110                            BooleanQuery contextQuery, SearchContext searchContext)
111                    throws Exception {
112    
113                    int status = GetterUtil.getInteger(
114                            searchContext.getAttribute(Field.STATUS),
115                            WorkflowConstants.STATUS_ANY);
116    
117                    if (status != WorkflowConstants.STATUS_ANY) {
118                            contextQuery.addRequiredTerm(Field.STATUS, status);
119                    }
120    
121                    boolean discussion = GetterUtil.getBoolean(
122                            searchContext.getAttribute("discussion"), false);
123    
124                    contextQuery.addRequiredTerm("discussion", discussion);
125    
126                    long threadId = GetterUtil.getLong(
127                            (String)searchContext.getAttribute("threadId"));
128    
129                    if (threadId > 0) {
130                            contextQuery.addRequiredTerm("threadId", threadId);
131                    }
132    
133                    long[] categoryIds = searchContext.getCategoryIds();
134    
135                    if ((categoryIds != null) && (categoryIds.length > 0)) {
136                            if (categoryIds[0] ==
137                                            MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
138    
139                                    return;
140                            }
141    
142                            BooleanQuery categoriesQuery = BooleanQueryFactoryUtil.create(
143                                    searchContext);
144    
145                            for (long categoryId : categoryIds) {
146                                    try {
147                                            MBCategoryServiceUtil.getCategory(categoryId);
148                                    }
149                                    catch (Exception e) {
150                                            continue;
151                                    }
152    
153                                    categoriesQuery.addTerm(Field.CATEGORY_ID, categoryId);
154                            }
155    
156                            contextQuery.add(categoriesQuery, BooleanClauseOccur.MUST);
157                    }
158            }
159    
160            protected void addReindexCriteria(
161                    DynamicQuery dynamicQuery, long companyId) {
162    
163                    Property property = PropertyFactoryUtil.forName("companyId");
164    
165                    dynamicQuery.add(property.eq(companyId));
166            }
167    
168            protected void addReindexCriteria(
169                    DynamicQuery dynamicQuery, long groupId, long categoryId) {
170    
171                    Property groupIdProperty = PropertyFactoryUtil.forName("groupId");
172    
173                    dynamicQuery.add(groupIdProperty.eq(groupId));
174    
175                    Property categoryIdProperty = PropertyFactoryUtil.forName("categoryId");
176    
177                    dynamicQuery.add(categoryIdProperty.eq(categoryId));
178    
179                    Property statusProperty = PropertyFactoryUtil.forName("status");
180    
181                    dynamicQuery.add(statusProperty.eq(WorkflowConstants.STATUS_APPROVED));
182            }
183    
184            @Override
185            protected void doDelete(Object obj) throws Exception {
186                    SearchContext searchContext = new SearchContext();
187    
188                    searchContext.setSearchEngineId(getSearchEngineId());
189    
190                    if (obj instanceof MBCategory) {
191                            MBCategory category = (MBCategory)obj;
192    
193                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
194                                    searchContext);
195    
196                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
197    
198                            booleanQuery.addRequiredTerm(
199                                    "categoryId", category.getCategoryId());
200    
201                            Hits hits = SearchEngineUtil.search(
202                                    getSearchEngineId(), category.getCompanyId(), booleanQuery,
203                                    QueryUtil.ALL_POS, QueryUtil.ALL_POS);
204    
205                            for (int i = 0; i < hits.getLength(); i++) {
206                                    Document document = hits.doc(i);
207    
208                                    SearchEngineUtil.deleteDocument(
209                                            getSearchEngineId(), category.getCompanyId(),
210                                            document.get(Field.UID));
211                            }
212                    }
213                    else if (obj instanceof MBMessage) {
214                            MBMessage message = (MBMessage)obj;
215    
216                            Document document = new DocumentImpl();
217    
218                            document.addUID(PORTLET_ID, message.getMessageId());
219    
220                            SearchEngineUtil.deleteDocument(
221                                    getSearchEngineId(), message.getCompanyId(),
222                                    document.get(Field.UID));
223                    }
224                    else if (obj instanceof MBThread) {
225                            MBThread thread = (MBThread)obj;
226    
227                            MBMessage message = MBMessageLocalServiceUtil.getMessage(
228                                    thread.getRootMessageId());
229    
230                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
231                                    searchContext);
232    
233                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
234    
235                            booleanQuery.addRequiredTerm("threadId", thread.getThreadId());
236    
237                            Hits hits = SearchEngineUtil.search(
238                                    getSearchEngineId(), message.getCompanyId(), booleanQuery,
239                                    QueryUtil.ALL_POS, QueryUtil.ALL_POS);
240    
241                            for (int i = 0; i < hits.getLength(); i++) {
242                                    Document document = hits.doc(i);
243    
244                                    SearchEngineUtil.deleteDocument(
245                                            getSearchEngineId(), message.getCompanyId(),
246                                            document.get(Field.UID));
247                            }
248                    }
249            }
250    
251            @Override
252            protected Document doGetDocument(Object obj) throws Exception {
253                    MBMessage message = (MBMessage)obj;
254    
255                    Document document = getBaseModelDocument(PORTLET_ID, message);
256    
257                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
258                    document.addText(Field.CONTENT, processContent(message));
259                    document.addKeyword(
260                            Field.ROOT_ENTRY_CLASS_PK, message.getRootMessageId());
261                    document.addText(Field.TITLE, message.getSubject());
262    
263                    if (message.isAnonymous()) {
264                            document.remove(Field.USER_NAME);
265                    }
266    
267                    try {
268                            MBDiscussionLocalServiceUtil.getThreadDiscussion(
269                                    message.getThreadId());
270    
271                            document.addKeyword("discussion", true);
272                    }
273                    catch (NoSuchDiscussionException nsde) {
274                            document.addKeyword("discussion", false);
275                    }
276    
277                    document.addKeyword("threadId", message.getThreadId());
278    
279                    return document;
280            }
281    
282            @Override
283            protected Summary doGetSummary(
284                    Document document, Locale locale, String snippet,
285                    PortletURL portletURL) {
286    
287                    String title = document.get(Field.TITLE);
288    
289                    String content = snippet;
290    
291                    if (Validator.isNull(snippet)) {
292                            content = StringUtil.shorten(document.get(Field.CONTENT), 200);
293                    }
294    
295                    String messageId = document.get(Field.ENTRY_CLASS_PK);
296    
297                    portletURL.setParameter(
298                            "struts_action", "/message_boards/view_message");
299                    portletURL.setParameter("messageId", messageId);
300    
301                    return new Summary(title, content, portletURL);
302            }
303    
304            @Override
305            protected void doReindex(Object obj) throws Exception {
306                    MBMessage message = (MBMessage)obj;
307    
308                    if (message.isDiscussion() ||
309                            (message.getStatus() != WorkflowConstants.STATUS_APPROVED)) {
310    
311                            return;
312                    }
313    
314                    Document document = getDocument(message);
315    
316                    SearchEngineUtil.updateDocument(
317                            getSearchEngineId(), message.getCompanyId(), document);
318            }
319    
320            @Override
321            protected void doReindex(String className, long classPK) throws Exception {
322                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
323    
324                    doReindex(message);
325    
326                    if (message.isRoot()) {
327                            List<MBMessage> messages =
328                                    MBMessageLocalServiceUtil.getThreadMessages(
329                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
330    
331                            for (MBMessage curMessage : messages) {
332                                    reindex(curMessage);
333                            }
334                    }
335                    else {
336                            reindex(message);
337                    }
338            }
339    
340            @Override
341            protected void doReindex(String[] ids) throws Exception {
342                    long companyId = GetterUtil.getLong(ids[0]);
343    
344                    reindexCategories(companyId);
345                    reindexRoot(companyId);
346            }
347    
348            @Override
349            protected String getPortletId(SearchContext searchContext) {
350                    return PORTLET_ID;
351            }
352    
353            protected String processContent(MBMessage message) {
354                    String content = message.getBody();
355    
356                    try {
357                            content = BBCodeTranslatorUtil.getHTML(content);
358                    }
359                    catch (Exception e) {
360                            _log.error(
361                                    "Could not parse message " + message.getMessageId() + ": " +
362                                            e.getMessage());
363                    }
364    
365                    content = HtmlUtil.extractText(content);
366    
367                    return content;
368            }
369    
370            protected void reindexCategories(long companyId) throws Exception {
371                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
372                            MBCategory.class, PACLClassLoaderUtil.getPortalClassLoader());
373    
374                    Projection minCategoryIdProjection = ProjectionFactoryUtil.min(
375                            "categoryId");
376                    Projection maxCategoryIdProjection = ProjectionFactoryUtil.max(
377                            "categoryId");
378    
379                    ProjectionList projectionList = ProjectionFactoryUtil.projectionList();
380    
381                    projectionList.add(minCategoryIdProjection);
382                    projectionList.add(maxCategoryIdProjection);
383    
384                    dynamicQuery.setProjection(projectionList);
385    
386                    addReindexCriteria(dynamicQuery, companyId);
387    
388                    List<Object[]> results = MBCategoryLocalServiceUtil.dynamicQuery(
389                            dynamicQuery);
390    
391                    Object[] minAndMaxCategoryIds = results.get(0);
392    
393                    if ((minAndMaxCategoryIds[0] == null) ||
394                            (minAndMaxCategoryIds[1] == null)) {
395    
396                            return;
397                    }
398    
399                    long minCategoryId = (Long)minAndMaxCategoryIds[0];
400                    long maxCategoryId = (Long)minAndMaxCategoryIds[1];
401    
402                    long startCategoryId = minCategoryId;
403                    long endCategoryId = startCategoryId + DEFAULT_INTERVAL;
404    
405                    while (startCategoryId <= maxCategoryId) {
406                            reindexCategories(companyId, startCategoryId, endCategoryId);
407    
408                            startCategoryId = endCategoryId;
409                            endCategoryId += DEFAULT_INTERVAL;
410                    }
411            }
412    
413            protected void reindexCategories(
414                            long companyId, long startCategoryId, long endCategoryId)
415                    throws Exception {
416    
417                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
418                            MBCategory.class, PACLClassLoaderUtil.getPortalClassLoader());
419    
420                    Property property = PropertyFactoryUtil.forName("categoryId");
421    
422                    dynamicQuery.add(property.ge(startCategoryId));
423                    dynamicQuery.add(property.lt(endCategoryId));
424    
425                    addReindexCriteria(dynamicQuery, companyId);
426    
427                    List<MBCategory> categories = MBCategoryLocalServiceUtil.dynamicQuery(
428                            dynamicQuery);
429    
430                    for (MBCategory category : categories) {
431                            long groupId = category.getGroupId();
432                            long categoryId = category.getCategoryId();
433    
434                            reindexMessages(companyId, groupId, categoryId);
435                    }
436            }
437    
438            protected void reindexMessages(
439                            long companyId, long groupId, long categoryId)
440                    throws Exception {
441    
442                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
443                            MBMessage.class, PACLClassLoaderUtil.getPortalClassLoader());
444    
445                    Projection minMessageIdProjection = ProjectionFactoryUtil.min(
446                            "messageId");
447                    Projection maxMessageIdProjection = ProjectionFactoryUtil.max(
448                            "messageId");
449    
450                    ProjectionList projectionList = ProjectionFactoryUtil.projectionList();
451    
452                    projectionList.add(minMessageIdProjection);
453                    projectionList.add(maxMessageIdProjection);
454    
455                    dynamicQuery.setProjection(projectionList);
456    
457                    addReindexCriteria(dynamicQuery, groupId, categoryId);
458    
459                    List<Object[]> results = MBMessageLocalServiceUtil.dynamicQuery(
460                            dynamicQuery);
461    
462                    Object[] minAndMaxMessageIds = results.get(0);
463    
464                    if ((minAndMaxMessageIds[0] == null) ||
465                            (minAndMaxMessageIds[1] == null)) {
466    
467                            return;
468                    }
469    
470                    long minMessageId = (Long)minAndMaxMessageIds[0];
471                    long maxMessageId = (Long)minAndMaxMessageIds[1];
472    
473                    long startMessageId = minMessageId;
474                    long endMessageId = startMessageId + DEFAULT_INTERVAL;
475    
476                    while (startMessageId <= maxMessageId) {
477                            reindexMessages(
478                                    companyId, groupId, categoryId, startMessageId, endMessageId);
479    
480                            startMessageId = endMessageId;
481                            endMessageId += DEFAULT_INTERVAL;
482                    }
483            }
484    
485            protected void reindexMessages(
486                            long companyId, long groupId, long categoryId, long startMessageId,
487                            long endMessageId)
488                    throws Exception {
489    
490                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
491                            MBMessage.class, PACLClassLoaderUtil.getPortalClassLoader());
492    
493                    Property property = PropertyFactoryUtil.forName("messageId");
494    
495                    dynamicQuery.add(property.ge(startMessageId));
496                    dynamicQuery.add(property.lt(endMessageId));
497    
498                    addReindexCriteria(dynamicQuery, groupId, categoryId);
499    
500                    List<MBMessage> messages = MBMessageLocalServiceUtil.dynamicQuery(
501                            dynamicQuery);
502    
503                    if (messages.isEmpty()) {
504                            return;
505                    }
506    
507                    Collection<Document> documents = new ArrayList<Document>(
508                            messages.size());
509    
510                    for (MBMessage message : messages) {
511                            Document document = getDocument(message);
512    
513                            documents.add(document);
514                    }
515    
516                    SearchEngineUtil.updateDocuments(
517                            getSearchEngineId(), companyId, documents);
518            }
519    
520            protected void reindexRoot(long companyId) throws Exception {
521                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
522                            Group.class, PACLClassLoaderUtil.getPortalClassLoader());
523    
524                    Projection minGroupIdProjection = ProjectionFactoryUtil.min("groupId");
525                    Projection maxGroupIdProjection = ProjectionFactoryUtil.max("groupId");
526    
527                    ProjectionList projectionList = ProjectionFactoryUtil.projectionList();
528    
529                    projectionList.add(minGroupIdProjection);
530                    projectionList.add(maxGroupIdProjection);
531    
532                    dynamicQuery.setProjection(projectionList);
533    
534                    addReindexCriteria(dynamicQuery, companyId);
535    
536                    List<Object[]> results = GroupLocalServiceUtil.dynamicQuery(
537                            dynamicQuery);
538    
539                    Object[] minAndMaxGroupIds = results.get(0);
540    
541                    if ((minAndMaxGroupIds[0] == null) || (minAndMaxGroupIds[1] == null)) {
542                            return;
543                    }
544    
545                    long minGroupId = (Long)minAndMaxGroupIds[0];
546                    long maxGroupId = (Long)minAndMaxGroupIds[1];
547    
548                    long startGroupId = minGroupId;
549                    long endGroupId = startGroupId + DEFAULT_INTERVAL;
550    
551                    while (startGroupId <= maxGroupId) {
552                            reindexRoot(companyId, startGroupId, endGroupId);
553    
554                            startGroupId = endGroupId;
555                            endGroupId += DEFAULT_INTERVAL;
556                    }
557            }
558    
559            protected void reindexRoot(
560                            long companyId, long startGroupId, long endGroupId)
561                    throws Exception {
562    
563                    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
564                            Group.class, PACLClassLoaderUtil.getPortalClassLoader());
565    
566                    Property property = PropertyFactoryUtil.forName("groupId");
567    
568                    dynamicQuery.add(property.ge(startGroupId));
569                    dynamicQuery.add(property.lt(endGroupId));
570    
571                    addReindexCriteria(dynamicQuery, companyId);
572    
573                    List<Group> groups = GroupLocalServiceUtil.dynamicQuery(dynamicQuery);
574    
575                    for (Group group : groups) {
576                            long groupId = group.getGroupId();
577                            long categoryId = MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID;
578    
579                            reindexMessages(companyId, groupId, categoryId);
580                    }
581            }
582    
583            private static final boolean _FILTER_SEARCH = true;
584    
585            private static final boolean _PERMISSION_AWARE = true;
586    
587            private static Log _log = LogFactoryUtil.getLog(MBIndexer.class);
588    
589    }