ServiceWorkflow.java

/*
 * Copyright (C) 2000 - 2025 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 <http://www.gnu.org/licenses/>.
 */

package org.silverpeas.mobile.server.services;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import org.silverpeas.core.admin.service.Administration;
import org.silverpeas.core.admin.user.model.GroupDetail;
import org.silverpeas.core.admin.user.model.ProfileInst;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.contribution.attachment.AttachmentServiceProvider;
import org.silverpeas.core.contribution.attachment.model.SimpleDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleDocumentPK;
import org.silverpeas.core.contribution.content.form.*;
import org.silverpeas.core.contribution.content.form.field.MultipleUserField;
import org.silverpeas.core.util.DateUtil;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.silverpeas.core.web.rs.annotation.Authorized;
import org.silverpeas.core.workflow.api.Workflow;
import org.silverpeas.core.workflow.api.instance.ProcessInstance;
import org.silverpeas.core.workflow.api.model.Input;
import org.silverpeas.core.workflow.api.model.ProcessModel;
import org.silverpeas.core.workflow.api.model.Role;
import org.silverpeas.core.workflow.api.task.Task;
import org.silverpeas.core.workflow.api.user.User;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.mobile.server.services.helpers.UserHelper;
import org.silverpeas.mobile.shared.StreamingList;
import org.silverpeas.mobile.shared.dto.BaseDTO;
import org.silverpeas.mobile.shared.dto.workflow.*;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

/**
 * Service de gestion des workflows.
 *
 * @author svuillet
 */
@WebService
@Authorized
@Path(ServiceWorkflow.PATH + "/{appId}")
public class ServiceWorkflow extends AbstractRestWebService {

  static final String PATH = "mobile/workflow";

  @Context
  HttpServletRequest request;

  @PathParam("appId")
  private String componentId;

  private List<GroupDetail> getGroups(List<String> ids) throws Exception {
    ArrayList<GroupDetail> groups = new ArrayList<>();
    for (String id : ids) {
      groups.add(Administration.get().getGroup(id));
    }
    return groups;
  }

  private Map<String, String> getWorkflowUserRoles() throws Exception {
    String[] roles = getOrganisationController().getUserProfiles(getUser().getId(), getComponentId());
    TreeMap<String, String> userRoles = new TreeMap<>();
    Role[] allRoles = Workflow.getProcessModelManager().getProcessModel(getComponentId()).getRoles();

    for (String role : roles) {
      for (Role r : allRoles) {
        if (r.getName().equals(role)){
          userRoles.put(r.getName(), r.getLabel(r.getName(), getUser().getUserPreferences().getLanguage()));
          break;
        }
      }
    }

    return userRoles;
  }

  private Map<String, List<String>> getHeaderLabels(Map<String, String> roles) throws Exception {

    Map<String, List<String>> headerLabels = new TreeMap<>();
    for (Map.Entry<String, String> role : roles.entrySet()) {
      headerLabels.put(role.getKey(), getHeaderLabels(getComponentId(), role.getKey()));
    }
    return headerLabels;
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("userField/{instanceId}/{actionName}/{fieldName}/{role}")
  public List<BaseDTO> getUserField(@PathParam("instanceId") String instanceId, @PathParam("actionName") String actionName,
      @PathParam("fieldName") String fieldName, @PathParam("role") String role) throws Exception {
    HashSet<BaseDTO> result = new HashSet<>();
    try {
      boolean group = false;
      ProcessModel model = Workflow.getProcessModelManager().getProcessModel(instanceId);

      org.silverpeas.core.workflow.api.model.Form form;
      if (actionName.equals("create")) {
        form = model.getCreateAction(role).getForm();
      } else {
        form = model.getAction(actionName).getForm();
      }
      Map<String, String> parameters = null;
      for (Input input : form.getInputs()) {
        if (input.getItem().getName().equals(fieldName)) {
          parameters = input.getItem().getKeyValuePairs();
          group = input.getItem().getType().equalsIgnoreCase("group");
          break;
        }
      }
      ArrayList<String> users = new ArrayList<>();
      ArrayList<GroupDetail> groups = new ArrayList<>();
      String roles = parameters.get("roles");
      List<ProfileInst> profiles =
          Administration.get().getComponentInst(getComponentId()).getProfiles();

      if (group) {
        if (roles == null || roles.isEmpty()) {
          groups.addAll(Administration.get().getAllGroups());
        } else {
          StringTokenizer stk = new StringTokenizer(roles, ",");
          while (stk.hasMoreTokens()) {
            String r = stk.nextToken();
            for (ProfileInst profil : profiles) {
              if (profil.getName().equals(r)) {
                groups.addAll(getGroups(profil.getAllGroups()));
              }
            }
          }
        }

        //populate groups
        for (GroupDetail gp : groups) {
          result.add(UserHelper.getInstance().populateGroupDTO(gp));
        }

      } else {
        if (roles == null || roles.isEmpty()) {
          users.addAll(Arrays.asList(Administration.get().getAllUsersIds()));
        } else {
          StringTokenizer stk = new StringTokenizer(roles, ",");
          while (stk.hasMoreTokens()) {
            String r = stk.nextToken();
            for (ProfileInst profil : profiles) {
              if (profil.getName().equals(r)) {
                users.addAll(profil.getAllUsers());
              }
            }
          }
        }

        // populate users
        for (String id : users) {
          UserDetail u = Administration.get().getUserDetail(id);
          result.add(UserHelper.getInstance().populateUserDTO(u));
        }
      }
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
      throw e;
    }

    return new ArrayList<>(result);
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("instances/{role}/{callNumber}")
  public StreamingList<WorkflowInstanceDTO> getWorkflowInstances(@PathParam("role") String userRole, @PathParam("callNumber") int callNumber) throws Exception {
    int callSize = 25;

    User user = Workflow.getUserManager().getUser(getUser().getId());

    List<ProcessInstance> list = (List<ProcessInstance>) request.getSession().getAttribute("Cache_processInstances_"+userRole);
    if (list == null) {
      list =
          Workflow.getProcessInstanceManager().getProcessInstances(getComponentId(), user, userRole);
      request.getSession().setAttribute("Cache_processInstances_"+userRole, list);
    }

    Role[] roles = Workflow.getProcessModelManager().getProcessModel(getComponentId()).getRoles();
    if (userRole == null || userRole.isEmpty()) {
      userRole = roles[0].getName();
    }


    int calledSize = 0;
    boolean moreElements = true;
    if (callNumber > 0) calledSize = callSize * callNumber;

    if ((calledSize + callSize) >= list.size()) {
      moreElements = false;
      callSize = list.size() - calledSize;
    }

    ArrayList<WorkflowInstanceDTO> instances = new ArrayList<>();
    for (ProcessInstance processInstance : list.subList(calledSize, calledSize + callSize)) {
      instances.add(populate(processInstance, userRole));
    }

    StreamingList<WorkflowInstanceDTO> streamingList = new StreamingList<>(instances, moreElements);
    if (!streamingList.getMoreElement()) getHttpRequest().getSession().removeAttribute("Cache_processInstances_"+userRole);

    return streamingList;
  }


  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("datainstances/{role}")
  public WorkflowDataDTO getDataInstances(@PathParam("role") String userRole) throws Exception {
    WorkflowDataDTO data = new WorkflowDataDTO();
    try {
      var processModel = Workflow.getProcessModelManager().getProcessModel(getComponentId());
      data.setRoles(getWorkflowUserRoles());
      data.setRolesAllowedToCreate(new ArrayList<>(Arrays.asList(processModel.getCreationRoles())));
      data.setHeaderLabels(getHeaderLabels(data.getRoles()));
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
      throw e;
    }
    return data;
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("presentationForm/{instanceId}/{role}")
  public WorkflowInstancePresentationFormDTO getPresentationForm(@PathParam("instanceId") String instanceId, @PathParam("role") String role)
      throws Exception {
    WorkflowInstancePresentationFormDTO dto = new WorkflowInstancePresentationFormDTO();
    List<FieldPresentationDTO> fields = new ArrayList<>();
    Map<String, String> mapActions = new TreeMap<>();
    try {
      ProcessInstance instance =
          Workflow.getProcessInstanceManager().getProcessInstance(instanceId);
      Form form = instance.getProcessModel().getPresentationForm("presentationForm", role,
          getUser().getUserPreferences().getLanguage());
      dto.setId(instance.getInstanceId());
      DataRecord data = instance.getFormRecord("presentationForm", role,
          getUser().getUserPreferences().getLanguage());

      Task[] tasks = Workflow.getTaskManager()
          .getTasks(Workflow.getUserManager().getUser(getUser().getId()), role, instance);

      for (Task task : tasks) {
        for (String actionName : task.getActionNames()) {
          String label = instance.getProcessModel().getAction(actionName)
              .getLabel(role, getUser().getUserPreferences().getLanguage());
          mapActions.put(actionName, label);
          dto.setState(task.getState().getName());
        }
      }
      for (FieldTemplate ft : form.getFieldTemplates()) {
        String label = ft.getLabel(getUser().getUserPreferences().getLanguage());
        String value = data.getField(ft.getFieldName()).getStringValue();
        String id = "";
        String type = ft.getTypeName();
        String displayerName = ft.getDisplayerName();
        value = getDisplayValue(ft, value, instance);

        if (type.equalsIgnoreCase("user")) {
          if (value.isEmpty()) {
            value = null;
          } else {
            UserDetail u = Administration.get().getUserDetail(value);
            value = u.getDisplayedName();
          }
        } else if (type.equalsIgnoreCase("multipleUser")) {
          StringTokenizer stk = new StringTokenizer(value, ",");
          value = "";
          while (stk.hasMoreTokens()) {
            String idUser = stk.nextToken();
            UserDetail u = Administration.get().getUserDetail(idUser);
            value += u.getDisplayedName() + ", ";
          }
          if (!value.isEmpty()) {
            value = value.substring(0, value.length() - 2);
          }
        } else if (type.equalsIgnoreCase("group")) {
          GroupDetail g = Administration.get().getGroup(value);
          value = g.getName();
        } else if (type.equalsIgnoreCase("date")) {
          value =
              DateUtil.getInputDate(value, getUser().getUserPreferences().getLanguage());
        } else if (type.equalsIgnoreCase("file") && value != null) {
            SimpleDocument doc = AttachmentServiceProvider.getAttachmentService()
                .searchDocumentById(new SimpleDocumentPK(value),
                    getUser().getUserPreferences().getLanguage());
            value = doc.getTitle();
            id = doc.getId();
          }

        if (value != null) {
          fields.add(new FieldPresentationDTO(label, value, id, type, displayerName));
        }
      }
      dto.setTitle(form.getTitle());
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
      throw e;
    }
    dto.setInstanceId(getComponentId());
    dto.setActions(mapActions);
    dto.setFields(fields);

    return dto;
  }

  private String getDisplayValue(final FieldTemplate ft, String value, ProcessInstance instance) {
    Map<String, String> params =
        ft.getParameters(getUser().getUserPreferences().getLanguage());
    String keys = params.get("keys");
    String values = params.get("values");
    if (keys != null && values != null) {
      String[] k = keys.split("##");
      String[] v = values.split("##");
      for (int i = 0; i < k.length; i++) {
        if (k[i].equals(value)) {
          return v[i];
        }
      }
    }

    if (value != null && value.startsWith("xmlWysiwygField")) {
      String wysiwygFile = value.substring(value.indexOf('_') + 1);
      try {
        String path = FileRepositoryManager.getAbsolutePath(instance.getModelId()) + "xmlWysiwyg" +
            File.separator + wysiwygFile;
        value = new String(Files.readAllBytes(Paths.get(path)));
      } catch (Exception e) {
        SilverLogger.getLogger(this).error(e);
      }
    }

    return value;
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("actionForm/{instanceId}/{role}/{action}")
  public WorkflowFormActionDTO getActionForm(@PathParam("instanceId") String instanceId, @PathParam("role") String role, @PathParam("action") String action)
      throws Exception {
    WorkflowFormActionDTO dto = new WorkflowFormActionDTO();
    try {
      org.silverpeas.core.workflow.api.model.Form form;
      DataRecord data = null;
      if (action.equals("create")) {
        ProcessModel model = Workflow.getProcessModelManager().getProcessModel(getComponentId());
        form = model.getCreateAction(role).getForm();
        dto.setId(model.getModelId());
      } else {
        ProcessInstance instance =
            Workflow.getProcessInstanceManager().getProcessInstance(instanceId);
        form = instance.getProcessModel().getActionForm(action);
        data = instance.getFolder();
        dto.setId(instance.getInstanceId());
      }
      if (form != null) {
        for (Input input : form.getInputs()) {
          WorkflowFieldDTO fdto = new WorkflowFieldDTO();
          fdto.setId(getComponentId());
          fdto.setDisplayerName(input.getDisplayerName());
          fdto.setMandatory(input.isMandatory());
          fdto.setReadOnly(input.isReadonly());
          fdto.setName(input.getItem().getName());
          fdto.setActionName(action);
          fdto.setLabel(
                  input.getItem().getLabel(role, getUser().getUserPreferences().getLanguage()));
          fdto.setValue(input.getValue());
          if (!action.equals("create")) {
            Field f = data.getField(input.getItem().getName());
            if (f.getValue() != null && !f.getValue().isEmpty()) {
              if (input.getItem().getType().equalsIgnoreCase("date")) {
                fdto.setValue(f.getValue().replace("/", "-"));
                //TODO : user, multipleUser, group data
              } else if (input.getItem().getType().equalsIgnoreCase("user")) {
                UserDetail u = (UserDetail) f.getObjectValue();
                fdto.setValueId(u.getId());
                fdto.setValue(f.getValue());
              } else if (input.getItem().getType().equalsIgnoreCase("multipleUser")) {
                String[] usersId = ((MultipleUserField) f).getUserIds();
                if (usersId.length > 0) {
                  String ids = Arrays.toString(usersId);
                  ids = ids.substring(1, ids.length() - 1);
                  fdto.setValueId(ids);
                  String value = "";
                  for (String id : usersId) {
                    value += Administration.get().getUserDetail(id).getDisplayedName() + ",";
                  }
                  value = value.substring(0, value.length() - 1);
                  fdto.setValue(value);
                }
              } else if (input.getItem().getType().equalsIgnoreCase("group")) {
                GroupDetail g = (GroupDetail) f.getObjectValue();
                fdto.setValueId(g.getId());
                fdto.setValue(f.getValue());
              } else if (input.getItem().getType().equalsIgnoreCase("file")) {
                SimpleDocument doc = AttachmentServiceProvider.getAttachmentService()
                        .searchDocumentById(new SimpleDocumentPK(f.getValue()),
                                getUser().getUserPreferences().getLanguage());
                fdto.setValue(doc.getTitle());
                fdto.setValueId(doc.getId());
              } else {
                fdto.setValue(f.getValue());
              }
            }
          }
          fdto.setType(input.getItem().getType());
          fdto.setValues(input.getItem().getKeyValuePairs());
          dto.addField(fdto);
        }
        dto.setTitle(form.getTitle(role, getUser().getUserPreferences().getLanguage()));
      }
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
      throw e;
    }
    return dto;
  }

  private List<String> getHeaderLabels(String modelId, String role) throws Exception {
    ArrayList<String> labels = new ArrayList<>();
    RecordTemplate template = Workflow.getProcessModelManager().getProcessModel(modelId)
        .getRowTemplate(role, getUser().getUserPreferences().getLanguage());
    FieldTemplate[] headers = template.getFieldTemplates();
    for (FieldTemplate header : headers) {
      labels.add(header.getLabel());
    }
    return labels;
  }

  private WorkflowInstanceDTO populate(ProcessInstance processInstance, String role)
      throws Exception {
    WorkflowInstanceDTO dto = new WorkflowInstanceDTO();
    dto.setId(processInstance.getInstanceId());
    String title =
        processInstance.getTitle(role, getUser().getUserPreferences().getLanguage());
    dto.setTitle(title);
    dto.setState(Arrays.toString(processInstance.getActiveStates()));

    RecordTemplate template =
        Workflow.getProcessModelManager().getProcessModel(processInstance.getModelId())
            .getRowTemplate(role, getUser().getUserPreferences().getLanguage());
    FieldTemplate[] headers = template.getFieldTemplates();

    for (FieldTemplate header : headers) {
      Field fd = processInstance.getRowDataRecord(role,
          getUser().getUserPreferences().getLanguage()).getField(header.getFieldName());
      String value = getDisplayValue(header, fd.getStringValue(), processInstance);
      dto.addHeaderField(value);
    }

    return dto;
  }

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

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