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.QueryUtil;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslatorUtil;
021    import com.liferay.portal.kernel.search.BaseIndexer;
022    import com.liferay.portal.kernel.search.BooleanClauseOccur;
023    import com.liferay.portal.kernel.search.BooleanQuery;
024    import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
025    import com.liferay.portal.kernel.search.Document;
026    import com.liferay.portal.kernel.search.DocumentImpl;
027    import com.liferay.portal.kernel.search.Field;
028    import com.liferay.portal.kernel.search.Hits;
029    import com.liferay.portal.kernel.search.Indexer;
030    import com.liferay.portal.kernel.search.SearchContext;
031    import com.liferay.portal.kernel.search.SearchEngineUtil;
032    import com.liferay.portal.kernel.search.Summary;
033    import com.liferay.portal.kernel.util.GetterUtil;
034    import com.liferay.portal.kernel.util.HtmlUtil;
035    import com.liferay.portal.kernel.util.StringUtil;
036    import com.liferay.portal.kernel.util.Validator;
037    import com.liferay.portal.kernel.workflow.WorkflowConstants;
038    import com.liferay.portal.model.Group;
039    import com.liferay.portal.security.permission.ActionKeys;
040    import com.liferay.portal.security.permission.PermissionChecker;
041    import com.liferay.portal.service.GroupLocalServiceUtil;
042    import com.liferay.portal.util.PortletKeys;
043    import com.liferay.portlet.messageboards.NoSuchDiscussionException;
044    import com.liferay.portlet.messageboards.model.MBCategory;
045    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
046    import com.liferay.portlet.messageboards.model.MBMessage;
047    import com.liferay.portlet.messageboards.model.MBThread;
048    import com.liferay.portlet.messageboards.service.MBCategoryLocalServiceUtil;
049    import com.liferay.portlet.messageboards.service.MBCategoryServiceUtil;
050    import com.liferay.portlet.messageboards.service.MBDiscussionLocalServiceUtil;
051    import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;
052    import com.liferay.portlet.messageboards.service.permission.MBMessagePermission;
053    
054    import java.util.ArrayList;
055    import java.util.Collection;
056    import java.util.List;
057    import java.util.Locale;
058    
059    import javax.portlet.PortletURL;
060    
061    /**
062     * @author Brian Wing Shun Chan
063     * @author Harry Mark
064     * @author Bruno Farache
065     * @author Raymond Augé
066     */
067    public class MBIndexer extends BaseIndexer {
068    
069            public static final String[] CLASS_NAMES = {MBMessage.class.getName()};
070    
071            public static final String PORTLET_ID = PortletKeys.MESSAGE_BOARDS;
072    
073            public String[] getClassNames() {
074                    return CLASS_NAMES;
075            }
076    
077            public String getPortletId() {
078                    return PORTLET_ID;
079            }
080    
081            @Override
082            public boolean hasPermission(
083                            PermissionChecker permissionChecker, long entryClassPK,
084                            String actionId)
085                    throws Exception {
086    
087                    return MBMessagePermission.contains(
088                            permissionChecker, entryClassPK, ActionKeys.VIEW);
089            }
090    
091            @Override
092            public boolean isFilterSearch() {
093                    return _FILTER_SEARCH;
094            }
095    
096            @Override
097            public boolean isPermissionAware() {
098                    return _PERMISSION_AWARE;
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            @Override
154            protected void doDelete(Object obj) throws Exception {
155                    SearchContext searchContext = new SearchContext();
156    
157                    searchContext.setSearchEngineId(SearchEngineUtil.SYSTEM_ENGINE_ID);
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                                    category.getCompanyId(), booleanQuery, QueryUtil.ALL_POS,
172                                    QueryUtil.ALL_POS);
173    
174                            for (int i = 0; i < hits.getLength(); i++) {
175                                    Document document = hits.doc(i);
176    
177                                    SearchEngineUtil.deleteDocument(
178                                            category.getCompanyId(), document.get(Field.UID));
179                            }
180                    }
181                    else if (obj instanceof MBMessage) {
182                            MBMessage message = (MBMessage)obj;
183    
184                            Document document = new DocumentImpl();
185    
186                            document.addUID(PORTLET_ID, message.getMessageId());
187    
188                            SearchEngineUtil.deleteDocument(
189                                    message.getCompanyId(), document.get(Field.UID));
190                    }
191                    else if (obj instanceof MBThread) {
192                            MBThread thread = (MBThread)obj;
193    
194                            MBMessage message = MBMessageLocalServiceUtil.getMessage(
195                                    thread.getRootMessageId());
196    
197                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
198                                    searchContext);
199    
200                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
201    
202                            booleanQuery.addRequiredTerm("threadId", thread.getThreadId());
203    
204                            Hits hits = SearchEngineUtil.search(
205                                    message.getCompanyId(), booleanQuery, QueryUtil.ALL_POS,
206                                    QueryUtil.ALL_POS);
207    
208                            for (int i = 0; i < hits.getLength(); i++) {
209                                    Document document = hits.doc(i);
210    
211                                    SearchEngineUtil.deleteDocument(
212                                            message.getCompanyId(), document.get(Field.UID));
213                            }
214                    }
215            }
216    
217            @Override
218            protected Document doGetDocument(Object obj) throws Exception {
219                    MBMessage message = (MBMessage)obj;
220    
221                    Document document = getBaseModelDocument(PORTLET_ID, message);
222    
223                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
224                    document.addText(Field.CONTENT, processContent(message));
225                    document.addKeyword(
226                            Field.ROOT_ENTRY_CLASS_PK, message.getRootMessageId());
227                    document.addText(Field.TITLE, message.getSubject());
228    
229                    if (message.isAnonymous()) {
230                            document.remove(Field.USER_NAME);
231                    }
232    
233                    try {
234                            MBDiscussionLocalServiceUtil.getThreadDiscussion(
235                                    message.getThreadId());
236    
237                            document.addKeyword("discussion", true);
238                    }
239                    catch (NoSuchDiscussionException nsde) {
240                            document.addKeyword("discussion", false);
241                    }
242    
243                    document.addKeyword("threadId", message.getThreadId());
244    
245                    return document;
246            }
247    
248            @Override
249            protected Summary doGetSummary(
250                    Document document, Locale locale, String snippet,
251                    PortletURL portletURL) {
252    
253                    String title = document.get(Field.TITLE);
254    
255                    String content = snippet;
256    
257                    if (Validator.isNull(snippet)) {
258                            content = StringUtil.shorten(document.get(Field.CONTENT), 200);
259                    }
260    
261                    String messageId = document.get(Field.ENTRY_CLASS_PK);
262    
263                    portletURL.setParameter(
264                            "struts_action", "/message_boards/view_message");
265                    portletURL.setParameter("messageId", messageId);
266    
267                    return new Summary(title, content, portletURL);
268            }
269    
270            @Override
271            protected void doReindex(Object obj) throws Exception {
272                    MBMessage message = (MBMessage)obj;
273    
274                    if (message.isDiscussion() ||
275                            (message.getStatus() != WorkflowConstants.STATUS_APPROVED)) {
276    
277                            return;
278                    }
279    
280                    Document document = getDocument(message);
281    
282                    SearchEngineUtil.updateDocument(message.getCompanyId(), document);
283            }
284    
285            @Override
286            protected void doReindex(String className, long classPK) throws Exception {
287                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
288    
289                    doReindex(message);
290    
291                    if (message.isRoot()) {
292                            List<MBMessage> messages =
293                                    MBMessageLocalServiceUtil.getThreadMessages(
294                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
295    
296                            for (MBMessage curMessage : messages) {
297                                    reindex(curMessage);
298                            }
299                    }
300                    else {
301                            reindex(message);
302                    }
303            }
304    
305            @Override
306            protected void doReindex(String[] ids) throws Exception {
307                    long companyId = GetterUtil.getLong(ids[0]);
308    
309                    reindexCategories(companyId);
310                    reindexRoot(companyId);
311            }
312    
313            @Override
314            protected String getPortletId(SearchContext searchContext) {
315                    return PORTLET_ID;
316            }
317    
318            protected String processContent(MBMessage message) {
319                    String content = message.getBody();
320    
321                    try {
322                            content = BBCodeTranslatorUtil.getHTML(content);
323                    }
324                    catch (Exception e) {
325                            _log.error(
326                                    "Could not parse message " + message.getMessageId() + ": " +
327                                            e.getMessage());
328                    }
329    
330                    content = HtmlUtil.extractText(content);
331    
332                    return content;
333            }
334    
335            protected void reindexCategories(long companyId) throws Exception {
336                    int categoryCount =
337                            MBCategoryLocalServiceUtil.getCompanyCategoriesCount(companyId);
338    
339                    int categoryPages = categoryCount / Indexer.DEFAULT_INTERVAL;
340    
341                    for (int i = 0; i <= categoryPages; i++) {
342                            int categoryStart = (i * Indexer.DEFAULT_INTERVAL);
343                            int categoryEnd = categoryStart + Indexer.DEFAULT_INTERVAL;
344    
345                            reindexCategories(companyId, categoryStart, categoryEnd);
346                    }
347            }
348    
349            protected void reindexCategories(
350                            long companyId, int categoryStart, int categoryEnd)
351                    throws Exception {
352    
353                    List<MBCategory> categories =
354                            MBCategoryLocalServiceUtil.getCompanyCategories(
355                                    companyId, categoryStart, categoryEnd);
356    
357                    for (MBCategory category : categories) {
358                            long groupId = category.getGroupId();
359                            long categoryId = category.getCategoryId();
360    
361                            int messageCount =
362                                    MBMessageLocalServiceUtil.getCategoryMessagesCount(
363                                            groupId, categoryId, WorkflowConstants.STATUS_APPROVED);
364    
365                            int messagePages = messageCount / Indexer.DEFAULT_INTERVAL;
366    
367                            for (int i = 0; i <= messagePages; i++) {
368                                    int messageStart = (i * Indexer.DEFAULT_INTERVAL);
369                                    int messageEnd = messageStart + Indexer.DEFAULT_INTERVAL;
370    
371                                    reindexMessages(
372                                            companyId, groupId, categoryId, messageStart, messageEnd);
373                            }
374                    }
375            }
376    
377            protected void reindexMessages(
378                            long companyId, long groupId, long categoryId, int messageStart,
379                            int messageEnd)
380                    throws Exception {
381    
382                    List<MBMessage> messages =
383                            MBMessageLocalServiceUtil.getCategoryMessages(
384                                    groupId, categoryId, WorkflowConstants.STATUS_APPROVED,
385                                    messageStart, messageEnd);
386    
387                    if (messages.isEmpty()) {
388                            return;
389                    }
390    
391                    Collection<Document> documents = new ArrayList<Document>();
392    
393                    for (MBMessage message : messages) {
394                            Document document = getDocument(message);
395    
396                            documents.add(document);
397                    }
398    
399                    SearchEngineUtil.updateDocuments(companyId, documents);
400            }
401    
402            protected void reindexRoot(long companyId) throws Exception {
403                    int groupCount = GroupLocalServiceUtil.getCompanyGroupsCount(companyId);
404    
405                    int groupPages = groupCount / Indexer.DEFAULT_INTERVAL;
406    
407                    for (int i = 0; i <= groupPages; i++) {
408                            int groupStart = (i * Indexer.DEFAULT_INTERVAL);
409                            int groupEnd = groupStart + Indexer.DEFAULT_INTERVAL;
410    
411                            reindexRoot(companyId, groupStart, groupEnd);
412                    }
413            }
414    
415            protected void reindexRoot(long companyId, int groupStart, int groupEnd)
416                    throws Exception {
417    
418                    List<Group> groups = GroupLocalServiceUtil.getCompanyGroups(
419                            companyId, groupStart, groupEnd);
420    
421                    for (Group group : groups) {
422                            long groupId = group.getGroupId();
423                            long categoryId = MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID;
424    
425                            int entryCount = MBMessageLocalServiceUtil.getCategoryMessagesCount(
426                                    groupId, categoryId, WorkflowConstants.STATUS_APPROVED);
427    
428                            int entryPages = entryCount / Indexer.DEFAULT_INTERVAL;
429    
430                            for (int i = 0; i <= entryPages; i++) {
431                                    int entryStart = (i * Indexer.DEFAULT_INTERVAL);
432                                    int entryEnd = entryStart + Indexer.DEFAULT_INTERVAL;
433    
434                                    reindexMessages(
435                                            companyId, groupId, categoryId, entryStart, entryEnd);
436                            }
437                    }
438            }
439    
440            private static final boolean _FILTER_SEARCH = true;
441    
442            private static final boolean _PERMISSION_AWARE = true;
443    
444            private static Log _log = LogFactoryUtil.getLog(MBIndexer.class);
445    
446    }