ServiceNotifications.java

/*
 * Copyright (C) 2000 - 2025 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.silverpeas.mobile.server.services;

import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import org.silverpeas.components.kmelia.model.TopicDetail;
import org.silverpeas.components.kmelia.service.KmeliaService;
import org.silverpeas.core.admin.ProfiledObjectId;
import org.silverpeas.core.admin.component.model.ComponentInst;
import org.silverpeas.core.admin.service.AdminException;
import org.silverpeas.core.admin.service.Administration;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.admin.user.model.*;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.contribution.publication.model.PublicationPK;
import org.silverpeas.core.node.model.NodeDetail;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.core.node.service.NodeService;
import org.silverpeas.core.notification.NotificationException;
import org.silverpeas.core.notification.user.client.GroupRecipient;
import org.silverpeas.core.notification.user.client.NotificationMetaData;
import org.silverpeas.core.notification.user.client.NotificationSender;
import org.silverpeas.core.notification.user.client.UserRecipient;
import org.silverpeas.core.notification.user.client.model.SentNotificationDetail;
import org.silverpeas.core.notification.user.client.model.SentNotificationInterface;
import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILMessage;
import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILPersistence;
import org.silverpeas.core.notification.user.server.channel.silvermail.SilvermailCriteria;
import org.silverpeas.core.web.rs.UserPrivilegeValidation;
import org.silverpeas.core.web.rs.annotation.Authorized;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.mobile.server.services.helpers.UserHelper;
import org.silverpeas.mobile.shared.StreamingList;
import org.silverpeas.mobile.shared.dto.BaseDTO;
import org.silverpeas.mobile.shared.dto.GroupDTO;
import org.silverpeas.mobile.shared.dto.UserDTO;
import org.silverpeas.mobile.shared.dto.notifications.*;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Service de gestion des Notifications.
 *
 * @author svuillet
 */
@WebService
@Authorized
@Path(ServiceNotifications.PATH)
public class ServiceNotifications extends AbstractRestWebService {

  @Inject
  private OrganizationController organizationController;
  static final String PATH = "mobile/notification";

  @Context
  HttpServletRequest request;

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("sended/{callNumber}")
  public StreamingList<NotificationSendedDTO> getUserSendedNotifications(
      @PathParam("callNumber") int callNumber) {

    //noinspection unchecked
    return (StreamingList<NotificationSendedDTO>) makeStreamingList(callNumber,
        "Cache_userNotificationsSended", request, new Populator() {
      @Override
      public List<?> execute() {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        List<NotificationSendedDTO> list = new ArrayList<>();
        try {
          List<SentNotificationDetail> notifications =
              SentNotificationInterface.get().getAllNotifByUser(getUser().getId());
          for (SentNotificationDetail notif : notifications) {
            NotificationSendedDTO dto = new NotificationSendedDTO();
            dto.setSource(notif.getBody());
            dto.setDate(sdf.format(notif.getNotifDate()));
            dto.setLink(notif.getLink());
            dto.setTitle(notif.getTitle());
            dto.setIdNotif(notif.getNotifId());
            list.add(dto);
          }
        } catch (NotificationException e) {
          SilverLogger.getLogger(this).error(e);
        }
        return list;
      }
    });

  }


  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("received/{callNumber}")
  public StreamingList<NotificationReceivedDTO> getUserNotifications(
      @PathParam("callNumber") int callNumber) {

    //noinspection unchecked
    return (StreamingList<NotificationReceivedDTO>) makeStreamingList(callNumber,
        "Cache_userNotificationsReceived", request, () -> {
      List<NotificationReceivedDTO> list = new ArrayList<>();
      SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
      Collection<SILVERMAILMessage> messages =
          SILVERMAILPersistence.getMessageOfFolder(getUser().getId(), "INBOX", null,
              SilvermailCriteria.QUERY_ORDER_BY.RECEPTION_DATE_ASC);
      for (SILVERMAILMessage message : messages) {
        NotificationReceivedDTO dto = new NotificationReceivedDTO();
        dto.setSource(message.getSource());
        dto.setAuthor(message.getSenderName());
        dto.setDate(sdf.format(message.getDate()));
        dto.setLink(message.getUrl());
        dto.setReaden(message.getReaden());
        dto.setTitle(message.getSubject());
        dto.setIdNotif(message.getId());
        list.add(dto);
      }
      Collections.reverse(list);
      return list;
    });
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("allowedUsersAndGroups/{componentId}/{contentId}")
  public List<BaseDTO> getAllowedUsersAndGroups(@PathParam("componentId") String componentId,
      @PathParam("contentId") String contentId) throws Exception {
    ArrayList<BaseDTO> usersAndGroups = new ArrayList<>();
    ArrayList<UserDTO> users = new ArrayList<>();
    ArrayList<GroupDTO> groups = new ArrayList<>();

    try {
      if (componentId.equalsIgnoreCase("null")) {
        for (UserDetail user : Administration.get().getAllUsers()) {
          users.add(populate(user));
        }
        for (GroupDetail group : Administration.get().getAllGroups()) {
          GroupDTO g = UserHelper.getInstance().populateGroupDTO(group);
          groups.add(g);
        }
      } else {
        ComponentInst componentInst = Administration.get().getComponentInst(componentId);
        List<ProfileInst> profiles = new ArrayList<>();

        if (componentId.toLowerCase().startsWith("kmelia") && isRightsOnTopicsEnabled(componentId)) {
          TopicDetail topic = getKmeliaService().getBestTopicDetailOfPublicationForUser(
              new PublicationPK(contentId, componentId), true, getUser().getId());
          if (topic != null) {
            NodePK pk = topic.getNodePK();
            NodeDetail node = getNodeService().getDetail(pk);
            if (node.haveRights()) {
              if (node.haveLocalRights()) {
                profiles.addAll(getTopicProfiles(pk.getId(), componentId));
              } else if (node.haveInheritedRights()) {
                profiles.addAll(
                    getTopicProfiles(String.valueOf(node.getRightsDependsOn()), componentId));
              }

            } else {
              profiles.addAll(componentInst.getInheritedProfiles());
              profiles.addAll(componentInst.getProfiles());
            }
          } else {
            profiles.addAll(componentInst.getInheritedProfiles());
            profiles.addAll(componentInst.getProfiles());
          }
        } else {
          profiles.addAll(componentInst.getInheritedProfiles());
          profiles.addAll(componentInst.getProfiles());
        }

        for (ProfileInst profile : profiles) {
          for (String groupId : profile.getAllGroups()) {
            if (!isGroupPresent(groups, groupId)) {
              Group group = organizationController.getGroup(groupId);
              GroupDTO g = UserHelper.getInstance().populateGroupDTO(group);
              groups.add(g);
            }
          }
        }

        for (ProfileInst profile : profiles) {
          for (String userId : profile.getAllUsers()) {
            if (!isUserPresent(users, userId)) {
              UserDTO u = populate(userId);
              users.add(u);
            }
          }
          for (String groupId : profile.getAllGroups()) {
            Group group = organizationController.getGroup(groupId);
            for (User user : group.getAllUsers()) {
              if (!isUserPresent(users, user.getId())) {
                UserDTO u = populate(user.getId());
                users.add(u);
              }
            }
          }
        }
      }
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
      throw e;
    }

    users.sort(Comparator.comparing(UserDTO::getLastName, String.CASE_INSENSITIVE_ORDER));
    groups.sort(Comparator.comparing(GroupDTO::getName, String.CASE_INSENSITIVE_ORDER));
    usersAndGroups.addAll(groups);
    usersAndGroups.addAll(users);
    return usersAndGroups;
  }

  private boolean isGroupPresent(List<GroupDTO> groups, String id) {
    for (GroupDTO dto : groups) {
      if (dto.getId().equals(id)) {
        return true;
      }
    }
    return false;
  }

  private boolean isUserPresent(List<UserDTO> users, String id) {
    for (UserDTO dto : users) {
      if (dto.getId().equals(id)) {
        return true;
      }
    }
    return false;
  }

  private UserDTO populate(final String userId) {
    UserDetail userDetail = organizationController.getUserDetail(userId);
    return populate(userDetail);
  }

  private UserDTO populate(UserDetail userDetail) {
    return UserHelper.getInstance().populateUserDTO(userDetail);
  }

  private List<ProfileInst> getTopicProfiles(String topicId, String componentId)
      throws AdminException {
    List<ProfileInst> alShowProfile = new ArrayList<>();
    String[] asAvailProfileNames = Administration.get().getAllProfilesNames("kmelia");
    for (String asAvailProfileName : asAvailProfileNames) {
      ProfileInst profile = getTopicProfile(asAvailProfileName, topicId, componentId);
      profile.setLabel(Administration.get().getProfileLabelFromName("kmelia", asAvailProfileName,
          getUser().getUserPreferences().getLanguage()));
      alShowProfile.add(profile);
    }

    return alShowProfile;
  }

  private ProfileInst getTopicProfile(String role, String topicId, String componentId)
      throws AdminException {
    NodePK f = new NodePK(topicId, componentId);
    NodeDetail node = NodeService.get().getHeader(f, false);
    ProfiledObjectId profiledObjectId = ProfiledObjectId.fromNode(node.getRightsDependsOn());
    List<ProfileInst> profiles =
        Administration.get().getProfilesByObject(profiledObjectId, componentId);
    for (int p = 0; profiles != null && p < profiles.size(); p++) {
      ProfileInst profile = profiles.get(p);
      if (profile.getName().equals(role)) {
        return profile;
      }
    }
    ProfileInst profile = new ProfileInst();
    profile.setName(role);
    return profile;
  }

  private boolean isRightsOnTopicsEnabled(String instanceId) {
    String value =
        getMainSessionController().getComponentParameterValue(instanceId, "rightsOnTopics");
    return StringUtil.getBooleanValue(value);
  }

  @PUT
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  @Path("readed/{id}")
  public void markAsReaden(@PathParam("id") long id) {
    ArrayList<String> ids = new ArrayList<>();
    ids.add(String.valueOf(id));
    SILVERMAILPersistence.markMessagesAsRead(getUser().getId(), ids);
  }

  @PUT
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  @Path("readed/")
  public void markAsRead(List<NotificationBoxDTO> selection) {
    SILVERMAILPersistence.markMessagesAsRead(getUser().getId(), getSelectionIds(selection));
  }

  private List<String> getSelectionIds(List<NotificationBoxDTO> selection) {
    ArrayList<String> ids = new ArrayList<>();
    for (NotificationBoxDTO dto : selection) {
      ids.add(String.valueOf(dto.getIdNotif()));
    }
    return ids;
  }

  @DELETE
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  @Path("")
  public void delete(List<NotificationBoxDTO> selection) {
    if (selection.get(0).isSended()) {
      for (NotificationBoxDTO dto : selection) {
        try {
          SentNotificationInterface.get().deleteNotif(getUser().getId(), (int) dto.getIdNotif());
        } catch (NotificationException e) {
          SilverLogger.getLogger(this).error(e);
        }
      }
    } else {
      SILVERMAILPersistence.deleteMessages(getUser().getId(), getSelectionIds(selection));
    }
  }

  @PUT
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  @Path("send/")
  public void send(NotificationToSendDTO notificationToSendDTO) throws AdminException,
      NotificationException {
    NotificationSender notificationSender =
        new NotificationSender(notificationToSendDTO.getNotification().getInstanceId());
    NotificationMetaData metaData = new NotificationMetaData();

    for (ReceiverDTO receiver : notificationToSendDTO.getReceivers()) {
      if (receiver.getType().equals(ReceiverDTO.TYPE_USER)) {
        metaData.addUserRecipient(new UserRecipient(receiver.getId()));
      } else if (receiver.getType().equals(ReceiverDTO.TYPE_GROUP)) {
        metaData.addGroupRecipient(new GroupRecipient(receiver.getId()));
      }
    }

    metaData.setSendImmediately(true);
    UserDetail u = Administration.get().getUserDetail(getUser().getId());
    String silverpeasServerUrl = u.getDomain().getSilverpeasServerURL();
    if (!silverpeasServerUrl.contains("/silverpeas")) {
      silverpeasServerUrl = silverpeasServerUrl + "/silverpeas";
    }

    switch (notificationToSendDTO.getNotification().getContentType()) {
      case NotificationDTO.TYPE_PUBLICATION: {
        String url = silverpeasServerUrl + "/Publication/" +
            notificationToSendDTO.getNotification().getContentId();
        metaData.setLink(url);
        break;
      }
      case NotificationDTO.TYPE_DOCUMENT: {
        String url = silverpeasServerUrl + "/services/media/viewer/embed/pdf?documentId=" +
            notificationToSendDTO.getNotification().getContentId() + "&documentType=attachment" +
            "&embedPlayer=true";
        metaData.setLink(url);
        break;
      }
      case NotificationDTO.TYPE_PHOTO:
      case NotificationDTO.TYPE_SOUND:
      case NotificationDTO.TYPE_VIDEO:
      case NotificationDTO.TYPE_STREAMING: {
        String url = silverpeasServerUrl + "/autoRedirect.jsp?goto=%2FRgallery%2F" +
            notificationToSendDTO.getNotification().getInstanceId() + "%2FsearchResult%3FType%3D" +
            notificationToSendDTO.getNotification().getContentType() + "%26Id%3D" +
            notificationToSendDTO.getNotification().getContentId();
        metaData.setLink(url);
        break;
      }
      case NotificationDTO.TYPE_EVENT: {
        String url = silverpeasServerUrl + "/Contribution/" +
            notificationToSendDTO.getNotification().getContentId();
        metaData.setLink(url);
        break;
      }
      default:
        break;
    }
    metaData.setAnswerAllowed(false);
    metaData.setContent(notificationToSendDTO.getNotification().getMessage());
    metaData.setSender(getUser().getEmailAddress());

    metaData.setTitle(notificationToSendDTO.getSubject());
    notificationSender.notifyUser(metaData);
  }

  private KmeliaService getKmeliaService() {
    return KmeliaService.get();
  }

  private NodeService getNodeService() {
    return NodeService.get();
  }

  @Override
  protected String getResourceBasePath() {
    return PATH;
  }

  @Override
  public String getComponentId() {
    return null;
  }

  @Override
  public void validateUserAuthorization(final UserPrivilegeValidation validation) {
    // no authorization
  }
}