KmeliaResource.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:
 * "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.web;

import org.silverpeas.components.kmelia.service.KmeliaService;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.contribution.publication.model.PublicationDetail;
import org.silverpeas.core.contribution.publication.model.PublicationRuntimeException;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.core.web.rs.RESTWebService;
import org.silverpeas.core.web.rs.annotation.Authorized;
import org.silverpeas.core.webapi.publication.PublicationEntity;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.net.URI;

/**
 * A REST Web resource allowing to update data related to a publication.
 */
@WebService
@Path(KmeliaResource.PATH + "/{componentId}")
@Authorized
public class KmeliaResource extends RESTWebService {

  static final String PATH = "publications";

  @PathParam("componentId")
  private String componentId;

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

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

  /**
   * Creates a publication corresponding to the given publication entity and whose parent node
   * matches the specified node ID.
   *
   * @param nodeId The ID of the publication's parent node.
   * @param publicationEntity The description of the publication to create.
   * @return
   */
  @Path("/{nodeId}")
  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public Response savePublication(@PathParam("nodeId") String nodeId,
      PublicationEntity publicationEntity) {
    try {
      checkIsWriter();
      publicationEntity.setCreator(UserDetail.from(getUser()));
      PublicationDetail publication = publicationEntity.toPublicationDetail();

      NodePK nodePK = getNodePK(nodeId);

      String pubId = getKmeliaService().createPublicationIntoTopic(publication, nodePK);
      publication.getPK().setId(pubId);

      URI publicationURI = getUri().getRequestUriBuilder().path(publication.getPK().getId())
          .build();
      return Response.created(publicationURI).
          entity(asWebEntity(publication, identifiedBy(publicationURI))).build();
    } catch (PublicationRuntimeException ex) {
      throw new WebApplicationException(ex, Status.CONFLICT);
    } catch (Exception ex) {
      throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
    }
  }

  /**
   * Updates the publication described by the given publication entity.
   *
   * @param publicationEntity The description of the publication to update.
   * @return a response containing the entity describing the updated publication.
   */
  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public Response updatePublication(final PublicationEntity publicationEntity) {
    try {
      PublicationDetail publication = publicationEntity.toPublicationDetail();
      checkIsUpdatable(publication);

      // Publication status is a mandatory data into the context of publication update.
      // As this data is not handled by this service, it is retrieved from the silverpeas data
      // before performing the update.
      publication.setStatus(getKmeliaService().getPublicationDetail(publication.getPK()).getStatus());

      // Now, the update can be performed
      getKmeliaService().updatePublication(publication);

      URI publicationURI = getUri().getRequestUriBuilder().path(publication.getPK().getId())
          .build();
      return Response.ok(publicationURI).
          entity(asWebEntity(publication, identifiedBy(publicationURI))).build();
    } catch (PublicationRuntimeException ex) {
      throw new WebApplicationException(ex, Status.CONFLICT);
    } catch (Exception ex) {
      throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
    }
  }

  /**
   * Checks the publication can be updated by the current user. The authorization performed by the
   * REST framework validates the access of the user to the component instance referred by the URL,
   * whereas the publication to update is entirely defined by the request body. Without the check
   * below, a user could hence update a publication of any other component instance by referring it
   * in the body. Accessing a component instance isn't either enough to write into it.
   * @param publication the publication the request asks to update.
   */
  void checkIsUpdatable(final PublicationDetail publication) {
    if (!getComponentId().equals(publication.getPK().getInstanceId())) {
      throw new WebApplicationException(Status.FORBIDDEN);
    }
    checkIsWriter();
  }

  /**
   * Checks the current user is entitled to write publications into the component instance referred
   * by the URL. The authorization performed by the REST framework only validates the access to
   * that instance, whatever the role played in it, so it isn't enough to write into it.
   */
  void checkIsWriter() {
    final SilverpeasRole role = getHighestUserRole();
    if (role == null || !role.isGreaterThanOrEquals(SilverpeasRole.WRITER)) {
      throw new WebApplicationException(Status.FORBIDDEN);
    }
  }

  private NodePK getNodePK(String nodeId) {
    return new NodePK(nodeId, getComponentId());
  }

  private URI identifiedBy(URI uri) {
    return uri;
  }

  private PublicationEntity asWebEntity(final PublicationDetail publication, URI publicationURI) {
    return PublicationEntity.fromPublicationDetail(publication, publicationURI);
  }

  private KmeliaService getKmeliaService() {
    try {
      return KmeliaService.get();
    } catch (Exception e) {
      throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
    }
  }

}