LocalExecClientHandler.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.commandexec.client;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.waarp.commandexec.utils.LocalExecDefaultResult;
import org.waarp.commandexec.utils.LocalExecResult;
import org.waarp.common.crypto.ssl.WaarpSslUtility;
import org.waarp.common.future.WaarpFuture;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.WaarpNettyUtil;

/**
 * Handles a client-side channel for LocalExec
 */
public class LocalExecClientHandler
    extends SimpleChannelInboundHandler<String> {

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

  protected LocalExecResult result;
  protected StringBuilder back;
  protected boolean firstMessage = true;
  protected WaarpFuture future;
  protected final LocalExecClientInitializer factory;
  protected long delay;
  protected String command;
  protected Channel channel;
  protected final WaarpFuture ready = new WaarpFuture(true);

  /**
   * Constructor
   */
  public LocalExecClientHandler(final LocalExecClientInitializer factory) {
    this.factory = factory;
  }

  /**
   * Initialize the client status for a new execution
   *
   * @param delay
   * @param command
   */
  public final void initExecClient(final long delay, final String command) {
    result = new LocalExecResult(LocalExecDefaultResult.NoStatus);
    back = new StringBuilder();
    firstMessage = true;
    future = new WaarpFuture(true);
    this.delay = delay;
    this.command = command;
    // Sends the received line to the server.
    if (!ready.awaitOrInterruptible() && channel == null) {
      throw new RuntimeException("Cannot get client connected");
    }
    logger.debug("write command: {}", this.command);
    if (this.delay != 0) {
      WaarpNettyUtil.awaitOrInterrupted(
          channel.writeAndFlush(this.delay + " " + this.command + '\n'));
    } else {
      WaarpNettyUtil.awaitOrInterrupted(
          channel.writeAndFlush(this.command + '\n'));
    }
  }

  @Override
  public void channelActive(final ChannelHandlerContext ctx) throws Exception {
    channel = ctx.channel();
    factory.addChannel(channel);
    ready.setSuccess();
    super.channelActive(ctx);
  }

  /**
   * When closed, <br>
   * If no messaged were received => NoMessage error is set to future<br>
   * Else if an error was detected => Set the future to error (with or without
   * exception)<br>
   * Else if no error occurs => Set success to the future<br>
   */
  @Override
  public void channelInactive(final ChannelHandlerContext ctx)
      throws Exception {
    if (future == null || !future.isDone()) {
      // Should not be
      finalizeMessage();
    }
    super.channelInactive(ctx);
  }

  /**
   * Finalize a message
   */
  private void finalizeMessage() {
    if (result == null) {
      if (future != null) {
        future.cancel();
      }
      return;
    }
    if (firstMessage) {
      result.set(LocalExecDefaultResult.NoMessage);
    } else {
      result.setResult(back.toString());
    }
    if (result.getStatus() < 0) {
      if (result.getException() != null) {
        future.setFailure(result.getException());
      } else {
        future.cancel();
      }
    } else {
      future.setSuccess();
    }
  }

  /**
   * Waiting for the close of the exec
   *
   * @return The LocalExecResult
   */
  public final LocalExecResult waitFor(final long delay) {
    if (delay <= 0) {
      future.awaitOrInterruptible();
    } else {
      future.awaitOrInterruptible(delay);
    }
    result.setSuccess(future.isSuccess());
    return result;
  }

  /**
   * Action to do before close
   */
  public final void actionBeforeClose(final Channel channel) {
    // here nothing to do
  }

  @Override
  protected void channelRead0(final ChannelHandlerContext ctx, String mesg) {
    // Add the line received from the server.
    // If first message, then take the status and then the message
    if (firstMessage) {
      firstMessage = false;
      final int pos = mesg.indexOf(' ');
      try {
        result.setStatus(Integer.parseInt(mesg.substring(0, pos)));
      } catch (final NumberFormatException e1) {
        // Error
        logger.debug(
            command + ':' + "Bad Transmission: " + mesg + "\n\t" + back);
        result.set(LocalExecDefaultResult.BadTransmition);
        back.append(mesg);
        actionBeforeClose(ctx.channel());
        WaarpSslUtility.closingSslChannel(ctx.channel());
        return;
      }
      mesg = mesg.substring(pos + 1);
      if (mesg.startsWith(LocalExecDefaultResult.ENDOFCOMMAND)) {
        logger.debug("{}:Receive End of Command", command);
        result.setResult(LocalExecDefaultResult.NoMessage.getResult());
        back.append(result.getResult());
        finalizeMessage();
      } else {
        result.setResult(mesg);
        back.append(mesg);
      }
    } else if (mesg.startsWith(LocalExecDefaultResult.ENDOFCOMMAND)) {
      logger.debug("{}:Receive End of Command", command);
      finalizeMessage();
    } else {
      back.append('\n').append(mesg);
    }
  }

  @Override
  public void exceptionCaught(final ChannelHandlerContext ctx,
                              final Throwable cause) {
    logger.warn(command + ':' +
                "Unexpected exception from Outband while get information: " +
                firstMessage, cause);
    if (firstMessage) {
      firstMessage = false;
      result.set(LocalExecDefaultResult.BadTransmition);
      result.setException((Exception) cause);
      back = new StringBuilder("Error in LocalExec: ").append(
          result.getException().getMessage()).append('\n');
    } else {
      back.append("\nERROR while receiving answer: ");
      result.setException((Exception) cause);
      back.append(result.getException().getMessage()).append('\n');
    }
    actionBeforeClose(ctx.channel());
    WaarpSslUtility.closingSslChannel(ctx.channel());
  }
}