001    /**
002     * Copyright (c) 2000-present 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.service.impl;
016    
017    import com.liferay.asset.kernel.model.AssetEntry;
018    import com.liferay.document.library.kernel.model.DLFolderConstants;
019    import com.liferay.exportimport.kernel.lar.ExportImportThreadLocal;
020    import com.liferay.message.boards.kernel.constants.MBConstants;
021    import com.liferay.message.boards.kernel.exception.NoSuchCategoryException;
022    import com.liferay.message.boards.kernel.exception.SplitThreadException;
023    import com.liferay.message.boards.kernel.model.MBCategory;
024    import com.liferay.message.boards.kernel.model.MBCategoryConstants;
025    import com.liferay.message.boards.kernel.model.MBMessage;
026    import com.liferay.message.boards.kernel.model.MBMessageDisplay;
027    import com.liferay.message.boards.kernel.model.MBThread;
028    import com.liferay.message.boards.kernel.model.MBThreadConstants;
029    import com.liferay.message.boards.kernel.model.MBTreeWalker;
030    import com.liferay.portal.kernel.dao.orm.QueryDefinition;
031    import com.liferay.portal.kernel.exception.PortalException;
032    import com.liferay.portal.kernel.increment.BufferedIncrement;
033    import com.liferay.portal.kernel.increment.NumberIncrement;
034    import com.liferay.portal.kernel.json.JSONFactoryUtil;
035    import com.liferay.portal.kernel.json.JSONObject;
036    import com.liferay.portal.kernel.model.Group;
037    import com.liferay.portal.kernel.model.ResourceConstants;
038    import com.liferay.portal.kernel.model.SystemEventConstants;
039    import com.liferay.portal.kernel.model.User;
040    import com.liferay.portal.kernel.portletfilerepository.PortletFileRepositoryUtil;
041    import com.liferay.portal.kernel.repository.model.FileEntry;
042    import com.liferay.portal.kernel.repository.model.Folder;
043    import com.liferay.portal.kernel.search.Field;
044    import com.liferay.portal.kernel.search.Hits;
045    import com.liferay.portal.kernel.search.Indexer;
046    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
047    import com.liferay.portal.kernel.search.SearchContext;
048    import com.liferay.portal.kernel.search.Sort;
049    import com.liferay.portal.kernel.service.ServiceContext;
050    import com.liferay.portal.kernel.social.SocialActivityManagerUtil;
051    import com.liferay.portal.kernel.systemevent.SystemEvent;
052    import com.liferay.portal.kernel.util.StringUtil;
053    import com.liferay.portal.kernel.util.Validator;
054    import com.liferay.portal.kernel.workflow.WorkflowConstants;
055    import com.liferay.portlet.messageboards.service.base.MBThreadLocalServiceBaseImpl;
056    import com.liferay.portlet.messageboards.util.MBUtil;
057    import com.liferay.social.kernel.model.SocialActivityConstants;
058    import com.liferay.trash.kernel.exception.RestoreEntryException;
059    import com.liferay.trash.kernel.exception.TrashEntryException;
060    import com.liferay.trash.kernel.model.TrashEntry;
061    import com.liferay.trash.kernel.model.TrashVersion;
062    
063    import java.util.ArrayList;
064    import java.util.Date;
065    import java.util.HashSet;
066    import java.util.List;
067    import java.util.Set;
068    
069    /**
070     * @author Brian Wing Shun Chan
071     * @author Shuyang Zhou
072     */
073    public class MBThreadLocalServiceImpl extends MBThreadLocalServiceBaseImpl {
074    
075            @Override
076            public MBThread addThread(
077                            long categoryId, MBMessage message, ServiceContext serviceContext)
078                    throws PortalException {
079    
080                    // Thread
081    
082                    long threadId = message.getThreadId();
083    
084                    if (!message.isRoot() || (threadId <= 0)) {
085                            threadId = counterLocalService.increment();
086                    }
087    
088                    MBThread thread = mbThreadPersistence.create(threadId);
089    
090                    thread.setUuid(serviceContext.getUuid());
091                    thread.setGroupId(message.getGroupId());
092                    thread.setCompanyId(message.getCompanyId());
093                    thread.setUserId(message.getUserId());
094                    thread.setUserName(message.getUserName());
095                    thread.setCategoryId(categoryId);
096                    thread.setRootMessageId(message.getMessageId());
097                    thread.setRootMessageUserId(message.getUserId());
098    
099                    if (message.isAnonymous()) {
100                            thread.setLastPostByUserId(0);
101                    }
102                    else {
103                            thread.setLastPostByUserId(message.getUserId());
104                    }
105    
106                    thread.setLastPostDate(message.getModifiedDate());
107    
108                    if (message.getPriority() != MBThreadConstants.PRIORITY_NOT_GIVEN) {
109                            thread.setPriority(message.getPriority());
110                    }
111    
112                    thread.setStatus(message.getStatus());
113                    thread.setStatusByUserId(message.getStatusByUserId());
114                    thread.setStatusByUserName(message.getStatusByUserName());
115                    thread.setStatusDate(message.getStatusDate());
116    
117                    mbThreadPersistence.update(thread);
118    
119                    // Asset
120    
121                    if (categoryId >= 0) {
122                            assetEntryLocalService.updateEntry(
123                                    message.getUserId(), message.getGroupId(),
124                                    thread.getStatusDate(), thread.getLastPostDate(),
125                                    MBThread.class.getName(), thread.getThreadId(),
126                                    thread.getUuid(), 0, new long[0], new String[0], true, false,
127                                    null, null, thread.getStatusDate(), null, null,
128                                    String.valueOf(thread.getRootMessageId()), null, null, null,
129                                    null, 0, 0, serviceContext.getAssetPriority());
130                    }
131    
132                    return thread;
133            }
134    
135            @Override
136            public void deleteThread(long threadId) throws PortalException {
137                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
138    
139                    mbThreadLocalService.deleteThread(thread);
140            }
141    
142            @Override
143            @SystemEvent(
144                    action = SystemEventConstants.ACTION_SKIP,
145                    type = SystemEventConstants.TYPE_DELETE
146            )
147            public void deleteThread(MBThread thread) throws PortalException {
148                    MBMessage rootMessage = mbMessagePersistence.findByPrimaryKey(
149                            thread.getRootMessageId());
150    
151                    // Indexer
152    
153                    Indexer<MBMessage> messageIndexer =
154                            IndexerRegistryUtil.nullSafeGetIndexer(MBMessage.class);
155    
156                    // Attachments
157    
158                    long folderId = thread.getAttachmentsFolderId();
159    
160                    if (folderId != DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
161                            PortletFileRepositoryUtil.deletePortletFolder(folderId);
162                    }
163    
164                    // Subscriptions
165    
166                    subscriptionLocalService.deleteSubscriptions(
167                            thread.getCompanyId(), MBThread.class.getName(),
168                            thread.getThreadId());
169    
170                    // Thread flags
171    
172                    mbThreadFlagLocalService.deleteThreadFlagsByThreadId(
173                            thread.getThreadId());
174    
175                    // Messages
176    
177                    List<MBMessage> messages = mbMessagePersistence.findByThreadId(
178                            thread.getThreadId());
179    
180                    for (MBMessage message : messages) {
181    
182                            // Ratings
183    
184                            ratingsStatsLocalService.deleteStats(
185                                    message.getWorkflowClassName(), message.getMessageId());
186    
187                            // Asset
188    
189                            assetEntryLocalService.deleteEntry(
190                                    message.getWorkflowClassName(), message.getMessageId());
191    
192                            // Resources
193    
194                            if (!message.isDiscussion()) {
195                                    resourceLocalService.deleteResource(
196                                            message.getCompanyId(), message.getWorkflowClassName(),
197                                            ResourceConstants.SCOPE_INDIVIDUAL, message.getMessageId());
198                            }
199    
200                            // Message
201    
202                            mbMessagePersistence.remove(message);
203    
204                            // Indexer
205    
206                            messageIndexer.delete(message);
207    
208                            // Statistics
209    
210                            if (!message.isDiscussion()) {
211                                    mbStatsUserLocalService.updateStatsUser(
212                                            message.getGroupId(), message.getUserId());
213                            }
214    
215                            // Workflow
216    
217                            workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
218                                    message.getCompanyId(), message.getGroupId(),
219                                    message.getWorkflowClassName(), message.getMessageId());
220                    }
221    
222                    // Category
223    
224                    if ((rootMessage.getCategoryId() !=
225                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
226                            (rootMessage.getCategoryId() !=
227                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
228    
229                            try {
230                                    MBCategory category = mbCategoryPersistence.findByPrimaryKey(
231                                            thread.getCategoryId());
232    
233                                    MBUtil.updateCategoryStatistics(category.getCategoryId());
234                            }
235                            catch (NoSuchCategoryException nsce) {
236                                    if (!thread.isInTrash()) {
237                                            throw nsce;
238                                    }
239                            }
240                    }
241    
242                    // Asset
243    
244                    AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
245                            MBThread.class.getName(), thread.getThreadId());
246    
247                    if (assetEntry != null) {
248                            assetEntry.setTitle(rootMessage.getSubject());
249    
250                            assetEntryLocalService.updateAssetEntry(assetEntry);
251                    }
252    
253                    assetEntryLocalService.deleteEntry(
254                            MBThread.class.getName(), thread.getThreadId());
255    
256                    // Trash
257    
258                    if (thread.isInTrashExplicitly()) {
259                            trashEntryLocalService.deleteEntry(
260                                    MBThread.class.getName(), thread.getThreadId());
261                    }
262                    else {
263                            trashVersionLocalService.deleteTrashVersion(
264                                    MBThread.class.getName(), thread.getThreadId());
265                    }
266    
267                    // Indexer
268    
269                    Indexer<MBThread> threadIndexer =
270                            IndexerRegistryUtil.nullSafeGetIndexer(MBThread.class);
271    
272                    threadIndexer.delete(thread);
273    
274                    // Thread
275    
276                    mbThreadPersistence.remove(thread);
277            }
278    
279            @Override
280            public void deleteThreads(long groupId, long categoryId)
281                    throws PortalException {
282    
283                    deleteThreads(groupId, categoryId, true);
284            }
285    
286            @Override
287            public void deleteThreads(
288                            long groupId, long categoryId, boolean includeTrashedEntries)
289                    throws PortalException {
290    
291                    List<MBThread> threads = mbThreadPersistence.findByG_C(
292                            groupId, categoryId);
293    
294                    for (MBThread thread : threads) {
295                            if (includeTrashedEntries || !thread.isInTrashExplicitly()) {
296                                    mbThreadLocalService.deleteThread(thread);
297                            }
298                    }
299    
300                    if (mbThreadPersistence.countByGroupId(groupId) == 0) {
301                            PortletFileRepositoryUtil.deletePortletRepository(
302                                    groupId, MBConstants.SERVICE_NAME);
303                    }
304            }
305    
306            @Override
307            public MBThread fetchThread(long threadId) {
308                    return mbThreadPersistence.fetchByPrimaryKey(threadId);
309            }
310    
311            @Override
312            public int getCategoryThreadsCount(
313                    long groupId, long categoryId, int status) {
314    
315                    if (status == WorkflowConstants.STATUS_ANY) {
316                            return mbThreadPersistence.countByG_C(groupId, categoryId);
317                    }
318                    else {
319                            return mbThreadPersistence.countByG_C_S(
320                                    groupId, categoryId, status);
321                    }
322            }
323    
324            @Override
325            public List<MBThread> getGroupThreads(
326                    long groupId, long userId, boolean subscribed, boolean includeAnonymous,
327                    QueryDefinition<MBThread> queryDefinition) {
328    
329                    if (userId <= 0) {
330                            return getGroupThreads(groupId, queryDefinition);
331                    }
332    
333                    if (subscribed) {
334                            return mbThreadFinder.findByS_G_U_C(
335                                    groupId, userId, null, queryDefinition);
336                    }
337                    else {
338                            if (includeAnonymous) {
339                                    return mbThreadFinder.findByG_U_C(
340                                            groupId, userId, null, queryDefinition);
341                            }
342                            else {
343                                    return mbThreadFinder.findByG_U_C_A(
344                                            groupId, userId, null, false, queryDefinition);
345                            }
346                    }
347            }
348    
349            @Override
350            public List<MBThread> getGroupThreads(
351                    long groupId, long userId, boolean subscribed,
352                    QueryDefinition<MBThread> queryDefinition) {
353    
354                    return getGroupThreads(
355                            groupId, userId, subscribed, true, queryDefinition);
356            }
357    
358            @Override
359            public List<MBThread> getGroupThreads(
360                    long groupId, long userId, QueryDefinition<MBThread> queryDefinition) {
361    
362                    return getGroupThreads(groupId, userId, false, queryDefinition);
363            }
364    
365            @Override
366            public List<MBThread> getGroupThreads(
367                    long groupId, QueryDefinition<MBThread> queryDefinition) {
368    
369                    if (queryDefinition.isExcludeStatus()) {
370                            return mbThreadPersistence.findByG_NotC_NotS(
371                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
372                                    queryDefinition.getStatus(), queryDefinition.getStart(),
373                                    queryDefinition.getEnd());
374                    }
375                    else {
376                            return mbThreadPersistence.findByG_NotC_S(
377                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
378                                    queryDefinition.getStatus(), queryDefinition.getStart(),
379                                    queryDefinition.getEnd());
380                    }
381            }
382    
383            @Override
384            public int getGroupThreadsCount(
385                    long groupId, long userId, boolean subscribed, boolean includeAnonymous,
386                    QueryDefinition<MBThread> queryDefinition) {
387    
388                    if (userId <= 0) {
389                            return getGroupThreadsCount(groupId, queryDefinition);
390                    }
391    
392                    if (subscribed) {
393                            return mbThreadFinder.countByS_G_U_C(
394                                    groupId, userId, null, queryDefinition);
395                    }
396                    else {
397                            if (includeAnonymous) {
398                                    return mbThreadFinder.countByG_U_C(
399                                            groupId, userId, null, queryDefinition);
400                            }
401                            else {
402                                    return mbThreadFinder.countByG_U_C_A(
403                                            groupId, userId, null, false, queryDefinition);
404                            }
405                    }
406            }
407    
408            @Override
409            public int getGroupThreadsCount(
410                    long groupId, long userId, boolean subscribed,
411                    QueryDefinition<MBThread> queryDefinition) {
412    
413                    return getGroupThreadsCount(
414                            groupId, userId, subscribed, true, queryDefinition);
415            }
416    
417            @Override
418            public int getGroupThreadsCount(
419                    long groupId, long userId, QueryDefinition<MBThread> queryDefinition) {
420    
421                    return getGroupThreadsCount(groupId, userId, false, queryDefinition);
422            }
423    
424            @Override
425            public int getGroupThreadsCount(
426                    long groupId, QueryDefinition<MBThread> queryDefinition) {
427    
428                    if (queryDefinition.isExcludeStatus()) {
429                            return mbThreadPersistence.countByG_NotC_NotS(
430                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
431                                    queryDefinition.getStatus());
432                    }
433                    else {
434                            return mbThreadPersistence.countByG_NotC_S(
435                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
436                                    queryDefinition.getStatus());
437                    }
438            }
439    
440            @Override
441            public List<MBThread> getNoAssetThreads() {
442                    return mbThreadFinder.findByNoAssets();
443            }
444    
445            @Override
446            public List<MBThread> getPriorityThreads(long categoryId, double priority)
447                    throws PortalException {
448    
449                    return getPriorityThreads(categoryId, priority, false);
450            }
451    
452            @Override
453            public List<MBThread> getPriorityThreads(
454                            long categoryId, double priority, boolean inherit)
455                    throws PortalException {
456    
457                    if (!inherit) {
458                            return mbThreadPersistence.findByC_P(categoryId, priority);
459                    }
460    
461                    List<MBThread> threads = new ArrayList<>();
462    
463                    while ((categoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
464                               (categoryId != MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
465    
466                            threads.addAll(
467                                    0, mbThreadPersistence.findByC_P(categoryId, priority));
468    
469                            MBCategory category = mbCategoryPersistence.findByPrimaryKey(
470                                    categoryId);
471    
472                            categoryId = category.getParentCategoryId();
473                    }
474    
475                    return threads;
476            }
477    
478            @Override
479            public MBThread getThread(long threadId) throws PortalException {
480                    return mbThreadPersistence.findByPrimaryKey(threadId);
481            }
482    
483            @Override
484            public List<MBThread> getThreads(
485                    long groupId, long categoryId, int status, int start, int end) {
486    
487                    if (status == WorkflowConstants.STATUS_ANY) {
488                            return mbThreadPersistence.findByG_C(
489                                    groupId, categoryId, start, end);
490                    }
491                    else {
492                            return mbThreadPersistence.findByG_C_S(
493                                    groupId, categoryId, status, start, end);
494                    }
495            }
496    
497            @Override
498            public int getThreadsCount(long groupId, long categoryId, int status) {
499                    if (status == WorkflowConstants.STATUS_ANY) {
500                            return mbThreadPersistence.countByG_C(groupId, categoryId);
501                    }
502                    else {
503                            return mbThreadPersistence.countByG_C_S(
504                                    groupId, categoryId, status);
505                    }
506            }
507    
508            @Override
509            public boolean hasAnswerMessage(long threadId) {
510                    int count = mbMessagePersistence.countByT_A(threadId, true);
511    
512                    if (count > 0) {
513                            return true;
514                    }
515                    else {
516                            return false;
517                    }
518            }
519    
520            @BufferedIncrement(
521                    configuration = "MBThread", incrementClass = NumberIncrement.class
522            )
523            @Override
524            public void incrementViewCounter(long threadId, int increment)
525                    throws PortalException {
526    
527                    if (ExportImportThreadLocal.isImportInProcess()) {
528                            return;
529                    }
530    
531                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
532    
533                    thread.setModifiedDate(thread.getModifiedDate());
534                    thread.setViewCount(thread.getViewCount() + increment);
535    
536                    mbThreadPersistence.update(thread);
537            }
538    
539            @Override
540            public void moveDependentsToTrash(
541                            long groupId, long threadId, long trashEntryId)
542                    throws PortalException {
543    
544                    Set<Long> userIds = new HashSet<>();
545    
546                    MBThread thread = mbThreadLocalService.getThread(threadId);
547    
548                    List<MBMessage> messages = mbMessageLocalService.getThreadMessages(
549                            threadId, WorkflowConstants.STATUS_ANY);
550    
551                    for (MBMessage message : messages) {
552    
553                            // Message
554    
555                            if (message.isDiscussion()) {
556                                    continue;
557                            }
558    
559                            int oldStatus = message.getStatus();
560    
561                            message.setStatus(WorkflowConstants.STATUS_IN_TRASH);
562    
563                            mbMessagePersistence.update(message);
564    
565                            userIds.add(message.getUserId());
566    
567                            // Trash
568    
569                            int status = oldStatus;
570    
571                            if (oldStatus == WorkflowConstants.STATUS_PENDING) {
572                                    status = WorkflowConstants.STATUS_DRAFT;
573                            }
574    
575                            if (oldStatus != WorkflowConstants.STATUS_APPROVED) {
576                                    trashVersionLocalService.addTrashVersion(
577                                            trashEntryId, MBMessage.class.getName(),
578                                            message.getMessageId(), status, null);
579                            }
580    
581                            // Asset
582    
583                            if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
584                                    assetEntryLocalService.updateVisible(
585                                            MBMessage.class.getName(), message.getMessageId(), false);
586                            }
587    
588                            // Attachments
589    
590                            for (FileEntry fileEntry : message.getAttachmentsFileEntries()) {
591                                    PortletFileRepositoryUtil.movePortletFileEntryToTrash(
592                                            thread.getStatusByUserId(), fileEntry.getFileEntryId());
593                            }
594    
595                            // Indexer
596    
597                            Indexer<MBMessage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
598                                    MBMessage.class);
599    
600                            indexer.reindex(message);
601    
602                            // Workflow
603    
604                            if (oldStatus == WorkflowConstants.STATUS_PENDING) {
605                                    workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
606                                            message.getCompanyId(), message.getGroupId(),
607                                            MBMessage.class.getName(), message.getMessageId());
608                            }
609                    }
610    
611                    // Statistics
612    
613                    for (long userId : userIds) {
614                            mbStatsUserLocalService.updateStatsUser(groupId, userId);
615                    }
616            }
617    
618            @Override
619            public MBThread moveThread(long groupId, long categoryId, long threadId)
620                    throws PortalException {
621    
622                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
623    
624                    long oldCategoryId = thread.getCategoryId();
625    
626                    MBCategory oldCategory = null;
627    
628                    if (oldCategoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
629                            oldCategory = mbCategoryPersistence.fetchByPrimaryKey(
630                                    oldCategoryId);
631                    }
632    
633                    MBCategory category = null;
634    
635                    if (categoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
636                            category = mbCategoryPersistence.fetchByPrimaryKey(categoryId);
637                    }
638    
639                    // Thread
640    
641                    thread.setCategoryId(categoryId);
642    
643                    mbThreadPersistence.update(thread);
644    
645                    // Messages
646    
647                    List<MBMessage> messages = mbMessagePersistence.findByG_C_T(
648                            groupId, oldCategoryId, thread.getThreadId());
649    
650                    for (MBMessage message : messages) {
651                            message.setCategoryId(categoryId);
652    
653                            mbMessagePersistence.update(message);
654    
655                            // Indexer
656    
657                            if (!message.isDiscussion()) {
658                                    Indexer<MBMessage> indexer =
659                                            IndexerRegistryUtil.nullSafeGetIndexer(MBMessage.class);
660    
661                                    indexer.reindex(message);
662                            }
663                    }
664    
665                    // Category
666    
667                    if ((oldCategory != null) && (categoryId != oldCategoryId)) {
668                            MBUtil.updateCategoryStatistics(oldCategory.getCategoryId());
669                    }
670    
671                    if ((category != null) && (categoryId != oldCategoryId)) {
672                            MBUtil.updateCategoryStatistics(category.getCategoryId());
673                    }
674    
675                    // Indexer
676    
677                    Indexer<MBThread> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
678                            MBThread.class);
679    
680                    indexer.reindex(thread);
681    
682                    return thread;
683            }
684    
685            @Override
686            public MBThread moveThreadFromTrash(
687                            long userId, long categoryId, long threadId)
688                    throws PortalException {
689    
690                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
691    
692                    if (!thread.isInTrash()) {
693                            throw new RestoreEntryException(
694                                    RestoreEntryException.INVALID_STATUS);
695                    }
696    
697                    if (thread.isInTrashExplicitly()) {
698                            restoreThreadFromTrash(userId, threadId);
699                    }
700                    else {
701    
702                            // Thread
703    
704                            TrashVersion trashVersion = trashVersionLocalService.fetchVersion(
705                                    MBThread.class.getName(), thread.getThreadId());
706    
707                            int status = WorkflowConstants.STATUS_APPROVED;
708    
709                            if (trashVersion != null) {
710                                    status = trashVersion.getStatus();
711                            }
712    
713                            updateStatus(userId, threadId, status);
714    
715                            // Trash
716    
717                            if (trashVersion != null) {
718                                    trashVersionLocalService.deleteTrashVersion(trashVersion);
719                            }
720    
721                            // Messages
722    
723                            restoreDependentsFromTrash(thread.getGroupId(), threadId);
724                    }
725    
726                    return moveThread(thread.getGroupId(), categoryId, threadId);
727            }
728    
729            @Override
730            public void moveThreadsToTrash(long groupId, long userId)
731                    throws PortalException {
732    
733                    List<MBThread> threads = mbThreadPersistence.findByGroupId(groupId);
734    
735                    for (MBThread thread : threads) {
736                            moveThreadToTrash(userId, thread);
737                    }
738            }
739    
740            @Override
741            public MBThread moveThreadToTrash(long userId, long threadId)
742                    throws PortalException {
743    
744                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
745    
746                    return moveThreadToTrash(userId, thread);
747            }
748    
749            @Override
750            public MBThread moveThreadToTrash(long userId, MBThread thread)
751                    throws PortalException {
752    
753                    // Thread
754    
755                    if (thread.isInTrash()) {
756                            throw new TrashEntryException();
757                    }
758    
759                    if (thread.getCategoryId() ==
760                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID) {
761    
762                            return thread;
763                    }
764    
765                    int oldStatus = thread.getStatus();
766    
767                    if (oldStatus == WorkflowConstants.STATUS_PENDING) {
768                            thread.setStatus(WorkflowConstants.STATUS_DRAFT);
769    
770                            mbThreadPersistence.update(thread);
771                    }
772    
773                    thread = updateStatus(
774                            userId, thread.getThreadId(), WorkflowConstants.STATUS_IN_TRASH);
775    
776                    // Trash
777    
778                    TrashEntry trashEntry = trashEntryLocalService.addTrashEntry(
779                            userId, thread.getGroupId(), MBThread.class.getName(),
780                            thread.getThreadId(), thread.getUuid(), null, oldStatus, null,
781                            null);
782    
783                    // Messages
784    
785                    moveDependentsToTrash(
786                            thread.getGroupId(), thread.getThreadId(), trashEntry.getEntryId());
787    
788                    // Social
789    
790                    MBMessage message = mbMessageLocalService.getMBMessage(
791                            thread.getRootMessageId());
792    
793                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
794    
795                    extraDataJSONObject.put("rootMessageId", thread.getRootMessageId());
796                    extraDataJSONObject.put("title", message.getSubject());
797    
798                    SocialActivityManagerUtil.addActivity(
799                            userId, thread, SocialActivityConstants.TYPE_MOVE_TO_TRASH,
800                            extraDataJSONObject.toString(), 0);
801    
802                    return thread;
803            }
804    
805            @Override
806            public void restoreDependentsFromTrash(long groupId, long threadId)
807                    throws PortalException {
808    
809                    Set<Long> userIds = new HashSet<>();
810    
811                    MBThread thread = mbThreadLocalService.getThread(threadId);
812    
813                    List<MBMessage> messages = mbMessageLocalService.getThreadMessages(
814                            threadId, WorkflowConstants.STATUS_ANY);
815    
816                    for (MBMessage message : messages) {
817    
818                            // Message
819    
820                            if (message.isDiscussion()) {
821                                    continue;
822                            }
823    
824                            TrashVersion trashVersion = trashVersionLocalService.fetchVersion(
825                                    MBMessage.class.getName(), message.getMessageId());
826    
827                            int oldStatus = WorkflowConstants.STATUS_APPROVED;
828    
829                            if (trashVersion != null) {
830                                    oldStatus = trashVersion.getStatus();
831                            }
832    
833                            message.setStatus(oldStatus);
834    
835                            mbMessagePersistence.update(message);
836    
837                            userIds.add(message.getUserId());
838    
839                            // Trash
840    
841                            if (trashVersion != null) {
842                                    trashVersionLocalService.deleteTrashVersion(trashVersion);
843                            }
844    
845                            // Asset
846    
847                            if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
848                                    assetEntryLocalService.updateVisible(
849                                            MBMessage.class.getName(), message.getMessageId(), true);
850                            }
851    
852                            // Attachments
853    
854                            for (FileEntry fileEntry : message.getAttachmentsFileEntries()) {
855                                    PortletFileRepositoryUtil.restorePortletFileEntryFromTrash(
856                                            thread.getStatusByUserId(), fileEntry.getFileEntryId());
857                            }
858    
859                            // Indexer
860    
861                            Indexer<MBMessage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
862                                    MBMessage.class);
863    
864                            indexer.reindex(message);
865                    }
866    
867                    // Statistics
868    
869                    for (long userId : userIds) {
870                            mbStatsUserLocalService.updateStatsUser(groupId, userId);
871                    }
872            }
873    
874            /**
875             * @deprecated As of 7.0.0, replaced by {@link
876             *             #restoreDependentsFromTrash(long, long)}
877             */
878            @Deprecated
879            @Override
880            public void restoreDependentsFromTrash(
881                            long groupId, long threadId, long trashEntryId)
882                    throws PortalException {
883    
884                    restoreDependentsFromTrash(groupId, threadId);
885            }
886    
887            @Override
888            public void restoreThreadFromTrash(long userId, long threadId)
889                    throws PortalException {
890    
891                    // Thread
892    
893                    MBThread thread = getThread(threadId);
894    
895                    if (!thread.isInTrash()) {
896                            throw new RestoreEntryException(
897                                    RestoreEntryException.INVALID_STATUS);
898                    }
899    
900                    if (thread.getCategoryId() ==
901                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID) {
902    
903                            return;
904                    }
905    
906                    TrashEntry trashEntry = trashEntryLocalService.getEntry(
907                            MBThread.class.getName(), threadId);
908    
909                    updateStatus(userId, threadId, trashEntry.getStatus());
910    
911                    // Messages
912    
913                    restoreDependentsFromTrash(thread.getGroupId(), threadId);
914    
915                    // Trash
916    
917                    trashEntryLocalService.deleteEntry(trashEntry.getEntryId());
918    
919                    // Social
920    
921                    MBMessage message = mbMessageLocalService.getMBMessage(
922                            thread.getRootMessageId());
923    
924                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
925    
926                    extraDataJSONObject.put("rootMessageId", thread.getRootMessageId());
927                    extraDataJSONObject.put("title", message.getSubject());
928    
929                    SocialActivityManagerUtil.addActivity(
930                            userId, thread, SocialActivityConstants.TYPE_RESTORE_FROM_TRASH,
931                            extraDataJSONObject.toString(), 0);
932            }
933    
934            @Override
935            public Hits search(
936                            long groupId, long userId, long creatorUserId, int status,
937                            int start, int end)
938                    throws PortalException {
939    
940                    return search(groupId, userId, creatorUserId, 0, 0, status, start, end);
941            }
942    
943            @Override
944            public Hits search(
945                            long groupId, long userId, long creatorUserId, long startDate,
946                            long endDate, int status, int start, int end)
947                    throws PortalException {
948    
949                    Indexer<MBThread> indexer = IndexerRegistryUtil.getIndexer(
950                            MBThread.class.getName());
951    
952                    SearchContext searchContext = new SearchContext();
953    
954                    searchContext.setAttribute(Field.STATUS, status);
955    
956                    if (endDate > 0) {
957                            searchContext.setAttribute("endDate", endDate);
958                    }
959    
960                    searchContext.setAttribute("paginationType", "none");
961    
962                    if (creatorUserId > 0) {
963                            searchContext.setAttribute(
964                                    "participantUserId", String.valueOf(creatorUserId));
965                    }
966    
967                    if (startDate > 0) {
968                            searchContext.setAttribute("startDate", startDate);
969                    }
970    
971                    Group group = groupLocalService.getGroup(groupId);
972    
973                    searchContext.setCompanyId(group.getCompanyId());
974    
975                    searchContext.setEnd(end);
976                    searchContext.setGroupIds(new long[] {groupId});
977                    searchContext.setSorts(new Sort("lastPostDate", true));
978                    searchContext.setStart(start);
979                    searchContext.setUserId(userId);
980    
981                    return indexer.search(searchContext);
982            }
983    
984            @Override
985            public MBThread splitThread(
986                            long userId, long messageId, String subject,
987                            ServiceContext serviceContext)
988                    throws PortalException {
989    
990                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
991    
992                    if (message.isRoot()) {
993                            throw new SplitThreadException(
994                                    "Unable to split message " + messageId +
995                                            " because it is a root message");
996                    }
997    
998                    MBCategory category = message.getCategory();
999    
1000                    MBThread oldThread = message.getThread();
1001    
1002                    MBMessage rootMessage = mbMessagePersistence.findByPrimaryKey(
1003                            oldThread.getRootMessageId());
1004    
1005                    long oldAttachmentsFolderId = message.getAttachmentsFolderId();
1006    
1007                    // Message flags
1008    
1009                    mbMessageLocalService.updateAnswer(message, false, true);
1010    
1011                    // Create new thread
1012    
1013                    MBThread thread = addThread(
1014                            message.getCategoryId(), message, serviceContext);
1015    
1016                    mbThreadPersistence.update(oldThread);
1017    
1018                    // Update messages
1019    
1020                    if (Validator.isNotNull(subject)) {
1021                            MBMessageDisplay messageDisplay =
1022                                    mbMessageLocalService.getMessageDisplay(
1023                                            userId, messageId, WorkflowConstants.STATUS_ANY);
1024    
1025                            MBTreeWalker treeWalker = messageDisplay.getTreeWalker();
1026    
1027                            List<MBMessage> messages = treeWalker.getMessages();
1028    
1029                            int[] range = treeWalker.getChildrenRange(message);
1030    
1031                            for (int i = range[0]; i < range[1]; i++) {
1032                                    MBMessage curMessage = messages.get(i);
1033    
1034                                    String oldSubject = message.getSubject();
1035                                    String curSubject = curMessage.getSubject();
1036    
1037                                    if (oldSubject.startsWith("RE: ")) {
1038                                            curSubject = StringUtil.replace(
1039                                                    curSubject, rootMessage.getSubject(), subject);
1040                                    }
1041                                    else {
1042                                            curSubject = StringUtil.replace(
1043                                                    curSubject, oldSubject, subject);
1044                                    }
1045    
1046                                    curMessage.setSubject(curSubject);
1047    
1048                                    mbMessagePersistence.update(curMessage);
1049                            }
1050    
1051                            message.setSubject(subject);
1052                    }
1053    
1054                    message.setThreadId(thread.getThreadId());
1055                    message.setRootMessageId(thread.getRootMessageId());
1056                    message.setParentMessageId(0);
1057    
1058                    mbMessagePersistence.update(message);
1059    
1060                    // Attachments
1061    
1062                    moveAttachmentsFolders(
1063                            message, oldAttachmentsFolderId, oldThread, thread, serviceContext);
1064    
1065                    // Indexer
1066    
1067                    if (!message.isDiscussion()) {
1068                            Indexer<MBMessage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1069                                    MBMessage.class);
1070    
1071                            indexer.reindex(message);
1072                    }
1073    
1074                    // Update children
1075    
1076                    moveChildrenMessages(message, category, oldThread.getThreadId());
1077    
1078                    // Update new thread
1079    
1080                    MBUtil.updateThreadMessageCount(thread.getThreadId());
1081    
1082                    // Update old thread
1083    
1084                    MBUtil.updateThreadMessageCount(oldThread.getThreadId());
1085    
1086                    // Category
1087    
1088                    if ((message.getCategoryId() !=
1089                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
1090                            (message.getCategoryId() !=
1091                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
1092    
1093                            MBUtil.updateCategoryThreadCount(category.getCategoryId());
1094                    }
1095    
1096                    // Indexer
1097    
1098                    Indexer<MBThread> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1099                            MBThread.class);
1100    
1101                    indexer.reindex(oldThread);
1102    
1103                    indexer.reindex(message.getThread());
1104    
1105                    return thread;
1106            }
1107    
1108            @Override
1109            public MBThread updateMessageCount(long threadId) {
1110                    MBThread mbThread = mbThreadPersistence.fetchByPrimaryKey(threadId);
1111    
1112                    if (mbThread == null) {
1113                            return null;
1114                    }
1115    
1116                    int messageCount = mbMessageLocalService.getThreadMessagesCount(
1117                            threadId, WorkflowConstants.STATUS_APPROVED);
1118    
1119                    mbThread.setMessageCount(messageCount);
1120    
1121                    return mbThreadPersistence.update(mbThread);
1122            }
1123    
1124            @Override
1125            public void updateQuestion(long threadId, boolean question)
1126                    throws PortalException {
1127    
1128                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
1129    
1130                    if (thread.isQuestion() == question) {
1131                            return;
1132                    }
1133    
1134                    thread.setQuestion(question);
1135    
1136                    mbThreadPersistence.update(thread);
1137    
1138                    if (!question) {
1139                            MBMessage message = mbMessagePersistence.findByPrimaryKey(
1140                                    thread.getRootMessageId());
1141    
1142                            mbMessageLocalService.updateAnswer(message, false, true);
1143                    }
1144            }
1145    
1146            @Override
1147            public MBThread updateStatus(long userId, long threadId, int status)
1148                    throws PortalException {
1149    
1150                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
1151    
1152                    // Thread
1153    
1154                    User user = userPersistence.findByPrimaryKey(userId);
1155    
1156                    thread.setStatus(status);
1157                    thread.setStatusByUserId(user.getUserId());
1158                    thread.setStatusByUserName(user.getFullName());
1159                    thread.setStatusDate(new Date());
1160    
1161                    mbThreadPersistence.update(thread);
1162    
1163                    // Messages
1164    
1165                    if (thread.getCategoryId() !=
1166                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
1167    
1168                            // Category
1169    
1170                            MBCategory category = mbCategoryPersistence.fetchByPrimaryKey(
1171                                    thread.getCategoryId());
1172    
1173                            if (category != null) {
1174                                    MBUtil.updateCategoryStatistics(category.getCategoryId());
1175                            }
1176                    }
1177    
1178                    // Indexer
1179    
1180                    Indexer<MBThread> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1181                            MBThread.class);
1182    
1183                    indexer.reindex(thread);
1184    
1185                    return thread;
1186            }
1187    
1188            protected void moveAttachmentsFolders(
1189                            MBMessage message, long oldAttachmentsFolderId, MBThread oldThread,
1190                            MBThread newThread, ServiceContext serviceContext)
1191                    throws PortalException {
1192    
1193                    if (oldAttachmentsFolderId !=
1194                                    DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
1195    
1196                            Folder newThreadFolder = newThread.addAttachmentsFolder();
1197    
1198                            PortletFileRepositoryUtil.movePortletFolder(
1199                                    message.getGroupId(), message.getUserId(),
1200                                    oldAttachmentsFolderId, newThreadFolder.getFolderId(),
1201                                    serviceContext);
1202                    }
1203    
1204                    List<MBMessage> childMessages = mbMessagePersistence.findByT_P(
1205                            oldThread.getThreadId(), message.getMessageId());
1206    
1207                    for (MBMessage childMessage : childMessages) {
1208                            moveAttachmentsFolders(
1209                                    childMessage, childMessage.getAttachmentsFolderId(), oldThread,
1210                                    newThread, serviceContext);
1211                    }
1212            }
1213    
1214            protected void moveChildrenMessages(
1215                            MBMessage parentMessage, MBCategory category, long oldThreadId)
1216                    throws PortalException {
1217    
1218                    List<MBMessage> messages = mbMessagePersistence.findByT_P(
1219                            oldThreadId, parentMessage.getMessageId());
1220    
1221                    for (MBMessage message : messages) {
1222                            message.setCategoryId(parentMessage.getCategoryId());
1223                            message.setThreadId(parentMessage.getThreadId());
1224                            message.setRootMessageId(parentMessage.getRootMessageId());
1225    
1226                            mbMessagePersistence.update(message);
1227    
1228                            if (!message.isDiscussion()) {
1229                                    Indexer<MBMessage> indexer =
1230                                            IndexerRegistryUtil.nullSafeGetIndexer(MBMessage.class);
1231    
1232                                    indexer.reindex(message);
1233                            }
1234    
1235                            moveChildrenMessages(message, category, oldThreadId);
1236                    }
1237            }
1238    
1239    }