View Javadoc

1   /*
2    * Copyright 2007-2008 the original author or authors.
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  
17  package org.gwe.drivers.netAccess;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.gwe.utils.IOUtils;
31  
32  /**
33   * @author Neil Jones
34   * @author Marco Ruiz
35   * @since Jul 11, 2007
36   */
37  public class ShellCommand {
38  
39  	private static Log log = LogFactory.getLog(ShellCommand.class);
40  
41  	public static String moveFile(String source, String destination) {
42          return "mv " + source + " " + destination;
43      }
44  
45  	public static String copyFile(String source, String destination) {
46          return "cp -r -p " + source + " " + destination;
47      }
48  
49  	public static String delete(String dir) {
50          return "rm -fdr " + dir;
51      }
52  
53  	public static String makeFilePathDir(String file) {
54          return "mkdir -p " + IOUtils.getFilePath(file);
55      }
56  
57  	public static final int NON_INACTIVITY_TIMEOUT = -1;
58  	
59  	private static ExecutionChannelInspector CHANNEL_OPENED_INSPECTOR = new ExecutionChannelInspector() {
60  		public boolean isExecutionChannelOpened() { return false; }
61  	};
62  	
63  	public static String runLocally(String cmd) throws ConnectorException {
64  		return new ShellCommand(cmd).runLocally();
65  	}
66  
67  	private String path;
68  	private String cmd;
69      private List<String> args = new ArrayList<String>();
70  	private Map<String, String> env;
71  	private int inactivityTimeout = NON_INACTIVITY_TIMEOUT;
72  	private String exitToken = null;
73  	private OutputStream outputStream = null;
74  	
75  	public ShellCommand(String cmd) {
76  		this(cmd, null, null);
77  	}
78  
79  	public ShellCommand(String cmd, String path, Map<String, String> env) {
80  		this.path = path;
81  		this.cmd = cmd;
82  		this.env = env;
83  	}
84  
85  	public String getCmd() {
86  		return cmd;
87  	}
88  
89  	public Map<String, String> getEnv() {
90  		return env;
91  	}
92  
93  	public String getPath() {
94  		return path;
95  	}
96  	
97  	public void addArgument(String arg) {
98  		cmd += " " + arg;
99  	}
100 
101     public String toString() {
102         return cmd;
103     }
104 
105     /**
106 	 * Default implementation creates a unix style command (pretty standard). 
107 	 * If needed other type of command extend this class and override this method.
108 	 */
109 	public String getUnixStyleCmd() {
110 		StringBuffer envString = new StringBuffer();
111 		if (env != null)
112 			for (String key : env.keySet()) 
113 				envString.append(" " + key + "=" + env.get(key));
114 		
115 		String realCmd = (path != null) ? "cd " + path + " && " : " ";
116         realCmd += envString.toString() + cmd;
117         for (String arg : args) realCmd += " \"" + arg + "\"";
118         
119         return realCmd;
120 	}
121 
122 	public int getInactivityTimeout() {
123 		return inactivityTimeout;
124 	}
125 
126 	public void setInactivityTimeout(int inactivityTimeout) {
127 		this.inactivityTimeout = inactivityTimeout;
128 	}
129 
130 	public String getExitToken() {
131 		return exitToken;
132 	}
133 
134 	public void setExitToken(String exitToken) {
135 		this.exitToken = exitToken;
136 	}
137 
138 	public OutputStream getOutputStream() {
139 		return outputStream;
140 	}
141 
142 	public void setOutputStream(OutputStream outputStream) {
143 		this.outputStream = outputStream;
144 	}
145 	
146 	public String runLocally() throws ConnectorException {
147 		long start = System.currentTimeMillis();
148         if (cmd == null) 
149         	throw new NullPointerException("Cannot execute 'null' shell command.");
150         
151         ProcessBuilder pb = new ProcessBuilder(cmd.split("\\s+"));
152         if (getEnv() != null) pb.environment().putAll(getEnv());
153         if (getPath() != null) pb.directory(new File(getPath()));
154 
155         try {
156 //        	Set<Thread> threads = (exitToken == null) ? null : Thread.getAllStackTraces().keySet();
157             Process processObj = pb.start();
158             log.debug("Command started '" + cmd + "'");
159             String results = IOUtils.readStream(processObj.getInputStream(), getOutputStream(), getExitToken()).toString();
160             log.debug("Command executed with results:\n" + results);
161             int exitCode = (exitToken == null) ? processObj.waitFor() : 0; //exitedWithToken(threads);
162         	return logResult(start, results, exitCode);
163         } catch (ConnectorException ex) {
164         	throw ex;
165         } catch (Exception ex) {
166             throw new ConnectorException("Command '" + cmd + "' execution failed", ex);
167         }
168     }
169 	
170 	// Hack to close those unnecessary process reapers that are locking daemon installations
171 	private int exitedWithToken(Set<Thread> threads) {
172 		Set<Thread> currentThreads = Thread.getAllStackTraces().keySet();
173 		for (Thread thread : currentThreads) {
174 	        String threadName = thread.getName();
175 			if (!threads.contains(thread) && threadName.contains("process reaper"))
176 				thread.stop();
177         }
178 	    return 0;
179     }
180 
181 	private String logResult(long start, String results, int exitCode) throws ConnectorException {
182 	    String logMsgPrefix = "Command [duration=" + (System.currentTimeMillis() - start) + "ms] '" + cmd + "'";
183 	    if (exitCode != 0) {
184 	    	String msg = logMsgPrefix + " failed with exit value: " + exitCode;
185 	    	log.info(msg);
186 	    	throw new ConnectorException(msg + "\nOutput:\n\n" + results);
187 	    } else {
188 	        log.debug(logMsgPrefix + "' successfully executed!");
189 	    }
190 	    return results;
191     }
192 	
193 	public String readExecutionOutput(InputStream in) throws IOException {
194 		return readExecutionOutput(in, null);
195 	}
196 	
197 	public String readExecutionOutput(InputStream in, ExecutionChannelInspector inspector) throws IOException {
198 		if (inspector == null) inspector = CHANNEL_OPENED_INSPECTOR;
199 		StringBuffer out = new StringBuffer();
200 		byte[] buff = new byte[1024];
201 		long startInactivity = System.currentTimeMillis();
202 		while (true) {
203 		    while (in.available() > 0) {
204 		    	// Read output
205 		        int count = in.read(buff);
206 		        log.trace("Read " + count + " bytes");
207 		        if (count < 0) break;
208 		        
209 		        // Save output
210 		        String readStr = new String(buff, 0, count);
211 				out.append(readStr);
212 		        if (outputStream != null) outputStream.write(readStr.getBytes());
213 		        
214 		        // Check if exit token reached
215 		        if (exitToken != null && out.lastIndexOf(exitToken) != -1) return out.toString();
216 		        
217 		        // Reset inactivity
218 			    startInactivity = System.currentTimeMillis();
219 		    }
220 		    if (reachedInactivityTimeout(startInactivity) || !inspector.isExecutionChannelOpened()) return out.toString();
221 		}
222 	}
223 
224 	private boolean reachedInactivityTimeout(long startInactivity) {
225 	    return inactivityTimeout != NON_INACTIVITY_TIMEOUT && System.currentTimeMillis() > startInactivity + inactivityTimeout;
226     }
227 }
228 
229