ListDelegatedNewsResource.java

/*
 * Copyright (C) 2000 - 2026 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.delegatednews.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

import org.silverpeas.components.delegatednews.DelegatedNewsRuntimeException;
import org.silverpeas.components.delegatednews.model.DelegatedNews;
import org.silverpeas.components.delegatednews.service.DelegatedNewsService;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.web.rs.RESTWebService;
import org.silverpeas.core.web.rs.annotation.Authorized;
import org.silverpeas.core.rs.doc.NotFound;

import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response.Status;

import java.util.ArrayList;
import java.util.List;

/**
 * A REST Web resource representing a given delegated news. It is a web service that provides access
 * to a delegated news referenced by its URL.
 */
@WebService
@Path(ListDelegatedNewsResource.PATH + "/{instanceId}")
@Authorized
public class ListDelegatedNewsResource extends RESTWebService {

  static final String PATH = "delegatednews";

  @Inject
  private DelegatedNewsService delegatednewsService;
  @PathParam("instanceId")
  private String instanceId;

  /**
   * Gets a business service on delegatedNews.
   *
   * @return a delegatedNews service instance.
   */
  protected DelegatedNewsService getDelegatedNewsService() {
    return this.delegatednewsService;
  }

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

  @Override
  public String getComponentId() {
    return this.instanceId;
  }

  /**
   * Updates order or delete the delegatedNews from the JSON representation. If the user isn't
   * authenticated, a 401 HTTP code is returned. If the user isn't authorized to save the delegated
   * news, a 403 is returned. If a problem occurs when processing the request, a 503 HTTP code is
   * returned.
   *
   * <p>
   * The delegated news are received as a list, not as a Java array: the JSON provider of the SCIM
   * library, registered for the whole REST API, fails with any Java array.
   * </p>
   *
   * @param newDelegatedNews the list of the delegated news to update order or to delete
   * @return the new list of delegated news after update or delete
   */
  @Operation(summary = "Reorders or deletes the delegated news.",
      description = "The whole list of the delegated news to keep is expected, in the order they " +
          "have to be displayed. The delegated news missing from that list are deleted.")
  @ApiResponse(responseCode = "200", description = "The delegated news once reordered or deleted.",
      content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelegatedNewsEntity.class))))
  @NotFound
  @PUT
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  public List<DelegatedNewsEntity> updateDelegatedNews(
      final List<DelegatedNewsEntity> newDelegatedNews) {
    checkIsManager();
    List<DelegatedNewsEntity> updatedEntities;
    List<DelegatedNews> initialListDelegatedNews = getDelegatedNewsService().getAllDelegatedNews();
    try {
      if (initialListDelegatedNews.size() == newDelegatedNews.size()) {// Update Order
        updatedEntities = updateOrder(newDelegatedNews);
      } else if (initialListDelegatedNews.size() > newDelegatedNews.size()) {// Delete
        deleteList(newDelegatedNews, initialListDelegatedNews);
        updatedEntities = newDelegatedNews;
      } else {
        updatedEntities = List.of();
      }
    } catch (DelegatedNewsRuntimeException ex) {
      throw new WebApplicationException(ex, Status.NOT_FOUND);
    } catch (Exception ex) {
      throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
    }
    return updatedEntities;
  }

  /**
   * Checks the user manages the delegated news, their modification and their deletion being
   * reserved to the managers of this application. The authorization performed by the REST
   * framework only validates the access to the component instance, whatever the role played in it,
   * so it isn't enough here.
   */
  void checkIsManager() {
    final SilverpeasRole role = getHighestUserRole();
    if (role == null || !role.isGreaterThanOrEquals(SilverpeasRole.ADMIN)) {
      throw new WebApplicationException(Status.FORBIDDEN);
    }
  }

  private void deleteList(final List<DelegatedNewsEntity> newDelegatedNews,
      List<DelegatedNews> initialListDelegatedNews) {
    boolean found;
    for (DelegatedNews delegatedNews : initialListDelegatedNews) {
      String pubId = delegatedNews.getPubId();
      found = false;
      for (DelegatedNewsEntity delegatedNewsEntity : newDelegatedNews) {
        // the new tab of DelegatedNewsEntity without the delegated news deleted
        if (pubId.equals(delegatedNewsEntity.getPubId())) {
          found = true;
          break;
        }
      }
      if (!found) {
        getDelegatedNewsService().deleteDelegatedNews(pubId); // delete the delegatedNews
      }
    }
  }

  private List<DelegatedNewsEntity> updateOrder(final List<DelegatedNewsEntity> newDelegatedNews) {
    List<DelegatedNewsEntity> results = new ArrayList<>(newDelegatedNews.size());
    int order = 0;
    for (DelegatedNewsEntity delegatedNewsEntity : newDelegatedNews) {
      // the tab of DelegatedNewsEntity is in the new order
      DelegatedNews delegatedNews = delegatedNewsEntity.toDelegatedNews();
      DelegatedNews delegatedNewsUpdated =
          getDelegatedNewsService().updateOrderDelegatedNews(delegatedNews.getPubId(), order);
      results.add(DelegatedNewsEntity.fromDelegatedNews(delegatedNewsUpdated));
      order++;
    }
    return results;
  }
}