protected void sendNotificationEvent(MBMessage mbMessage) throws PortalException {

    JSONObject notificationEventJSONObject = JSONFactoryUtil.createJSONObject();

    notificationEventJSONObject.put("classPK", mbMessage.getMessageId());
    notificationEventJSONObject.put("userId", mbMessage.getUserId());

    List<UserThread> userThreads =
        UserThreadLocalServiceUtil.getMBThreadUserThreads(mbMessage.getThreadId());

    for (UserThread userThread : userThreads) {
      if ((userThread.getUserId() == mbMessage.getUserId())
          || ((userThread.getUserId() != mbMessage.getUserId())
              && !UserNotificationManagerUtil.isDeliver(
                  userThread.getUserId(),
                  PortletKeys.PRIVATE_MESSAGING,
                  0,
                  PrivateMessagingConstants.NEW_MESSAGE,
                  UserNotificationDeliveryConstants.TYPE_WEBSITE))) {

        continue;
      }

      UserNotificationEventLocalServiceUtil.sendUserNotificationEvents(
          userThread.getUserId(),
          PortletKeys.PRIVATE_MESSAGING,
          UserNotificationDeliveryConstants.TYPE_WEBSITE,
          notificationEventJSONObject);
    }
  }
  public static List<User> getThreadUsers(long userId, long mbThreadId)
      throws PortalException, SystemException {

    List<User> users = new ArrayList<User>();

    // Users who have contributed to the thread

    List<MBMessage> mbMessages =
        UserThreadServiceUtil.getThreadMessages(
            mbThreadId, QueryUtil.ALL_POS, QueryUtil.ALL_POS, false);

    for (MBMessage mbMessage : mbMessages) {
      if (userId == mbMessage.getUserId()) {
        continue;
      }

      User user = UserLocalServiceUtil.fetchUser(mbMessage.getUserId());

      if (user == null) {
        user = UserLocalServiceUtil.createUser(mbMessage.getUserId());

        user.setFirstName(mbMessage.getUserName());
        user.setStatus(WorkflowConstants.STATUS_INACTIVE);
      }

      if (!users.contains(user)) {
        users.add(user);
      }
    }

    // Users who can view the thread

    List<UserThread> userThreads = UserThreadLocalServiceUtil.getMBThreadUserThreads(mbThreadId);

    for (UserThread userThread : userThreads) {
      if (userId == userThread.getUserId()) {
        continue;
      }

      User user = UserLocalServiceUtil.fetchUser(userThread.getUserId());

      if (user == null) {
        user = UserLocalServiceUtil.createUser(userThread.getUserId());

        user.setFirstName(userThread.getUserName());
        user.setStatus(WorkflowConstants.STATUS_INACTIVE);
      }

      if (!users.contains(user)) {
        users.add(user);
      }
    }

    return users;
  }
  /**
   * Each thread has a user that represents that thread. This person is either the last user to post
   * on that thread (exluding himself), or if he is the only person to have posted on the thread,
   * then he will the represenative.
   */
  public static long getThreadRepresentativeUserId(long userId, long mbThreadId) {

    List<MBMessage> mbMessages =
        MBMessageLocalServiceUtil.getThreadMessages(
            mbThreadId, WorkflowConstants.STATUS_ANY, new MessageCreateDateComparator(false));

    for (MBMessage mbMessage : mbMessages) {
      if (userId != mbMessage.getUserId()) {
        return mbMessage.getUserId();
      }
    }

    List<UserThread> userThreads = UserThreadLocalServiceUtil.getMBThreadUserThreads(mbThreadId);

    for (UserThread userThread : userThreads) {
      if (userId != userThread.getUserId()) {
        return userThread.getUserId();
      }
    }

    return userId;
  }
  protected void sendEmail(long mbMessageId, ThemeDisplay themeDisplay) throws Exception {

    MBMessage mbMessage = MBMessageLocalServiceUtil.getMBMessage(mbMessageId);

    User sender = UserLocalServiceUtil.getUser(mbMessage.getUserId());

    Company company = CompanyLocalServiceUtil.getCompany(sender.getCompanyId());

    InternetAddress from = new InternetAddress(company.getEmailAddress());

    String subject =
        StringUtil.read(
            PrivateMessagingPortlet.class.getResourceAsStream(
                "dependencies/notification_message_subject.tmpl"));

    subject =
        StringUtil.replace(
            subject,
            new String[] {"[$COMPANY_NAME$]", "[$FROM_NAME$]"},
            new String[] {company.getName(), sender.getFullName()});

    String body =
        StringUtil.read(
            PrivateMessagingPortlet.class.getResourceAsStream(
                "dependencies/notification_message_body.tmpl"));

    long portraitId = sender.getPortraitId();
    String tokenId = WebServerServletTokenUtil.getToken(sender.getPortraitId());
    String portraitURL =
        themeDisplay.getPortalURL()
            + themeDisplay.getPathImage()
            + "/user_"
            + (sender.isFemale() ? "female" : "male")
            + "_portrait?img_id="
            + portraitId
            + "&t="
            + tokenId;

    body =
        StringUtil.replace(
            body,
            new String[] {
              "[$BODY$]", "[$COMPANY_NAME$]", "[$FROM_AVATAR$]",
              "[$FROM_NAME$]", "[$FROM_PROFILE_URL$]", "[$SUBJECT$]"
            },
            new String[] {
              mbMessage.getBody(),
              company.getName(),
              portraitURL,
              sender.getFullName(),
              sender.getDisplayURL(themeDisplay),
              mbMessage.getSubject()
            });

    List<UserThread> userThreads =
        UserThreadLocalServiceUtil.getMBThreadUserThreads(mbMessage.getThreadId());

    for (UserThread userThread : userThreads) {
      if ((userThread.getUserId() == mbMessage.getUserId())
          && UserNotificationManagerUtil.isDeliver(
              userThread.getUserId(),
              PortletKeys.PRIVATE_MESSAGING,
              PrivateMessagingConstants.NEW_MESSAGE,
              0,
              UserNotificationDeliveryConstants.TYPE_EMAIL)) {

        continue;
      }

      User recipient = UserLocalServiceUtil.getUser(userThread.getUserId());

      String threadURL = getThreadURL(recipient, mbMessage.getThreadId(), themeDisplay);

      if (Validator.isNull(threadURL)) {
        continue;
      }

      InternetAddress to = new InternetAddress(recipient.getEmailAddress());

      Format dateFormatDateTime =
          FastDateFormatFactoryUtil.getDateTime(
              FastDateFormatConstants.LONG,
              FastDateFormatConstants.SHORT,
              recipient.getLocale(),
              recipient.getTimeZone());

      String userThreadBody =
          StringUtil.replace(
              body,
              new String[] {"[$SENT_DATE$]", "[$THREAD_URL$]"},
              new String[] {dateFormatDateTime.format(mbMessage.getCreateDate()), threadURL});

      MailMessage mailMessage = new MailMessage(from, to, subject, userThreadBody, true);

      MailServiceUtil.sendEmail(mailMessage);
    }
  }
  protected MBMessage addPrivateMessage(
      long userId,
      long mbThreadId,
      long parentMBMessageId,
      List<User> recipients,
      String subject,
      String body,
      List<ObjectValuePair<String, InputStream>> inputStreamOVPs,
      ThemeDisplay themeDisplay)
      throws PortalException {

    User user = UserLocalServiceUtil.getUser(userId);
    Group group = GroupLocalServiceUtil.getCompanyGroup(user.getCompanyId());
    long categoryId = PrivateMessagingConstants.PRIVATE_MESSAGING_CATEGORY_ID;

    if (Validator.isNull(subject)) {
      subject = StringUtil.shorten(body, 50);
    }

    boolean anonymous = false;
    double priority = 0.0;
    boolean allowPingbacks = false;

    ServiceContext serviceContext = new ServiceContext();

    serviceContext.setWorkflowAction(WorkflowConstants.ACTION_SAVE_DRAFT);

    MBMessage mbMessage =
        MBMessageLocalServiceUtil.addMessage(
            userId,
            user.getScreenName(),
            group.getGroupId(),
            categoryId,
            mbThreadId,
            parentMBMessageId,
            subject,
            body,
            MBMessageConstants.DEFAULT_FORMAT,
            inputStreamOVPs,
            anonymous,
            priority,
            allowPingbacks,
            serviceContext);

    if (mbThreadId == 0) {
      for (User recipient : recipients) {
        if (recipient.getUserId() != userId) {
          addUserThread(
              recipient.getUserId(),
              mbMessage.getThreadId(),
              mbMessage.getMessageId(),
              false,
              false);
        }
      }

      addUserThread(userId, mbMessage.getThreadId(), mbMessage.getMessageId(), true, false);
    } else {
      List<UserThread> userThreads =
          userThreadPersistence.findByMBThreadId(mbMessage.getThreadId());

      for (UserThread userThread : userThreads) {
        userThread.setModifiedDate(new Date());

        if (userThread.getUserId() == userId) {
          userThread.setRead(true);
        } else {
          userThread.setRead(false);
        }

        if (userThread.isDeleted()) {
          userThread.setTopMBMessageId(mbMessage.getMessageId());
          userThread.setDeleted(false);
        }

        userThreadPersistence.update(userThread);
      }
    }

    // Email

    try {
      sendEmail(mbMessage.getMessageId(), themeDisplay);
    } catch (Exception e) {
      throw new SystemException(e);
    }

    // Notifications

    sendNotificationEvent(mbMessage);

    return mbMessage;
  }