# CmisKmeliaContributionsProvider.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:
     * "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 <https://www.gnu.org/licenses/>.
     */

    package org.silverpeas.components.kmelia.service;

    import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
    import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
    import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
    import org.apache.chemistry.opencmis.commons.exceptions.CmisServiceUnavailableException;
    import org.silverpeas.components.kmelia.KmeliaPublicationHelper;
    import org.silverpeas.components.kmelia.model.KmeliaPublication;
    import org.silverpeas.components.kmelia.model.KmeliaRuntimeException;
    import org.silverpeas.core.ResourceIdentifier;
    import org.silverpeas.core.admin.component.model.SilverpeasSharedComponentInstance;
    import org.silverpeas.core.admin.service.OrganizationController;
    import org.silverpeas.core.admin.user.model.User;
    import org.silverpeas.core.annotation.Service;
    import org.silverpeas.core.cmis.CmisContributionsProvider;
    import org.silverpeas.core.contribution.model.ContributionIdentifier;
    import org.silverpeas.core.contribution.model.CoreContributionType;
    import org.silverpeas.core.contribution.model.I18nContribution;
    import org.silverpeas.core.contribution.publication.model.PublicationDetail;
    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.security.authorization.AccessControlContext;
    import org.silverpeas.core.security.authorization.AccessControlOperation;
    import org.silverpeas.core.security.authorization.PublicationAccessControl;

    import jakarta.inject.Inject;
    import jakarta.inject.Named;
    import jakarta.transaction.Transactional;
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    import static java.util.function.Predicate.not;

    /**
     * Providers of the user contributions managed in a Kmelia instance and to expose in the CMIS
     * objects tree of Silverpeas.
     * @author mmoquillon
     */
    @Service
    @Named("kmelia" + CmisContributionsProvider.Constants.NAME_SUFFIX)
    public class CmisKmeliaContributionsProvider implements CmisContributionsProvider {

      @Inject
      private KmeliaService kmeliaService;

      @Inject
      private OrganizationController controller;

      @Inject
      private PublicationAccessControl accessControl;

      @Override
      public List<I18nContribution> getAllowedRootContributions(final ResourceIdentifier appId,
          final User user) {
        String kmeliaId = appId.asString();
        checkKmeliaAccessible(kmeliaId, user);
        boolean treeEnabled = KmeliaPublicationHelper.isTreeEnabled(kmeliaId);
        NodePK root = new NodePK(NodePK.ROOT_NODE_ID, kmeliaId);
        if (treeEnabled) {
          ContributionIdentifier rootId = ContributionIdentifier.from(root, NodeDetail.TYPE);
          return getAllowedContributionsInFolder(rootId, user);
        } else {
          String profile = kmeliaService.getUserTopicProfile(root, user.getId());
          return kmeliaService.getAuthorizedPublicationsOfFolder(root, profile, user.getId(), false)
              .stream()
              .map(KmeliaPublication::getDetail)
              .collect(Collectors.toList());
        }
      }

      @Override
      public List<I18nContribution> getAllowedContributionsInFolder(final ContributionIdentifier folder,
          final User user) {
        String kmeliaId = folder.getComponentInstanceId();
        checkKmeliaAccessible(kmeliaId, user);
        boolean treeEnabled = KmeliaPublicationHelper.isTreeEnabled(kmeliaId);
        NodePK folderPK = toNodePK(folder);
        try {
          String profile = kmeliaService.getUserTopicProfile(folderPK, user.getId());
          Stream<? extends I18nContribution> publications;
          if (!folderPK.isRoot() || KmeliaPublicationHelper.isPublicationsOnRootAllowed(kmeliaId)) {
            var pubsInFolder = kmeliaService.getAuthorizedPublicationsOfFolder(folderPK, profile, user.getId(), treeEnabled)
                .stream()
                .filter(not(KmeliaPublication::isAlias).and(KmeliaPublication::isVisible))
                .map(KmeliaPublication::getDetail)
                .collect(Collectors.toList());
            var drafts = accessControl.filterAuthorizedByUser(user.getId(),
                    pubsInFolder.stream()
                        .filter(this::isInDraft)
                        .collect(Collectors.toList()), AccessControlContext.init()
                        .onOperationsOf(AccessControlOperation.MODIFICATION))
                .collect(Collectors.toMap(PublicationDetail::getId, p -> {
                  PublicationPK pk = new PublicationPK(p.getCloneId(), p.getInstanceId());
                  return kmeliaService.getPublicationDetail(pk);
                }));
            publications = pubsInFolder.stream()
                .map(p -> drafts.getOrDefault(p.getId(), p));
          } else {
            publications = Stream.empty();
          }

          var subFolders =
              kmeliaService.getFolderChildren(folderPK, user.getId())
                  .stream()
                  .filter(n -> !KmeliaHelper.isToValidateFolder(n.getNodePK()
                      .getId()))
                  .filter(n -> !n.isUnclassified() && !n.isBin() && !n.getId()
                      .equals(KmeliaHelper.SPECIALFOLDER_NONVISIBLEPUBS));

          return Stream.concat(publications, subFolders)
              .collect(Collectors.toList());
        } catch (Exception e) {
          throw new CmisObjectNotFoundException(e.getMessage());
        }
      }

      @Override
      public I18nContribution getContribution(final ContributionIdentifier contributionId,
          final User user) {
        if (contributionId.getType().equals(NodeDetail.TYPE)) {
          return getNodeDetail(contributionId, user);
        } else if (contributionId.getType().equals(PublicationDetail.TYPE)) {
          return getPublicationDetail(contributionId, user);
        }
        throw new CmisNotSupportedException("Don't support such contribution types " +
            contributionId.getType());
      }

      @Override
      @Transactional
      public I18nContribution createContribution(final I18nContribution contribution,
          final ResourceIdentifier appId, final String language) {
        User user = User.getCurrentRequester();
        String kmeliaId = appId.asString();
        checkKmeliaAccessible(kmeliaId, user);

        ContributionIdentifier rootFolder =
            ContributionIdentifier.from(kmeliaId, NodePK.ROOT_NODE_ID, CoreContributionType.NODE);
        return createContributionInFolder(contribution, rootFolder, language);
      }

      @Override
      @Transactional
      public I18nContribution createContributionInFolder(final I18nContribution contribution,
          final ContributionIdentifier folder, final String language) {
        if (contribution.getIdentifier().getType().equals(PublicationDetail.TYPE)) {
          PublicationDetail publication = toPublicationDetail(contribution, language);
          String id = kmeliaService.createPublicationIntoTopic(publication, toNodePK(folder));
          PublicationDetail saved = publication.copy();
          saved.setPk(new PublicationPK(id, publication.getInstanceId()));
          return publication;
        }
        throw new CmisNotSupportedException(
            String.format("CMIS creation of %s isn't yet supported in Kmelia",
                contribution.getContributionType()));
      }

      private NodePK toNodePK(final ContributionIdentifier identifier) {
        return new NodePK(identifier.getLocalId(), identifier.getComponentInstanceId());
      }

      private PublicationPK toPublicationPK(final ContributionIdentifier identifier) {
        return new PublicationPK(identifier.getLocalId(), identifier.getComponentInstanceId());
      }

      private PublicationDetail toPublicationDetail(final I18nContribution contribution,
          final String language) {
        PublicationDetail publication;
        if (contribution instanceof PublicationDetail) {
          publication = (PublicationDetail) contribution;
        } else {
          PublicationPK pk = new PublicationPK(contribution.getIdentifier()
              .getLocalId(), contribution.getIdentifier()
              .getComponentInstanceId());
          publication = PublicationDetail.builder(language)
              .setPk(pk)
              .setNameAndDescription(contribution.getName(), contribution.getDescription())
              .setImportance(1)
              .created(contribution.getCreationDate(), contribution.getCreator()
                  .getId())
              .updated(contribution.getLastUpdateDate(), contribution.getLastUpdater()
                  .getId())
              .build();
        }
        return publication;
      }

      private void checkKmeliaAccessible(String kmeliaId, User user) {
        if (controller.getComponentInstance(kmeliaId)
            .filter(SilverpeasSharedComponentInstance.class::isInstance)
            .map(SilverpeasSharedComponentInstance.class::cast)
            .filter(i -> i.getName()
                .equals("kmelia") && i.canBeAccessedBy(user))
            .isEmpty()) {
          throw new CmisObjectNotFoundException(
              String.format("The application %s doesn't exist or is not accessible to user %s",
                  kmeliaId, user.getId()));
        }
      }

      private boolean isInDraft(final PublicationDetail publication) {
        return publication.isValid() && publication.haveGotClone() && !publication.isClone();
      }

      private NodeDetail getNodeDetail(final ContributionIdentifier id, final User user) {
        NodeDetail node;
        try {
          node = kmeliaService.getNodeHeader(id.getLocalId(), id.getComponentInstanceId());
          if (node == null) {
            throw new KmeliaRuntimeException("");
          }
        } catch (Exception e) {
          throw new CmisObjectNotFoundException(String.format("Folder %s not found!", id.asString()));
        }

        if (node.isBin() || node.isUnclassified() || KmeliaHelper.isToValidateFolder(node.getId()) ||
            node.getId().equals(KmeliaHelper.SPECIALFOLDER_NONVISIBLEPUBS)) {
          throw new CmisPermissionDeniedException("Forbidden access to special folders!");
        }

        if (!node.canBeAccessedBy(user)) {
          throw new CmisPermissionDeniedException("Forbidden access to folder " + id.asString());
        }

        return node;
      }

      private PublicationDetail getPublicationDetail(final ContributionIdentifier id, final User user) {
        try {
          PublicationDetail publication = kmeliaService.getPublicationDetail(toPublicationPK(id));
          if (publication == null) {
            throw new CmisObjectNotFoundException(String.format("Publication %s not found!",
                id.asString()));
          }
          if (!publication.canBeAccessedBy(user)) {
            throw new CmisPermissionDeniedException("Forbidden access to publication " + id.asString());
          }
          if (isInDraft(publication) &&
            accessControl.isUserAuthorized(user.getId(), publication,
                AccessControlContext.init().onOperationsOf(AccessControlOperation.MODIFICATION))) {
            PublicationPK pk = new PublicationPK(publication.getCloneId(), publication.getInstanceId());
            return kmeliaService.getPublicationDetail(pk);
          }
          return publication;
        } catch (Exception e) {
          throw new CmisServiceUnavailableException(e.getMessage());
        }
      }
    }