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.openr66.context.task;
21  
22  import org.apache.commons.exec.CommandLine;
23  import org.apache.commons.exec.DefaultExecutor;
24  import org.apache.commons.exec.ExecuteException;
25  import org.apache.commons.exec.ExecuteWatchdog;
26  import org.apache.commons.exec.PumpStreamHandler;
27  import org.waarp.common.file.FileUtils;
28  import org.waarp.common.logging.SysErrLogger;
29  import org.waarp.common.logging.WaarpLogger;
30  import org.waarp.common.logging.WaarpLoggerFactory;
31  import org.waarp.openr66.context.ErrorCode;
32  import org.waarp.openr66.context.R66Result;
33  import org.waarp.openr66.context.R66Session;
34  import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException;
35  import org.waarp.openr66.protocol.configuration.Configuration;
36  
37  import java.io.File;
38  import java.io.IOException;
39  import java.io.PipedInputStream;
40  import java.io.PipedOutputStream;
41  import java.util.regex.Pattern;
42  
43  /**
44   * Execute an external command
45   * <p>
46   * It provides some common functionalities.
47   */
48  public abstract class AbstractExecTask extends AbstractTask {
49    /**
50     * Internal Logger
51     */
52    private static final WaarpLogger logger =
53        WaarpLoggerFactory.getLogger(AbstractExecTask.class);
54    private static final Pattern COMPILE_REPLACE_ALL =
55        Pattern.compile("#([A-Z]+)#");
56  
57    /**
58     * Constructor
59     *
60     * @param type
61     * @param delay
62     * @param argRule
63     * @param session
64     */
65    AbstractExecTask(final TaskType type, final int delay, final String argRule,
66                     final String argTransfer, final R66Session session) {
67      super(type, delay, argRule, argTransfer, session);
68    }
69  
70    /**
71     * Generates a Command line object from rule and transfer data
72     *
73     * @param line the command to process as a string
74     */
75    protected final CommandLine buildCommandLine(final String line) {
76      if (line.contains(NOWAIT)) {
77        waitForValidation = false;
78      }
79      if (line.contains(LOCALEXEC)) {
80        useLocalExec = true;
81      }
82  
83      final String replacedLine =
84          COMPILE_REPLACE_ALL.matcher(line).replaceAll("\\${$1}");
85  
86      final CommandLine commandLine =
87          CommandLine.parse(replacedLine, getSubstitutionMap());
88  
89      final File exec = new File(commandLine.getExecutable());
90      if (exec.isAbsolute() && !exec.canExecute()) {
91        logger.error("Exec command is not executable: " + line);
92        final R66Result result =
93            new R66Result(session, false, ErrorCode.CommandNotFound,
94                          session.getRunner());
95        futureCompletion.setResult(result);
96        futureCompletion.cancel();
97        return null;
98      }
99  
100     return commandLine;
101   }
102 
103   /**
104    * For External command execution
105    */
106   static class PrepareCommandExec {
107     private final AbstractExecTask abstractTask;
108     private final boolean noOutput;
109     private final boolean waitForValidation;
110     private final String finalname;
111     private boolean myResult;
112     private CommandLine commandLine;
113     private DefaultExecutor defaultExecutor;
114     private PipedInputStream inputStream;
115     private PipedOutputStream outputStream;
116     private PumpStreamHandler pumpStreamHandler;
117     private ExecuteWatchdog watchdog;
118 
119     PrepareCommandExec(final AbstractExecTask abstractTask,
120                        final String finalname, final boolean noOutput,
121                        final boolean waitForValidation) {
122       this.abstractTask = abstractTask;
123       this.finalname = finalname;
124       this.noOutput = noOutput;
125       this.waitForValidation = waitForValidation;
126     }
127 
128     final boolean isError() {
129       return myResult;
130     }
131 
132     public final CommandLine getCommandLine() {
133       return commandLine;
134     }
135 
136     public final DefaultExecutor getDefaultExecutor() {
137       return defaultExecutor;
138     }
139 
140     public final PipedInputStream getInputStream() {
141       return inputStream;
142     }
143 
144     public final PipedOutputStream getOutputStream() {
145       return outputStream;
146     }
147 
148     public final PumpStreamHandler getPumpStreamHandler() {
149       return pumpStreamHandler;
150     }
151 
152     public final ExecuteWatchdog getWatchdog() {
153       return watchdog;
154     }
155 
156     public final PrepareCommandExec invoke() {
157       commandLine = abstractTask.buildCommandLine(finalname);
158       if (commandLine == null) {
159         myResult = true;
160         return this;
161       }
162 
163       defaultExecutor = new DefaultExecutor();
164       if (noOutput) {
165         pumpStreamHandler = new PumpStreamHandler(null, null);
166       } else {
167         inputStream = new PipedInputStream();
168         outputStream = null;
169         try {
170           outputStream = new PipedOutputStream(inputStream);
171         } catch (final IOException e1) {
172           FileUtils.close(inputStream);
173           logger.error(
174               "Exception: " + e1.getMessage() + " Exec in error with " +
175               commandLine + ": {}", e1.getMessage());
176           abstractTask.futureCompletion.setFailure(e1);
177           myResult = true;
178           return this;
179         }
180         pumpStreamHandler = new PumpStreamHandler(outputStream, null);
181       }
182       defaultExecutor.setStreamHandler(pumpStreamHandler);
183       final int[] correctValues = { 0, 1 };
184       defaultExecutor.setExitValues(correctValues);
185       watchdog = null;
186       if (abstractTask.delay > 0 && waitForValidation) {
187         watchdog = new ExecuteWatchdog(abstractTask.delay);
188         defaultExecutor.setWatchdog(watchdog);
189       }
190       myResult = false;
191       return this;
192     }
193   }
194 
195   /**
196    * For External command execution
197    */
198   static class ExecuteCommand {
199     private final AbstractExecTask abstractExecTask;
200     private final CommandLine commandLine;
201     private final DefaultExecutor defaultExecutor;
202     private final PipedInputStream inputStream;
203     private final PipedOutputStream outputStream;
204     private final PumpStreamHandler pumpStreamHandler;
205     private final Thread thread;
206     private boolean myResult;
207     private int status;
208 
209     ExecuteCommand(final AbstractExecTask abstractExecTask,
210                    final CommandLine commandLine,
211                    final DefaultExecutor defaultExecutor,
212                    final PipedInputStream inputStream,
213                    final PipedOutputStream outputStream,
214                    final PumpStreamHandler pumpStreamHandler,
215                    final Thread thread) {
216       this.abstractExecTask = abstractExecTask;
217       this.commandLine = commandLine;
218       this.defaultExecutor = defaultExecutor;
219       this.inputStream = inputStream;
220       this.outputStream = outputStream;
221       this.pumpStreamHandler = pumpStreamHandler;
222       this.thread = thread;
223     }
224 
225     final boolean isError() {
226       return myResult;
227     }
228 
229     public final int getStatus() {
230       return status;
231     }
232 
233     public final ExecuteCommand invoke() {
234       status = -1;
235       try {
236         status = defaultExecutor.execute(commandLine);//NOSONAR
237       } catch (final ExecuteException e) {
238         if (e.getExitValue() == -559038737) {
239           // Cannot run immediately so retry once
240           try {
241             Thread.sleep(Configuration.RETRYINMS);
242           } catch (final InterruptedException e1) {//NOSONAR
243             SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
244           }
245           try {
246             status = defaultExecutor.execute(commandLine);//NOSONAR
247           } catch (final ExecuteException e1) {
248             closeAllForExecution(true);
249             abstractExecTask.finalizeFromError(thread, status, commandLine, e1);
250             myResult = true;
251             return this;
252           } catch (final IOException e1) {
253             closeAllForExecution(true);
254             logger.error(
255                 "IOException: " + e.getMessage() + " . Exec in error with " +
256                 commandLine);
257             abstractExecTask.futureCompletion.setFailure(e);
258             myResult = true;
259             return this;
260           }
261         } else {
262           closeAllForExecution(true);
263           abstractExecTask.finalizeFromError(thread, status, commandLine, e);
264           myResult = true;
265           return this;
266         }
267       } catch (final IOException e) {
268         closeAllForExecution(true);
269         logger.error(
270             "IOException: " + e.getMessage() + " . Exec in error with " +
271             commandLine);
272         abstractExecTask.futureCompletion.setFailure(e);
273         myResult = true;
274         return this;
275       }
276       closeAllForExecution(false);
277       if (thread != null) {
278         try {
279           if (abstractExecTask.delay > 0) {
280             thread.join(abstractExecTask.delay);
281           } else {
282             thread.join();
283           }
284         } catch (final InterruptedException e) {//NOSONAR
285           SysErrLogger.FAKE_LOGGER.ignoreLog(e);
286           FileUtils.close(inputStream);
287           Thread.currentThread().interrupt();
288         }
289       }
290       FileUtils.close(inputStream);
291       myResult = false;
292       return this;
293     }
294 
295     private void closeAllForExecution(final boolean interrupt) {
296       FileUtils.close(outputStream);
297       if (interrupt && thread != null) {
298         thread.interrupt();
299       }
300       FileUtils.close(inputStream);
301       try {
302         pumpStreamHandler.stop();
303       } catch (final IOException ignored) {
304         // nothing
305       }
306     }
307   }
308 
309   void finalizeFromError(final Runnable threadReader, final int status,
310                          final CommandLine commandLine, final Exception e) {
311     logger.error("Status: " + status + " Exec in error with " + commandLine +
312                  " returns " + e.getMessage());
313     final OpenR66RunnerErrorException exc = new OpenR66RunnerErrorException(
314         "<STATUS>" + status + "</STATUS><ERROR>" + e.getMessage() + "</ERROR>");
315     futureCompletion.setFailure(exc);
316   }
317 }