R66PreparedTransferExecutor.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.gateway.ftp.exec;

import org.waarp.common.command.exception.CommandAbstractException;
import org.waarp.common.command.exception.Reply421Exception;
import org.waarp.common.database.data.AbstractDbData.UpdatedInfo;
import org.waarp.common.database.exception.WaarpDatabaseException;
import org.waarp.common.future.WaarpFuture;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.openr66.client.TransferArgs;
import org.waarp.openr66.database.data.DbRule;
import org.waarp.openr66.database.data.DbTaskRunner;
import org.waarp.openr66.protocol.configuration.PartnerConfiguration;
import org.waarp.openr66.protocol.localhandler.packet.RequestPacket;

import java.io.File;

import static org.waarp.common.database.DbConstant.*;
import static org.waarp.common.file.filesystembased.FilesystemBasedFileImpl.*;

/**
 * R66PreparedTransferExecutor class. If the command starts with "REFUSED", the
 * command will be refused for
 * execution. If "REFUSED" is set, the command "RETR" or "STOR" like operations
 * will be stopped at starting of
 * command.
 * <p>
 * <p>
 * <p>
 * Format is like r66send command in any order except "-info" which should be
 * the last item:<br>
 * "-to Host -file FILE -rule RULE [-md5] [-nolog] [-start yyyyMMddHHmmss or
 * -delay (delay or +delay)] [-info INFO]" <br>
 * <br>
 * INFO is the only one field that can contains blank character and MUST be
 * the last argument.<br>
 * <br>
 * The following replacement are done dynamically before the command is
 * executed:<br>
 * - #BASEPATH# is replaced by the full path for the root of FTP Directory<br>
 * - #FILE# is replaced by the current file path relative to FTP Directory (so
 * #BASEPATH##FILE# is the full
 * path of the file)<br>
 * - #USER# is replaced by the username<br>
 * - #ACCOUNT# is replaced by the account<br>
 * - #COMMAND# is replaced by the command issued for the file<br>
 * - #SPECIALID# is replaced by the FTP id of the transfer (whatever in or
 * out)<br>
 * - #UUID# is replaced by a special UUID globally unique for the transfer, in
 * general to be placed in -info
 * part (for instance ##UUID## giving #uuid#)<br>
 * <br>
 * So for instance "-to Host -file #BASEPATH##FILE# -rule RULE [-md5] [-nolog]
 * [-delay +delay] [-info ##UUID##
 * #USER# #ACCOUNT# #COMMAND# INFO]" <br>
 * will be a standard use of this function.
 */
public class R66PreparedTransferExecutor extends AbstractExecutor {
  private static final String CANNOT_GET_NEW_TASK = "Cannot get new task\n    ";

  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(R66PreparedTransferExecutor.class);

  protected final WaarpFuture future;
  protected final TransferArgs transferArgs;

  protected boolean nolog;

  /**
   * Transfer arguments:<br>
   * <br>
   * -to <arg>        Specify the requested Host<br>
   * (-id <arg>|      Specify the id of transfer<br>
   * (-file <arg>     Specify the file path to operate on<br>
   * -rule <arg>))    Specify the Rule<br>
   * [-block <arg>]   Specify the block size<br>
   * [-nofollow]      Specify the transfer should not integrate a FOLLOW id<br>
   * [-md5]           Specify the option to have a hash computed for the
   * transfer<br>
   * [-delay <arg>]   Specify the delay time as an epoch time or '+' a delay in ms<br>
   * [-start <arg>]   Specify the start time as yyyyMMddHHmmss<br>
   * [-info <arg>)    Specify the transfer information (generally in last position)<br>
   * [-nolog]         Specify to not log anything included database once the
   * transfer is done<br>
   * [-notlogWarn |   Specify to log final result as Info if OK<br>
   * -logWarn]        Specify to log final result as Warn if OK<br>
   *
   * @param command transfer arguments
   * @param delay delay
   * @param futureCompletion future for completion
   */
  public R66PreparedTransferExecutor(final String command, final long delay,
                                     final WaarpFuture futureCompletion) {
    final String[] args = BLANK.split(command);
    transferArgs = TransferArgs.getParamsInternal(0, args, false);
    if (transferArgs != null) {
      TransferArgs.getAllInfo(transferArgs, 0, args, null);
      nolog = transferArgs.isNolog();
    }
    future = futureCompletion;
  }

  @Override
  public final void run() throws CommandAbstractException {
    if (transferArgs == null) {
      logger.error(
          "Mandatory argument is missing: -to  -rule  -file or -to -id");
      throw new Reply421Exception(
          "Mandatory argument is missing: -to  -rule  -file or -to -id");
    }
    if (transferArgs.getRemoteHost() == null ||
        transferArgs.getRulename() == null ||
        transferArgs.getFilename() == null) {
      logger.error(
          "Mandatory argument is missing: -to " + transferArgs.getRemoteHost() +
          " -rule " + transferArgs.getRulename() + " -file " +
          transferArgs.getFilename());
      throw new Reply421Exception("Mandatory argument is missing");
    }
    final String message =
        "R66Prepared with -to " + transferArgs.getRemoteHost() + " -rule " +
        transferArgs.getRulename() + " -file " + transferArgs.getFilename() +
        " -nolog: " + nolog + " -isMD5: " + transferArgs.isMD5() + " -info " +
        transferArgs.getTransferInfo();
    logger.debug(message);
    final DbRule rule;
    try {
      rule = new DbRule(transferArgs.getRulename());
    } catch (final WaarpDatabaseException e) {
      logger.error("Cannot get Rule: {} since {}\n    {}",
                   transferArgs.getRulename(), e.getMessage(), message);
      throw new Reply421Exception(
          "Cannot get Rule: " + transferArgs.getRulename() + "\n    " +
          message);
    }
    int mode = rule.getMode();
    if (transferArgs.isMD5()) {
      mode = RequestPacket.getModeMD5(mode);
    }
    final String sep =
        PartnerConfiguration.getSeparator(transferArgs.getRemoteHost());
    long originalSize = -1;
    if (RequestPacket.isSendMode(mode) && !RequestPacket.isThroughMode(mode)) {
      final File file = new File(transferArgs.getFilename());
      if (canRead(file)) {
        originalSize = file.length();
      }
    }
    final RequestPacket request =
        new RequestPacket(transferArgs.getRulename(), mode,
                          transferArgs.getFilename(),
                          transferArgs.getBlockSize(), 0, ILLEGALVALUE,
                          transferArgs.getTransferInfo(), originalSize, sep);
    // Not isRecv since it is the requester, so send => isRetrieve is true
    final boolean isRetrieve = !RequestPacket.isRecvMode(request.getMode());
    logger.debug("Will prepare: {}", request);
    final DbTaskRunner taskRunner;
    try {
      taskRunner = new DbTaskRunner(rule, isRetrieve, request,
                                    transferArgs.getRemoteHost(),
                                    transferArgs.getStartTime());
    } catch (final WaarpDatabaseException e) {
      logger.error("Cannot get new task since {}\n    {}", e.getMessage(),
                   message);
      throw new Reply421Exception(CANNOT_GET_NEW_TASK + message);
    }
    taskRunner.changeUpdatedInfo(UpdatedInfo.TOSUBMIT);
    if (!taskRunner.forceSaveStatus()) {
      try {
        if (!taskRunner.specialSubmit()) {
          logger.error("Cannot prepare task: " + message);
          throw new Reply421Exception(CANNOT_GET_NEW_TASK + message);
        }
      } catch (final WaarpDatabaseException e) {
        logger.error("Cannot prepare task since {}\n    {}", e.getMessage(),
                     message);
        throw new Reply421Exception(CANNOT_GET_NEW_TASK + message);
      }
    }
    logger.debug("R66PreparedTransfer prepared: {}", request);
    future.setSuccess();
  }
}