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.ExecuteWatchdog;
25  import org.apache.commons.exec.PumpStreamHandler;
26  import org.waarp.commandexec.utils.LocalExecResult;
27  import org.waarp.common.command.exception.CommandAbstractException;
28  import org.waarp.common.logging.WaarpLogger;
29  import org.waarp.common.logging.WaarpLoggerFactory;
30  import org.waarp.openr66.context.ErrorCode;
31  import org.waarp.openr66.context.R66Result;
32  import org.waarp.openr66.context.R66Session;
33  import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException;
34  import org.waarp.openr66.context.task.localexec.LocalExecClient;
35  import org.waarp.openr66.protocol.configuration.Configuration;
36  
37  import java.io.File;
38  import java.io.PipedInputStream;
39  import java.io.PipedOutputStream;
40  
41  /**
42   * Execute an external command and Use the output if an error occurs.<br>
43   * <p>
44   * The output is ignored if the command has a correct status.<br>
45   * In case of error, if the output finishes with <tt>NEWFINALNAME:xxx</tt> then
46   * this part is removed from the
47   * output and the xxx is used as the last valid name for the file (meaning the
48   * file was moved or renamed even
49   * in case of error)<br>
50   * <br>
51   * <p>
52   * waitForValidation (#NOWAIT#) must not be set since it will prevent to have
53   * the feedback in case of error.
54   * So it is ignored.
55   */
56  public class ExecOutputTask extends AbstractExecTask {
57    /**
58     * Internal Logger
59     */
60    private static final WaarpLogger logger =
61        WaarpLoggerFactory.getLogger(ExecOutputTask.class);
62    /**
63     * In the final line output, the filename must prefixed by the following
64     * field
65     */
66    public static final String DELIMITER = "NEWFINALNAME:";
67  
68    /**
69     * @param argRule
70     * @param delay
71     * @param argTransfer
72     * @param session
73     */
74    public ExecOutputTask(final String argRule, final int delay,
75                          final String argTransfer, final R66Session session) {
76      super(TaskType.EXECOUTPUT, delay, argRule, argTransfer, session);
77    }
78  
79    @Override
80    public void run() {
81      /*
82       * First apply all replacements and format to argRule from context and argTransfer. Will call exec (from first
83       * element of resulting string) with arguments as the following value from the replacements. Return 0 if OK,
84       * else 1 for a warning else as an error. In case of an error (> 0), all the line from output will be send
85       * back to the partner with the Error code. No change is made to the file.
86       */
87      logger.info("ExecOutput with {}:{} and {}", argRule, argTransfer, session);
88      final String finalname = applyTransferSubstitutions(argRule);
89      //
90      // Force the WaitForValidation
91      waitForValidation = true;
92      if (Configuration.configuration.isUseLocalExec() && useLocalExec) {
93        final LocalExecClient localExecClient = new LocalExecClient();
94        if (localExecClient.connect()) {
95          localExecClient.runOneCommand(finalname, delay, waitForValidation,
96                                        futureCompletion);
97          final LocalExecResult result = localExecClient.getLocalExecResult();
98          finalizeExec(result.getStatus(), result.getResult(), finalname);
99          localExecClient.disconnect();
100         return;
101       } // else continue
102     }
103 
104     final PrepareCommandExec prepareCommandExec =
105         new PrepareCommandExec(this, finalname, false,
106                                waitForValidation).invoke();
107     if (prepareCommandExec.isError()) {
108       return;
109     }
110     final CommandLine commandLine = prepareCommandExec.getCommandLine();
111     final DefaultExecutor defaultExecutor =
112         prepareCommandExec.getDefaultExecutor();
113     final PipedInputStream inputStream = prepareCommandExec.getInputStream();
114     final PipedOutputStream outputStream = prepareCommandExec.getOutputStream();
115     final PumpStreamHandler pumpStreamHandler =
116         prepareCommandExec.getPumpStreamHandler();
117     final ExecuteWatchdog watchdog = prepareCommandExec.getWatchdog();
118 
119     final AllLineReader allLineReader = new AllLineReader(inputStream);
120     allLineReader.setName(
121         "LastLineReader" + session.getRunner().getSpecialId());
122     allLineReader.setDaemon(true);
123     Configuration.configuration.getExecutorService().execute(allLineReader);
124     final ExecuteCommand executeCommand =
125         new ExecuteCommand(this, commandLine, defaultExecutor, inputStream,
126                            outputStream, pumpStreamHandler,
127                            allLineReader).invoke();
128     if (executeCommand.isError()) {
129       return;
130     }
131     int status = executeCommand.getStatus();
132     final String newname;
133     if (defaultExecutor.isFailure(status) && watchdog != null &&
134         watchdog.killedProcess()) {
135       // kill by the watchdoc (time out)
136       status = -1;
137       newname = "TimeOut";
138     } else {
139       newname = allLineReader.getLastLine().toString();
140     }
141     finalizeExec(status, newname, commandLine.toString());
142   }
143 
144   private void finalizeExec(final int status, final String newName,
145                             final String commandLine) {
146     String newname = newName;
147     if (status == 0) {
148       final R66Result result =
149           new R66Result(session, true, ErrorCode.CompleteOk,
150                         session.getRunner());
151       result.setOther(newName);
152       futureCompletion.setResult(result);
153       futureCompletion.setSuccess();
154       logger.info("Exec OK with {} returns {}", commandLine, newname);
155     } else if (status == 1) {
156       final R66Result result =
157           new R66Result(session, true, ErrorCode.Warning, session.getRunner());
158       result.setOther(newName);
159       futureCompletion.setResult(result);
160       logger.warn(
161           "Exec in warning with " + commandLine + " returns " + newname);
162       session.getRunner().setErrorExecutionStatus(ErrorCode.Warning);
163       futureCompletion.setSuccess();
164     } else {
165       final int pos = newname.lastIndexOf(DELIMITER);
166       if (pos >= 0) {
167         final String newfilename = newname.substring(pos + DELIMITER.length());
168         newname = newname.substring(0, pos);
169         if (newfilename.indexOf(' ') > 0) {
170           logger.warn(
171               "Exec returns a multiple string in final line: " + newfilename);
172           // XXX FIXME: should not split String[] args = newfilename.split(" ")
173           // newfilename = args[args.length - 1]
174         }
175         // now test if the previous file was deleted (should be)
176         final File file = new File(newfilename);
177         if (!file.exists()) {
178           logger.warn(
179               "New file does not exist at the end of the exec: " + newfilename);
180         }
181         // now replace the file with the new one
182         try {
183           session.getFile().replaceFilename(newfilename, true);
184         } catch (final CommandAbstractException e) {
185           logger.warn("Exec in warning with " + commandLine + " : {}",
186                       e.getMessage());
187         }
188         session.getRunner().setFileMoved(newfilename, true);
189       }
190       logger.error("Status: " + status + " Exec in error with " + commandLine +
191                    " returns " + newname);
192       final OpenR66RunnerErrorException exc = new OpenR66RunnerErrorException(
193           "<STATUS>" + status + "</STATUS><ERROR>" + newname + "</ERROR>");
194       futureCompletion.setFailure(exc);
195     }
196   }
197 
198   @Override
199   final void finalizeFromError(final Runnable threadReader, final int status,
200                                final CommandLine commandLine,
201                                final Exception e) {
202     final String result =
203         ((AllLineReader) threadReader).getLastLine().toString();
204     logger.error("Status: " + status + " Exec in error with " + commandLine +
205                  " returns " + result);
206     final OpenR66RunnerErrorException exc = new OpenR66RunnerErrorException(
207         "<STATUS>" + status + "</STATUS><ERROR>" + result + "</ERROR>");
208     futureCompletion.setFailure(exc);
209   }
210 }