View Javadoc
1   /*
2    * This file is part of dependency-check-utils.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2020 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.utils.processing;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.nio.charset.StandardCharsets;
23  import java.util.ArrayList;
24  import java.util.List;
25  import org.apache.commons.io.IOUtils;
26  
27  /**
28   * Utility to read the output from a `Process` and places the output into
29   * provide storage containers.
30   *
31   * @author Jeremy Long
32   */
33  public class ProcessReader implements AutoCloseable {
34  
35      /**
36       * A reference to the process that will be read.
37       */
38      private final Process process;
39      /**
40       * Reader for the error stream.
41       */
42      private Gobbler errorGobbler = null;
43      /**
44       * Reader for the input stream.
45       */
46      private Gobbler inputGobbler = null;
47      /**
48       * Processor for the input stream.
49       */
50      private Processor<InputStream> processor = null;
51      /**
52       * A list of threads that were started.
53       */
54      private final List<Thread> threads = new ArrayList<>();
55  
56      /**
57       * Creates a new reader for the given process. The output from the process
58       * is written to the provided stores.
59       *
60       * @param process the process to read from
61       */
62      public ProcessReader(Process process) {
63          this(process, null);
64      }
65  
66      /**
67       * Creates a new reader for the given process. The output from the process
68       * is written to the provided stores.
69       *
70       * @param process the process to read from
71       * @param processor used to process the input stream from the process
72       */
73      public ProcessReader(Process process, Processor<InputStream> processor) {
74          this.process = process;
75          this.processor = processor;
76      }
77  
78      /**
79       * Returns the error stream output from the process.
80       *
81       * @return the error stream output
82       */
83      public String getError() {
84          return errorGobbler.getText();
85      }
86  
87      /**
88       * Returns the output from standard out from the process.
89       *
90       * @return the output from standard out from the process
91       */
92      public String getOutput() {
93          return inputGobbler != null ? inputGobbler.getText() : null;
94      }
95  
96      /**
97       * Reads the standard output and standard error from the process and waits
98       * for the process to complete.
99       *
100      * @throws InterruptedException thrown if the processing threads are
101      * interrupted
102      * @throws IOException thrown if there is an error reading from the process
103      */
104     public void readAll() throws InterruptedException, IOException {
105         start();
106         close();
107     }
108 
109     /**
110      * Starts the processing of the `process`.
111      */
112     private void start() {
113         errorGobbler = new Gobbler(process.getErrorStream());
114         startProcessor(errorGobbler);
115         if (processor == null) {
116             inputGobbler = new Gobbler(process.getInputStream());
117             startProcessor(inputGobbler);
118         } else {
119             processor.setInput(process.getInputStream());
120             startProcessor(processor);
121         }
122     }
123 
124     /**
125      * Starts the process in its own thread and collects the threads so `join`
126      * can be called later to ensure the thread finishes.
127      *
128      * @param p a reference to the processor to start.
129      */
130     private void startProcessor(Processor<InputStream> p) {
131         if (p != null) {
132             final Thread t = new Thread(p);
133             threads.add(t);
134             t.start();
135         }
136     }
137 
138     /**
139      * Waits for the process and related threads to complete.
140      *
141      * @throws InterruptedException thrown if the processing threads are
142      * interrupted
143      * @throws IOException thrown if there was an error reading from the process
144      */
145     @Override
146     public void close() throws InterruptedException, IOException {
147         if (process.isAlive()) {
148             process.waitFor();
149         }
150         if (threads.size() > 0) {
151             for (Thread thread : threads) {
152                 thread.join();
153             }
154             threads.clear();
155         }
156         errorGobbler.close();
157         if (inputGobbler != null) {
158             inputGobbler.close();
159         }
160     }
161 
162     static class Gobbler extends Processor<InputStream> {
163 
164         /**
165          * A store for an exception - if one is thrown during processing.
166          */
167         private IOException exception;
168         /**
169          * A store for the text read from the input stream.
170          */
171         private String text;
172 
173         Gobbler(InputStream inputStream) {
174             super(inputStream);
175         }
176 
177         @Override
178         public void run() {
179             try {
180                 final InputStream inputStream = getInput();
181                 text = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
182 
183             } catch (IOException ex) {
184                 exception = ex;
185             }
186         }
187 
188         public String getText() {
189             return text;
190         }
191 
192         @Override
193         public void close() throws IOException {
194             if (exception != null) {
195                 throw exception;
196             }
197         }
198     }
199 }