View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.commandexec.client;
21  
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.SimpleChannelInboundHandler;
25  import org.waarp.commandexec.utils.LocalExecDefaultResult;
26  import org.waarp.commandexec.utils.LocalExecResult;
27  import org.waarp.common.crypto.ssl.WaarpSslUtility;
28  import org.waarp.common.future.WaarpFuture;
29  import org.waarp.common.logging.WaarpLogger;
30  import org.waarp.common.logging.WaarpLoggerFactory;
31  import org.waarp.common.utility.WaarpNettyUtil;
32  
33  /**
34   * Handles a client-side channel for LocalExec
35   */
36  public class LocalExecClientHandler
37      extends SimpleChannelInboundHandler<String> {
38  
39    /**
40     * Internal Logger
41     */
42    private static final WaarpLogger logger =
43        WaarpLoggerFactory.getLogger(LocalExecClientHandler.class);
44  
45    protected LocalExecResult result;
46    protected StringBuilder back;
47    protected boolean firstMessage = true;
48    protected WaarpFuture future;
49    protected final LocalExecClientInitializer factory;
50    protected long delay;
51    protected String command;
52    protected Channel channel;
53    protected final WaarpFuture ready = new WaarpFuture(true);
54  
55    /**
56     * Constructor
57     */
58    public LocalExecClientHandler(final LocalExecClientInitializer factory) {
59      this.factory = factory;
60    }
61  
62    /**
63     * Initialize the client status for a new execution
64     *
65     * @param delay
66     * @param command
67     */
68    public final void initExecClient(final long delay, final String command) {
69      result = new LocalExecResult(LocalExecDefaultResult.NoStatus);
70      back = new StringBuilder();
71      firstMessage = true;
72      future = new WaarpFuture(true);
73      this.delay = delay;
74      this.command = command;
75      // Sends the received line to the server.
76      if (!ready.awaitOrInterruptible() && channel == null) {
77        throw new RuntimeException("Cannot get client connected");
78      }
79      logger.debug("write command: {}", this.command);
80      if (this.delay != 0) {
81        WaarpNettyUtil.awaitOrInterrupted(
82            channel.writeAndFlush(this.delay + " " + this.command + '\n'));
83      } else {
84        WaarpNettyUtil.awaitOrInterrupted(
85            channel.writeAndFlush(this.command + '\n'));
86      }
87    }
88  
89    @Override
90    public void channelActive(final ChannelHandlerContext ctx) throws Exception {
91      channel = ctx.channel();
92      factory.addChannel(channel);
93      ready.setSuccess();
94      super.channelActive(ctx);
95    }
96  
97    /**
98     * When closed, <br>
99     * If no messaged were received => NoMessage error is set to future<br>
100    * Else if an error was detected => Set the future to error (with or without
101    * exception)<br>
102    * Else if no error occurs => Set success to the future<br>
103    */
104   @Override
105   public void channelInactive(final ChannelHandlerContext ctx)
106       throws Exception {
107     if (future == null || !future.isDone()) {
108       // Should not be
109       finalizeMessage();
110     }
111     super.channelInactive(ctx);
112   }
113 
114   /**
115    * Finalize a message
116    */
117   private void finalizeMessage() {
118     if (result == null) {
119       if (future != null) {
120         future.cancel();
121       }
122       return;
123     }
124     if (firstMessage) {
125       result.set(LocalExecDefaultResult.NoMessage);
126     } else {
127       result.setResult(back.toString());
128     }
129     if (result.getStatus() < 0) {
130       if (result.getException() != null) {
131         future.setFailure(result.getException());
132       } else {
133         future.cancel();
134       }
135     } else {
136       future.setSuccess();
137     }
138   }
139 
140   /**
141    * Waiting for the close of the exec
142    *
143    * @return The LocalExecResult
144    */
145   public final LocalExecResult waitFor(final long delay) {
146     if (delay <= 0) {
147       future.awaitOrInterruptible();
148     } else {
149       future.awaitOrInterruptible(delay);
150     }
151     result.setSuccess(future.isSuccess());
152     return result;
153   }
154 
155   /**
156    * Action to do before close
157    */
158   public final void actionBeforeClose(final Channel channel) {
159     // here nothing to do
160   }
161 
162   @Override
163   protected void channelRead0(final ChannelHandlerContext ctx, String mesg) {
164     // Add the line received from the server.
165     // If first message, then take the status and then the message
166     if (firstMessage) {
167       firstMessage = false;
168       final int pos = mesg.indexOf(' ');
169       try {
170         result.setStatus(Integer.parseInt(mesg.substring(0, pos)));
171       } catch (final NumberFormatException e1) {
172         // Error
173         logger.debug(
174             command + ':' + "Bad Transmission: " + mesg + "\n\t" + back);
175         result.set(LocalExecDefaultResult.BadTransmition);
176         back.append(mesg);
177         actionBeforeClose(ctx.channel());
178         WaarpSslUtility.closingSslChannel(ctx.channel());
179         return;
180       }
181       mesg = mesg.substring(pos + 1);
182       if (mesg.startsWith(LocalExecDefaultResult.ENDOFCOMMAND)) {
183         logger.debug("{}:Receive End of Command", command);
184         result.setResult(LocalExecDefaultResult.NoMessage.getResult());
185         back.append(result.getResult());
186         finalizeMessage();
187       } else {
188         result.setResult(mesg);
189         back.append(mesg);
190       }
191     } else if (mesg.startsWith(LocalExecDefaultResult.ENDOFCOMMAND)) {
192       logger.debug("{}:Receive End of Command", command);
193       finalizeMessage();
194     } else {
195       back.append('\n').append(mesg);
196     }
197   }
198 
199   @Override
200   public void exceptionCaught(final ChannelHandlerContext ctx,
201                               final Throwable cause) {
202     logger.warn(command + ':' +
203                 "Unexpected exception from Outband while get information: " +
204                 firstMessage, cause);
205     if (firstMessage) {
206       firstMessage = false;
207       result.set(LocalExecDefaultResult.BadTransmition);
208       result.setException((Exception) cause);
209       back = new StringBuilder("Error in LocalExec: ").append(
210           result.getException().getMessage()).append('\n');
211     } else {
212       back.append("\nERROR while receiving answer: ");
213       result.setException((Exception) cause);
214       back.append(result.getException().getMessage()).append('\n');
215     }
216     actionBeforeClose(ctx.channel());
217     WaarpSslUtility.closingSslChannel(ctx.channel());
218   }
219 }