IngestRequest.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

package org.waarp.vitam.ingest;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import fr.gouv.vitam.common.GlobalDataRest;
import fr.gouv.vitam.common.StringUtils;
import fr.gouv.vitam.common.exception.InvalidParseOperationException;
import fr.gouv.vitam.common.json.JsonHandler;
import fr.gouv.vitam.common.model.LocalFile;
import fr.gouv.vitam.common.model.RequestResponseOK;
import org.waarp.common.exception.IllegalFiniteStateException;
import org.waarp.common.exception.InvalidArgumentException;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.state.MachineState;
import org.waarp.common.state.Transition;
import org.waarp.common.utility.ParametersChecker;
import org.waarp.vitam.common.AbstractVitamRequest;
import org.waarp.vitam.common.WaarpCommon.TaskOption;

import java.io.File;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;

/**
 * IngestRequest is the unitary entry for Ingest operations made by Waarp to
 * Vitam
 */
public class IngestRequest extends AbstractVitamRequest {
  /**
   * Only kind of action supported by Vitam and Waarp
   */
  static final String RESUME = "RESUME";
  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(IngestRequest.class);
  @JsonIgnore
  MachineState<IngestStep> step = IngestStep.newSessionMachineState();
  /*
   contextId – a type of ingest among "DEFAULT_WORKFLOW" (Sip ingest),
   "HOLDING_SCHEME" (tree) "FILING_SCHEME" (plan)
   */
  @JsonProperty("contextId")
  private String contextId;
  @JsonProperty("action")
  private String action = RESUME;
  @JsonProperty("checkAtr")
  private boolean checkAtr;

  public IngestRequest() {
    // Empty constructor for Json
  }

  /**
   * Standard constructor
   *
   * @param taskOption
   * @param contextId
   * @param action
   * @param checkAtr
   * @param factory
   *
   * @throws InvalidParseOperationException
   */
  public IngestRequest(final TaskOption taskOption, final String contextId,
                       final String action, final boolean checkAtr,
                       final IngestRequestFactory factory)
      throws InvalidParseOperationException {
    super(taskOption);
    try {
      ParametersChecker
          .checkParameterDefault(getCheckMessage(), contextId, action);
      ParametersChecker.checkSanityString(contextId, action);
    } catch (IllegalArgumentException | InvalidArgumentException e) {
      logger.error(e);
      throw new InvalidParseOperationException(e);
    }
    this.contextId = contextId;
    this.action = action;
    this.checkAtr = checkAtr;
    this.status = this.step.getCurrent().getStatusMonitor();
    try {
      factory.saveNewIngestRequest(this);
    } catch (InvalidParseOperationException e) {
      logger.error("Will not be able to save: {}", this, e);
      throw e;
    }
  }

  @Override
  public String toString() {
    return "Ingest = Step: " + (step != null? step.getCurrent() : "noStep") +
           " " + JsonHandler.unprettyPrint(this);
  }

  @JsonGetter("contextId")
  public String getContextId() {
    return contextId;
  }

  /**
   * @param contextId a type of ingest among "DEFAULT_WORKFLOW" (Sip ingest),
   *     "HOLDING_SCHEME" (tree) "FILING_SCHEME" (plan)
   *
   * @return this
   */
  @JsonSetter("contextId")
  public IngestRequest setContextId(final String contextId) {
    try {
      ParametersChecker.checkParameterDefault(getCheckMessage(), contextId);
      StringUtils.checkSanityString(contextId);
    } catch (InvalidParseOperationException | IllegalArgumentException e) {
      logger.error(e);
      throw new IllegalArgumentException(e.getMessage(), e);
    }
    this.contextId = contextId;
    return this;
  }

  @JsonGetter("action")
  public String getAction() {
    return action;
  }

  /**
   * @param action shall be "RESUME" only
   *
   * @return this
   */
  @JsonSetter("action")
  public IngestRequest setAction(final String action) {
    try {
      ParametersChecker.checkParameterDefault(getCheckMessage(), action);
      StringUtils.checkSanityString(action);
    } catch (InvalidParseOperationException | IllegalArgumentException e) {
      logger.error(e);
      throw new IllegalArgumentException(e.getMessage(), e);
    }
    this.action = action;
    return this;
  }

  /**
   * Use to set the step and status accordingly.
   *
   * @param step
   * @param status
   * @param factory
   *
   * @return this
   *
   * @throws InvalidParseOperationException
   */
  @JsonIgnore
  public IngestRequest setStep(final IngestStep step, final int status,
                               IngestRequestFactory factory)
      throws InvalidParseOperationException {
    if (this.step == null) {
      if (!step.equals(IngestStep.END)) {
        logger.debug("Step {} could not be set since IngestRequest done", step);
      }
      // Nothing to do since already done
      return this;
    }
    if (!step.equals(IngestStep.ERROR) && this.step.getCurrent().equals(step)) {
      // nothing to do
      return this;
    }
    try {
      this.step.setCurrent(step);
    } catch (IllegalFiniteStateException e) {
      logger.error(e);
      this.step.setDryCurrent(step);
    }
    setStatus(step != IngestStep.ERROR? step.getStatusMonitor() : status)
        .setLastTryTime(System.currentTimeMillis());
    return save(factory);
  }

  /**
   * Set the status AND the step according to the value of the status (if
   * less than 0, it is a step value, not a final status), but in dry mode
   * (no check, used by Json deserialization)
   *
   * @param status
   *
   * @return this
   */
  @JsonSetter("status")
  public IngestRequest setStatus(final int status) {
    this.status = status;
    if (step != null) {
      step.setDryCurrent(IngestStep.getFromInt(status));
    }
    return this;
  }

  /**
   * Save this IngestRequest
   *
   * @param factory
   *
   * @return this
   *
   * @throws InvalidParseOperationException
   */
  @JsonIgnore
  public IngestRequest save(IngestRequestFactory factory)
      throws InvalidParseOperationException {
    factory.saveIngestRequest(this);
    return this;
  }

  @JsonIgnore
  public IngestStep getStep() {
    if (step == null) {
      return null;
    }
    return step.getCurrent();
  }

  @JsonGetter("checkAtr")
  public boolean isCheckAtr() {
    return checkAtr;
  }

  @JsonSetter("checkAtr")
  public IngestRequest setCheckAtr(final boolean checkAtr) {
    this.checkAtr = checkAtr;
    return this;
  }

  /**
   * Set extra information from first response from operation submission
   *
   * @param requestResponse
   *
   * @return this
   */
  @JsonIgnore
  public IngestRequest setFromRequestResponse(
      RequestResponseOK requestResponse) {
    String requestIdNew =
        requestResponse.getHeaderString(GlobalDataRest.X_REQUEST_ID);
    String globalExecutionStateNew = requestResponse
        .getHeaderString(GlobalDataRest.X_GLOBAL_EXECUTION_STATE);
    String globalExecutionStatusNew = requestResponse
        .getHeaderString(GlobalDataRest.X_GLOBAL_EXECUTION_STATUS);
    try {
      ParametersChecker.checkParameterDefault(getCheckMessage(), requestIdNew,
                                              globalExecutionStateNew,
                                              globalExecutionStatusNew);
      ParametersChecker.checkSanityString(requestIdNew, globalExecutionStateNew,
                                          globalExecutionStatusNew);
    } catch (IllegalArgumentException | InvalidArgumentException e) {
      logger.error(e);
      throw new IllegalArgumentException(e.getMessage(), e);
    }
    setGlobalExecutionState(globalExecutionStateNew)
        .setGlobalExecutionStatus(globalExecutionStatusNew)
        .setRequestId(requestIdNew);
    return this;
  }

  /**
   * @return the LocalFile according to this
   */
  @JsonIgnore
  public LocalFile getLocalFile() {
    return new LocalFile(getPath());
  }

  /**
   * @return the ATR File pointer according to this
   */
  @JsonIgnore
  public File getAtrFile(IngestRequestFactory factory) {
    return factory.getXmlAtrFile(this);
  }

  /**
   * Context accepted by Vitam
   */
  enum CONTEXT {
    /**
     * Sip ingest
     */
    DEFAULT_WORKFLOW,
    /**
     * Tree
     */
    HOLDING_SCHEME,
    /**
     * Plan
     */
    FILING_SCHEME;

    public static boolean checkCorrectness(String arg) {
      try {
        CONTEXT.valueOf(arg);
        return true;
      } catch (IllegalArgumentException ignore) {
        return false;
      }
    }
  }

  /**
   * Different steps of Ingest from Waarp point of view
   */
  enum IngestStep {
    /**
     * IngestRequest not started yet
     */
    STARTUP(-1),
    /**
     * IngestRequest INGEST to retry
     */
    RETRY_INGEST(-2),
    /**
     * IngestRequest INGEST Id to retry
     */
    RETRY_INGEST_ID(-3),
    /**
     * IngestRequest ATR to get
     */
    RETRY_ATR(-4),
    /**
     * IngestRequest ATR to forward
     */
    RETRY_ATR_FORWARD(-5),
    /**
     * IngestRequest Error
     */
    ERROR(-7),
    /**
     * Final End step
     */
    END(-10);

    private static final ConcurrentHashMap<IngestStep, EnumSet<IngestStep>>
        stateMap = new ConcurrentHashMap<>();

    static {
      initR66FiniteStates();
    }

    private final int statusMonitor;

    IngestStep(int status) {
      this.statusMonitor = status;
    }

    /**
     * This method should be called once at startup to initialize the Finite
     * States association.
     */
    private static void initR66FiniteStates() {
      for (final IngestTransition trans : IngestTransition.values()) {
        stateMap.put(trans.elt.getState(), trans.elt.getSet());
      }
    }

    /**
     * @return a new Session MachineState for OpenR66
     */
    private static MachineState<IngestStep> newSessionMachineState() {
      return new MachineState<>(STARTUP, stateMap);
    }

    /**
     * @param machine the Session MachineState to release
     */
    static void endSessionMachineSate(MachineState<IngestStep> machine) {
      if (machine != null) {
        machine.release();
      }
    }

    static IngestStep getFromInt(int status) {
      switch (status) {
        case -1:
          return STARTUP;
        case -2:
          return RETRY_INGEST;
        case -3:
          return RETRY_INGEST_ID;
        case -4:
          return RETRY_ATR;
        case -5:
          return RETRY_ATR_FORWARD;
        case -10:
          return END;
        case -7:
        default:
          return ERROR;
      }
    }

    int getStatusMonitor() {
      return statusMonitor;
    }

    private enum IngestTransition {
      T_STARTUP(STARTUP, EnumSet.of(RETRY_INGEST, ERROR)),
      T_RETRY_INGEST(RETRY_INGEST, EnumSet.of(RETRY_INGEST_ID, ERROR)),
      T_RETRY_INGEST_ID(RETRY_INGEST_ID, EnumSet.of(RETRY_ATR, ERROR, END)),
      T_RETRY_ATR(RETRY_ATR, EnumSet.of(RETRY_ATR_FORWARD, ERROR)),
      T_RETRY_ATR_FORWARD(RETRY_ATR_FORWARD, EnumSet.of(ERROR, END)),
      T_ERROR(ERROR, EnumSet.of(ERROR, END)), T_END(END, EnumSet.of(END));

      private final Transition<IngestStep> elt;

      IngestTransition(IngestStep state, EnumSet<IngestStep> set) {
        elt = new Transition<>(state, set);
      }

    }
  }

}