001    /**
002     * Copyright (c) 2000-2011 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.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 void postProcessContextQuery(
098                            BooleanQuery contextQuery, SearchContext searchContext)
099                    throws Exception {
100    
101                    int status = GetterUtil.getInteger(
102                            searchContext.getAttribute(Field.STATUS),
103                            WorkflowConstants.STATUS_ANY);
104    
105                    if (status != WorkflowConstants.STATUS_ANY) {
106                            contextQuery.addRequiredTerm(Field.STATUS, status);
107                    }
108    
109                    boolean discussion = GetterUtil.getBoolean(
110                            searchContext.getAttribute("discussion"), false);
111    
112                    contextQuery.addRequiredTerm("discussion", discussion);
113    
114                    long threadId = GetterUtil.getLong(
115                            (String)searchContext.getAttribute("threadId"));
116    
117                    if (threadId > 0) {
118                            contextQuery.addRequiredTerm("threadId", threadId);
119                    }
120    
121                    long[] categoryIds = searchContext.getCategoryIds();
122    
123                    if ((categoryIds != null) && (categoryIds.length > 0)) {
124                            if (categoryIds[0] ==
125                                            MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
126    
127                                    return;
128                            }
129    
130                            BooleanQuery categoriesQuery = BooleanQueryFactoryUtil.create(
131                                    searchContext);
132    
133                            for (long categoryId : categoryIds) {
134                                    try {
135                                            MBCategoryServiceUtil.getCategory(categoryId);
136                                    }
137                                    catch (Exception e) {
138                                            continue;
139                                    }
140    
141                                    categoriesQuery.addTerm(Field.CATEGORY_ID, categoryId);
142                            }
143    
144                            contextQuery.add(categoriesQuery, BooleanClauseOccur.MUST);
145                    }
146            }
147    
148            @Override
149            protected void doDelete(Object obj) throws Exception {
150                    SearchContext searchContext = new SearchContext();
151    
152                    searchContext.setSearchEngineId(SearchEngineUtil.SYSTEM_ENGINE_ID);
153    
154                    if (obj instanceof MBCategory) {
155                            MBCategory category = (MBCategory)obj;
156    
157                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
158                                    searchContext);
159    
160                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
161    
162                            booleanQuery.addRequiredTerm(
163                                    "categoryId", category.getCategoryId());
164    
165                            Hits hits = SearchEngineUtil.search(
166                                    category.getCompanyId(), booleanQuery, QueryUtil.ALL_POS,
167                                    QueryUtil.ALL_POS);
168    
169                            for (int i = 0; i < hits.getLength(); i++) {
170                                    Document document = hits.doc(i);
171    
172                                    SearchEngineUtil.deleteDocument(
173                                            category.getCompanyId(), document.get(Field.UID));
174                            }
175                    }
176                    else if (obj instanceof MBMessage) {
177                            MBMessage message = (MBMessage)obj;
178    
179                            Document document = new DocumentImpl();
180    
181                            document.addUID(PORTLET_ID, message.getMessageId());
182    
183                            SearchEngineUtil.deleteDocument(
184                                    message.getCompanyId(), document.get(Field.UID));
185                    }
186                    else if (obj instanceof MBThread) {
187                            MBThread thread = (MBThread)obj;
188    
189                            MBMessage message = MBMessageLocalServiceUtil.getMessage(
190                                    thread.getRootMessageId());
191    
192                            BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
193                                    searchContext);
194    
195                            booleanQuery.addRequiredTerm(Field.PORTLET_ID, PORTLET_ID);
196    
197                            booleanQuery.addRequiredTerm("threadId", thread.getThreadId());
198    
199                            Hits hits = SearchEngineUtil.search(
200                                    message.getCompanyId(), booleanQuery, QueryUtil.ALL_POS,
201                                    QueryUtil.ALL_POS);
202    
203                            for (int i = 0; i < hits.getLength(); i++) {
204                                    Document document = hits.doc(i);
205    
206                                    SearchEngineUtil.deleteDocument(
207                                            message.getCompanyId(), document.get(Field.UID));
208                            }
209                    }
210            }
211    
212            @Override
213            protected Document doGetDocument(Object obj) throws Exception {
214                    MBMessage message = (MBMessage)obj;
215    
216                    Document document = getBaseModelDocument(PORTLET_ID, message);
217    
218                    document.addKeyword(Field.CATEGORY_ID, message.getCategoryId());
219                    document.addText(Field.CONTENT, processContent(message));
220                    document.addKeyword(
221                            Field.ROOT_ENTRY_CLASS_PK, message.getRootMessageId());
222                    document.addText(Field.TITLE, message.getSubject());
223    
224                    if (message.isAnonymous()) {
225                            document.remove(Field.USER_NAME);
226                    }
227    
228                    try {
229                            MBDiscussionLocalServiceUtil.getThreadDiscussion(
230                                    message.getThreadId());
231    
232                            document.addKeyword("discussion", true);
233                    }
234                    catch (NoSuchDiscussionException nsde) {
235                            document.addKeyword("discussion", false);
236                    }
237    
238                    document.addKeyword("threadId", message.getThreadId());
239    
240                    return document;
241            }
242    
243            @Override
244            protected Summary doGetSummary(
245                    Document document, Locale locale, String snippet,
246                    PortletURL portletURL) {
247    
248                    String title = document.get(Field.TITLE);
249    
250                    String content = snippet;
251    
252                    if (Validator.isNull(snippet)) {
253                            content = StringUtil.shorten(document.get(Field.CONTENT), 200);
254                    }
255    
256                    String messageId = document.get(Field.ENTRY_CLASS_PK);
257    
258                    portletURL.setParameter(
259                            "struts_action", "/message_boards/view_message");
260                    portletURL.setParameter("messageId", messageId);
261    
262                    return new Summary(title, content, portletURL);
263            }
264    
265            @Override
266            protected void doReindex(Object obj) throws Exception {
267                    MBMessage message = (MBMessage)obj;
268    
269                    if (message.isDiscussion() ||
270                            (message.getStatus() != WorkflowConstants.STATUS_APPROVED)) {
271    
272                            return;
273                    }
274    
275                    Document document = getDocument(message);
276    
277                    SearchEngineUtil.updateDocument(message.getCompanyId(), document);
278            }
279    
280            @Override
281            protected void doReindex(String className, long classPK) throws Exception {
282                    MBMessage message = MBMessageLocalServiceUtil.getMessage(classPK);
283    
284                    doReindex(message);
285    
286                    if (message.isRoot()) {
287                            List<MBMessage> messages =
288                                    MBMessageLocalServiceUtil.getThreadMessages(
289                                            message.getThreadId(), WorkflowConstants.STATUS_APPROVED);
290    
291                            for (MBMessage curMessage : messages) {
292                                    reindex(curMessage);
293                            }
294                    }
295                    else {
296                            reindex(message);
297                    }
298            }
299    
300            @Override
301            protected void doReindex(String[] ids) throws Exception {
302                    long companyId = GetterUtil.getLong(ids[0]);
303    
304                    reindexCategories(companyId);
305                    reindexRoot(companyId);
306            }
307    
308            @Override
309            protected String getPortletId(SearchContext searchContext) {
310                    return PORTLET_ID;
311            }
312    
313            protected String processContent(MBMessage message) {
314                    String content = message.getBody();
315    
316                    try {
317                            content = BBCodeTranslatorUtil.getHTML(content);
318                    }
319                    catch (Exception e) {
320                            _log.error(
321                                    "Could not parse message " + message.getMessageId() + ": " +
322                                            e.getMessage());
323                    }
324    
325                    content = HtmlUtil.extractText(content);
326    
327                    return content;
328            }
329    
330            protected void reindexCategories(long companyId) throws Exception {
331                    int categoryCount =
332                            MBCategoryLocalServiceUtil.getCompanyCategoriesCount(companyId);
333    
334                    int categoryPages = categoryCount / Indexer.DEFAULT_INTERVAL;
335    
336                    for (int i = 0; i <= categoryPages; i++) {
337                            int categoryStart = (i * Indexer.DEFAULT_INTERVAL);
338                            int categoryEnd = categoryStart + Indexer.DEFAULT_INTERVAL;
339    
340                            reindexCategories(companyId, categoryStart, categoryEnd);
341                    }
342            }
343    
344            protected void reindexCategories(
345                            long companyId, int categoryStart, int categoryEnd)
346                    throws Exception {
347    
348                    List<MBCategory> categories =
349                            MBCategoryLocalServiceUtil.getCompanyCategories(
350                                    companyId, categoryStart, categoryEnd);
351    
352                    for (MBCategory category : categories) {
353                            long groupId = category.getGroupId();
354                            long categoryId = category.getCategoryId();
355    
356                            int messageCount =
357                                    MBMessageLocalServiceUtil.getCategoryMessagesCount(
358                                            groupId, categoryId, WorkflowConstants.STATUS_APPROVED);
359    
360                            int messagePages = messageCount / Indexer.DEFAULT_INTERVAL;
361    
362                            for (int i = 0; i <= messagePages; i++) {
363                                    int messageStart = (i * Indexer.DEFAULT_INTERVAL);
364                                    int messageEnd = messageStart + Indexer.DEFAULT_INTERVAL;
365    
366                                    reindexMessages(
367                                            companyId, groupId, categoryId, messageStart, messageEnd);
368                            }
369                    }
370            }
371    
372            protected void reindexMessages(
373                            long companyId, long groupId, long categoryId, int messageStart,
374                            int messageEnd)
375                    throws Exception {
376    
377                    List<MBMessage> messages =
378                            MBMessageLocalServiceUtil.getCategoryMessages(
379                                    groupId, categoryId, WorkflowConstants.STATUS_APPROVED,
380                                    messageStart, messageEnd);
381    
382                    if (messages.isEmpty()) {
383                            return;
384                    }
385    
386                    Collection<Document> documents = new ArrayList<Document>();
387    
388                    for (MBMessage message : messages) {
389                            Document document = getDocument(message);
390    
391                            documents.add(document);
392                    }
393    
394                    SearchEngineUtil.updateDocuments(companyId, documents);
395            }
396    
397            protected void reindexRoot(long companyId) throws Exception {
398                    int groupCount = GroupLocalServiceUtil.getCompanyGroupsCount(companyId);
399    
400                    int groupPages = groupCount / Indexer.DEFAULT_INTERVAL;
401    
402                    for (int i = 0; i <= groupPages; i++) {
403                            int groupStart = (i * Indexer.DEFAULT_INTERVAL);
404                            int groupEnd = groupStart + Indexer.DEFAULT_INTERVAL;
405    
406                            reindexRoot(companyId, groupStart, groupEnd);
407                    }
408            }
409    
410            protected void reindexRoot(long companyId, int groupStart, int groupEnd)
411                    throws Exception {
412    
413                    List<Group> groups = GroupLocalServiceUtil.getCompanyGroups(
414                            companyId, groupStart, groupEnd);
415    
416                    for (Group group : groups) {
417                            long groupId = group.getGroupId();
418                            long categoryId = MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID;
419    
420                            int entryCount = MBMessageLocalServiceUtil.getCategoryMessagesCount(
421                                    groupId, categoryId, WorkflowConstants.STATUS_APPROVED);
422    
423                            int entryPages = entryCount / Indexer.DEFAULT_INTERVAL;
424    
425                            for (int i = 0; i <= entryPages; i++) {
426                                    int entryStart = (i * Indexer.DEFAULT_INTERVAL);
427                                    int entryEnd = entryStart + Indexer.DEFAULT_INTERVAL;
428    
429                                    reindexMessages(
430                                            companyId, groupId, categoryId, entryStart, entryEnd);
431                            }
432                    }
433            }
434    
435            private static final boolean _FILTER_SEARCH = true;
436    
437            private static Log _log = LogFactoryUtil.getLog(MBIndexer.class);
438    
439    }