# StatisticServiceImpl.java

    /*
     * Copyright (C) 2000 - 2024 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:
     * "http://www.silverpeas.org/docs/core/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 <https://www.gnu.org/licenses/>.
     */
    package org.silverpeas.components.kmelia.stats;

    import org.silverpeas.components.kmelia.model.StatisticActivityVO;
    import org.silverpeas.components.kmelia.model.StatsFilterVO;
    import org.silverpeas.core.ResourceReference;
    import org.silverpeas.core.admin.service.AdminException;
    import org.silverpeas.core.admin.service.Administration;
    import org.silverpeas.core.admin.user.model.Group;
    import org.silverpeas.core.annotation.Service;
    import org.silverpeas.core.contribution.publication.model.PublicationDetail;
    import org.silverpeas.core.contribution.publication.service.PublicationService;
    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.silverstatistics.access.service.StatisticService;
    import org.silverpeas.kernel.util.Pair;
    import org.silverpeas.kernel.logging.SilverLogger;

    import jakarta.inject.Inject;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Date;
    import java.util.List;
    import java.util.stream.Collectors;

    import static java.time.OffsetDateTime.now;
    import static org.silverpeas.core.contribution.publication.dao.PublicationCriteria.onComponentInstanceIds;
    import static org.silverpeas.core.contribution.publication.model.PublicationDetail.VALID_STATUS;

    @Service
    public class StatisticServiceImpl implements
        org.silverpeas.components.kmelia.stats.StatisticService {

      private static final String PUBLICATION_TYPE = "Publication";
      @Inject
      private PublicationService publicationService;
      @Inject
      private NodeService nodeService;
      @Inject
      private StatisticService statisticService;
      @Inject
      private Administration admin;

      @Override
      public Integer getNbConsultedPublication(StatsFilterVO statFilter) {
        if (statFilter != null) {
          return getNumberOfConsultedPublications(statFilter);
        }
        return -1;
      }

      @Override
      public Integer getNbStatisticActivityByPeriod(StatsFilterVO statFilter) {
        if (statFilter != null) {
          // Retrieve the list of topic application publications
          List<PublicationDetail> publis = getValidApplicationPublications(statFilter);
          if (!publis.isEmpty()) {
            return (int)countGlobalPublicationActivity(statFilter, publis);
          }
          return 0;
        }
        return -1;
      }

      private Integer getNumberOfConsultedPublications(StatsFilterVO statFilter) {
        List<PublicationDetail> publis = getValidApplicationPublications(statFilter);
        int nbPubli = 0;
        if (!publis.isEmpty()) {
          Integer groupId = statFilter.getGroupId();
          List<ResourceReference> publiPKs = getReferencesToPublications(publis);
          if (groupId != null) {
            // Retrieve the list of user identifiers
            List<String> userIds = getListUserIdsFromGroup(groupId);
            try {
              // Retrieve the number of publication
              nbPubli = getStatisticService().getCountByPeriodAndUser(publiPKs, PUBLICATION_TYPE,
                  statFilter.getStartDate(), statFilter.getEndDate(), userIds);

            } catch (Exception e) {
              SilverLogger.getLogger(this)
                  .error("Error when counting number of access (getCountByPeriodAndUser)", e);
            }

          } else {
            try {
              nbPubli = getStatisticService().getCountByPeriod(publiPKs, 1, PUBLICATION_TYPE,
                  statFilter.getStartDate(), statFilter.getEndDate());
            } catch (Exception e) {
              SilverLogger.getLogger(this)
                  .error("Error when counting number of access (getCountByPeriod)", e);
            }

          }
        }
        return nbPubli;
      }

      /**
       * @param publis the list of publication detail where we extract WAPrimaryKey
       * @return a list of references to the specified publications
       */
      private List<ResourceReference> getReferencesToPublications(List<PublicationDetail> publis) {
        List<ResourceReference> publiPKs = new ArrayList<>();
        // Check access for each publication
        for (PublicationDetail publi : publis) {
          publiPKs.add(new ResourceReference(publi.getPK()));
        }
        return publiPKs;
      }

      /**
       * @param groupId the group identifier
       * @return the list of user identifiers which are linked to a group given in parameter, or empty
       * list if an exception occurs
       */
      private List<String> getListUserIdsFromGroup(Integer groupId) {
        List<String> userIds = new ArrayList<>();
        try {
          Group selectedGroup = admin.getGroup(Integer.toString(groupId));
          String[] arrayUserIds = selectedGroup.getUserIds();
          Collections.addAll(userIds, arrayUserIds);
        } catch (AdminException e) {
          SilverLogger.getLogger(this).error("Error when loading the list of filtered users", e);
        }
        return userIds;
      }

      /**
       * @param statFilter the stats filter value object
       * @return the list of application publications which respects the stats filter constraint
       */
      private List<PublicationDetail> getValidApplicationPublications(StatsFilterVO statFilter) {
        final NodePK fatherPK = new NodePK(Integer.toString(statFilter.getTopicId()),
            statFilter.getInstanceId());
        return getPublicationService().getPublicationsByCriteria(
            onComponentInstanceIds(statFilter.getInstanceId()).onNodes(
                getNodeService().getSubTree(fatherPK)
                    .stream()
                    .map(NodeDetail::getId)
                    .collect(Collectors.toSet()))
                .ofStatus(VALID_STATUS)
                .visibleAt(now()));
      }

      /**
       * @param statFilter the statistic filter object which contains all the statistic constraints
       * @param publis the list of PublicationDetail
       * @param isCreate true if counting create publication activity
       * @param isUpdate true if counting update publication activity
       * @return the number of global (create/modify) activity which happens on the list of publications
       */
      private long countPublicationActivity(StatsFilterVO statFilter,
          Collection<PublicationDetail> publis, boolean isCreate, boolean isUpdate) {
        long nbPubli;
        Date startTime = statFilter.getStartDate();
        Date endTime = statFilter.getEndDate();
        Integer groupId = statFilter.getGroupId();

        if (groupId != null) {
          List<String> userIds = getListUserIdsFromGroup(groupId);
          if (!userIds.isEmpty()) {
            nbPubli = publis.stream()
                .filter(
                    p -> isPubliActivityInsideTimeInterval(startTime, endTime, p, isCreate, isUpdate))
                .flatMap(p -> userIds.stream().map(u -> Pair.of(p, u)))
                .filter(pu -> isUserRelatedWithPubli(pu.getFirst(), pu.getSecond()))
                .count();
          } else {
            nbPubli = 0;
          }
        } else {
          // Check activity for each publication
          nbPubli = publis.stream()
              .filter(p -> isPubliActivityInsideTimeInterval(startTime, endTime, p, isCreate, isUpdate))
              .count();
        }
        return nbPubli;
      }

      /**
       * @param publi the publication detail
       * @param userId the user identifier
       * @return true if user has created, modified or validate this publication
       */
      private boolean isUserRelatedWithPubli(PublicationDetail publi, String userId) {
        return (userId.equals(publi.getCreatorId()) || userId.equals(publi.getUpdaterId()) || userId
            .equals(publi.getValidatorId()));
      }

      /**
       * @param startTime the start time interval
       * @param endTime the end time interval
       * @param publi the publication detail
       * @param isCreate true if check Create activity
       * @param isUpdate true if check Update activity
       * @return true if publication creation date and isCreate or modification date and isUpdate is
       * between startTime and endTime
       */
      private boolean isPubliActivityInsideTimeInterval(Date startTime, Date endTime,
          PublicationDetail publi, boolean isCreate, boolean isUpdate) {
        Date createDate = publi.getCreationDate();
        Date updateDate = publi.getLastUpdateDate();
        return (isCreate && (createDate.after(startTime) || createDate.equals(startTime)) && createDate.
            before(endTime)) || (isUpdate && (updateDate.after(startTime) || updateDate.
            equals(startTime)) && updateDate
            .before(endTime));
      }

      private PublicationService getPublicationService() {
        return publicationService;
      }

      private NodeService getNodeService() {
        return nodeService;
      }

      private StatisticService getStatisticService() {
        return statisticService;
      }

      @Override
      public StatisticActivityVO getStatisticActivity(StatsFilterVO statFilter) {
        List<PublicationDetail> publis = getValidApplicationPublications(statFilter);
        long nbCreate = countCreatePublicationActivity(statFilter, publis);
        long nbUpdate = countUpdatePublicationActivity(statFilter, publis);
        return new StatisticActivityVO((int)nbCreate, (int)nbUpdate);
      }

      /**
       * @param statFilter the statistic filter object which contains all the statistic constraints
       * @param publis the list of PublicationDetail
       * @return the number of global (create/modify) activity which happens on the list of publications
       */
      private long countCreatePublicationActivity(StatsFilterVO statFilter,
          List<PublicationDetail> publis) {
        return countPublicationActivity(statFilter, publis, true, false);
      }

      /**
       * @param statFilter the statistic filter object which contains all the statistic constraints
       * @param publis the list of PublicationDetail
       * @return the number of global (create/modify) activity which happens on the list of publications
       */
      private long countUpdatePublicationActivity(StatsFilterVO statFilter,
          List<PublicationDetail> publis) {
        return countPublicationActivity(statFilter, publis, false, true);
      }

      /**
       * @param statFilter the statistic filter object which contains all the statistic constraints
       * @param publis the list of PublicationDetail
       * @return the number of global (create/modify) activity which happens on the list of publications
       */
      private long countGlobalPublicationActivity(StatsFilterVO statFilter,
          List<PublicationDetail> publis) {
        return countPublicationActivity(statFilter, publis, true, true);
      }

      @Override
      public Integer getNumberOfDifferentConsultedPublications(StatsFilterVO statFilter) {
        if (statFilter != null) {
          List<PublicationDetail> publis = getValidApplicationPublications(statFilter);
          if (!publis.isEmpty()) {
            return countDistinctConsultedPublications(statFilter, publis);
          }
        }
        return -1;
      }

      /**
       * @param statFilter the statistic filter which contains all the statistics filter parameters
       * @param publis the list of publications
       * @return the number of distinct consulted publications
       */
      private Integer countDistinctConsultedPublications(StatsFilterVO statFilter,
          List<PublicationDetail> publis) {
        int nbPubli = 0;
        List<ResourceReference> publiPKs = getReferencesToPublications(publis);
        if (statFilter.getGroupId() != null) {
          // Retrieve the list of user identifiers
          List<String> userIds = getListUserIdsFromGroup(statFilter.getGroupId());
          try {
            return getStatisticService().getDistinctCountByPeriodUser(publiPKs, 1, PUBLICATION_TYPE,
                statFilter.getStartDate(), statFilter.getEndDate(), userIds);
          } catch (Exception e) {
            SilverLogger.getLogger(this)
                .error("Error when computing distinct access to publication", e);
          }
        } else {
          try {
            return getStatisticService().getDistinctCountByPeriod(publiPKs, 1, PUBLICATION_TYPE,
                statFilter.getStartDate(), statFilter.getEndDate());
          } catch (Exception e) {
            SilverLogger.getLogger(this)
                .error("Error when computing distinct access to publication", e);
          }
        }
        return nbPubli;
      }
    }