FolderResource.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.apache.commons.lang3.StringUtils;
import org.owasp.encoder.Encode;
import org.silverpeas.components.kmelia.service.KmeliaHelper;
import org.silverpeas.components.kmelia.service.KmeliaService;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.node.model.NodeDetail;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.core.node.service.NodeService;
import org.silverpeas.kernel.bundle.LocalizationBundle;
import org.silverpeas.kernel.bundle.ResourceLocator;
import org.silverpeas.core.web.rs.RESTWebService;
import org.silverpeas.core.web.rs.annotation.Authorized;
import org.silverpeas.core.webapi.node.NodeAttrEntity;
import org.silverpeas.core.webapi.node.NodeEntity;
import org.silverpeas.core.webapi.node.NodeType;
import org.silverpeas.kernel.logging.SilverLogger;

import javax.annotation.Nonnull;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

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

  static final String PATH = "folders";

  @PathParam("componentId")
  private String componentId;

  /**
   * Get the root of the application and its children.
   *
   * @return the application root and its children
   */
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public NodeEntity getRoot(@QueryParam("lang") String language) {
    NodeDetail root;
    try {
      root = getKmeliaService().getRoot(componentId, getUser().getId());
    } catch (Exception e) {
      throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
    }

    final URI uri;
    if (getUri().getRequestUri().toString().endsWith("/" + NodePK.ROOT_NODE_ID)) {
      uri = getUri().getRequestUri();
    } else {
      uri = getUri().getRequestUriBuilder().path(root.getNodePK().getId()).build();
    }

    return asNodeEntity(root, uri, language);
  }

  /**
   * Get any node of the application and its children.
   *
   * @return NodeEntity representing asking node
   */
  @GET
  @Path("{path: \\d+(/\\d+)*}")
  @Produces(MediaType.APPLICATION_JSON)
  public NodeEntity getNode(@PathParam("path") String path, @QueryParam("lang") String language) {
    String nodeId = getNodeIdFromURI(path);
    NodeDetail node = getKmeliaService().getFolder(getNodePK(nodeId), getUser().getId());
    URI uri = getUri().getRequestUri();
    return asNodeEntity(node, uri, language);
  }

  /**
   * Get all children of any node of the application.
   *
   * @return an array of NodeEntity representing children
   */
  @GET
  @Path("{path: \\d+(/\\d+)*/path}")
  @Produces(MediaType.APPLICATION_JSON)
  public NodeEntity[] getPath(@PathParam("path") String path, @QueryParam("lang") String language) {
    String[] nodeIds = path.split("/");
    String nodeId = nodeIds[nodeIds.length - 2];
    NodePK nodePK = new NodePK(nodeId, componentId);

    List<NodeDetail> nodes;
    try {
      nodes = new ArrayList<>(getNodeService().getPath(nodePK));
      Collections.reverse(nodes);

      return asNodeEntities(nodes, language, nodePK.isTrash());
    } catch (Exception e1) {
      throw new WebApplicationException(e1, Status.INTERNAL_SERVER_ERROR);
    }
  }

  @Nonnull
  private NodeEntity[] asNodeEntities(final Collection<NodeDetail> nodes, final String language,
      final boolean decorate) {
    String requestUri = getUri().getRequestUri().toString();
    String uri = requestUri.substring(0, requestUri.lastIndexOf('/'));

    List<NodeEntity> entities = new ArrayList<>();
    final SilverpeasRole highestUserRole = getHighestUserRoleIfAny();
    for (NodeDetail node : nodes) {
      entities.add(NodeEntity.fromNodeDetail(highestUserRole, node, uri, language));
    }

    return decorate ? decorateRootChildren(entities, language) : entities.toArray(new NodeEntity[0]);
  }

  /**
   * Get all children of any node of the application.
   *
   * @return an array of NodeEntity representing children
   */
  @GET
  @Path("{path: \\d+(/\\d+)*/children}")
  @Produces(MediaType.APPLICATION_JSON)
  public NodeEntity[] getChildren(@PathParam("path") String path,
      @QueryParam("lang") String language) {
    String[] nodeIds = path.split("/");
    String nodeId = nodeIds[nodeIds.length - 2];
    NodePK nodePK = new NodePK(nodeId, componentId);

    try {
      Collection<NodeDetail> children =
          getKmeliaService().getFolderChildren(nodePK, getUser().getId());
      return asNodeEntities(children, language, nodePK.isRoot());
    } catch (Exception e) {
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Creates a node corresponding to the given node entity and whose parent matches the specified
   * path.
   *
   * @param path The path of the parent node.
   * @param nodeEntity The description of the node to create.
   * @return a response containing the entity describing the newly created node.
   */
  @Path("{path: \\d+(/\\d+)*}")
  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public Response createNode(@PathParam("path") String path, final NodeEntity nodeEntity) {
    String parentNodeId = getNodeIdFromURI(path);
    NodePK nodePK = getNodePK(parentNodeId);

    String nodeName = nodeEntity.getText();
    if (StringUtils.isEmpty(nodeName)) {
      throw new WebApplicationException(Status.NO_CONTENT);
    }

    Collection<NodeDetail> children;
    try {
      children = getKmeliaService().getFolderChildren(nodePK, getUser().getId());
    } catch (Exception e) {
      throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
    }

    if (children != null) {
      for (NodeDetail node : children) {
        if (nodeName.equals(node.getName())) {
          throw new WebApplicationException(Status.NOT_ACCEPTABLE);
        }
      }
    }

    NodeAttrEntity nodeAttr = nodeEntity.getAttr();
    String userId = nodeAttr.getCreatorId();
    if (StringUtils.isEmpty(userId) && nodeAttr.getCreator() != null) {
      userId = nodeAttr.getCreator().getId();
    }
    String description = nodeAttr.getDescription();

    try {
      NodeDetail node = getKmeliaService().createTopic(
          componentId, parentNodeId, null, userId, nodeName, description);

      URI uri = getUri().getRequestUri();
      NodeEntity newNodeEntity = NodeEntity.fromNodeDetail(getHighestUserRoleIfAny(), node, uri);

      return Response.created(uri).entity(newNodeEntity).build();
    } catch (Exception ex) {
      throw new WebApplicationException(ex, Status.SERVICE_UNAVAILABLE);
    }
  }

  private void decorateRoot(NodeEntity root, String lang) {
    root.getState().setOpened(true);
    root.setType(NodeType.ROOT);
    decorateRootChildren(Arrays.asList(root.getChildren()), lang);
  }

  private NodeEntity[] decorateRootChildren(List<NodeEntity> children, String lang) {
    // case of special nodes (bin, to validate)
    LocalizationBundle messages =
        ResourceLocator.getLocalizationBundle("org.silverpeas.kmelia.multilang.kmeliaBundle", lang);
    for (NodeEntity child : children) {
      if (KmeliaHelper.isToValidateFolder(child.getAttr().getId())) {
        child.setType(NodeType.TO_VALIDATE);
        child.getState().opened(false).setSelected(false);
        child.setText(Encode.forHtml(messages.getString("ToValidateShort")));
        child.getAttr().setDescription(messages.getString("kmelia.tovalidate.desc"));
      } else if (child.getAttr().getId().equalsIgnoreCase(NodePK.BIN_NODE_ID)) {
        child.setType(NodeType.BIN);
        child.getState().opened(false).setSelected(false);
        child.setText(Encode.forHtml(messages.getString("kmelia.basket")));
        child.getAttr().setDescription(messages.getString("kmelia.basket.desc"));
      } else if (KmeliaHelper.isNonVisiblePubsFolder(child.getAttr().getId())) {
        child.setType(NodeType.NOT_VISIBLE_CONTRIBUTIONS);
        child.getState().opened(false).setSelected(false);
        child.setText(Encode.forHtml(messages.getString("kmelia.folder.nonvisiblepubs")));
        child.getAttr().setDescription(messages.getString("kmelia.folder.nonvisiblepubs.desc"));
      }
    }
    return children.toArray(new NodeEntity[0]);
  }

  /**
   * Get all children of any node of the application.
   *
   * @return an array of NodeEntity representing children
   */
  @GET
  @Path("{path: \\d+/treeview}")
  @Produces(MediaType.APPLICATION_JSON)
  public NodeEntity getTreeview(@PathParam("path") String path, @QueryParam("lang") String language) {
    String[] nodeIds = path.split("/");
    String nodeId = nodeIds[nodeIds.length - 2];

    try {
      List<NodeDetail> nodes = new ArrayList<>(getNodeService().getPath(new NodePK(nodeId,
          componentId)));
      Collections.reverse(nodes);
      NodeDetail root = getKmeliaService().getExpandedPathToNode(new NodePK(nodeId, componentId),
          getUser().getId());

      String requestUri = getUri().getRequestUri().toString();
      String uri = requestUri.substring(0, requestUri.lastIndexOf('/'));

      NodeEntity rootEntity = NodeEntity.fromNodeDetail(getHighestUserRoleIfAny(), root, uri, language);
      decorateRoot(rootEntity, language);

      setOpenState(rootEntity.getChildren(), nodes);

      return rootEntity;
    } catch (Exception e) {
      SilverLogger.getLogger(this).error("Error in getTreeview()",e);
      throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
    }
  }

  private void setOpenState(NodeEntity[] children, List<NodeDetail> path) {
    for (NodeEntity child : children) {
      if (isInPath(child, path)) {
        child.getState().setOpened(true);
        setOpenState(child.getChildren(), path);
      }
    }
  }

  private boolean isInPath(NodeEntity entity, List<NodeDetail> path) {
    for (NodeDetail node : path) {
      if (node.getNodePK().getId().equals(entity.getAttr().getId())) {
        return true;
      }
    }
    return false;
  }

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

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

  private String getNodeIdFromURI(String uri) {
    String[] nodeIds = uri.split("/");
    return nodeIds[nodeIds.length - 1];
  }

  private NodeEntity asNodeEntity(final NodeDetail node, final URI uri, final String language) {
    final NodeEntity entity = NodeEntity.fromNodeDetail(getHighestUserRoleIfAny(), node, uri, language);
    if (node.isRoot()) {
      decorateRoot(entity, language);
    }
    return entity;
  }

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

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

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

  private SilverpeasRole getHighestUserRoleIfAny() {
    return getUser() != null && getComponentId() != null ? getHighestUserRole() : null;
  }
}