001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/
005: * http://izpack.codehaus.org/
006: *
007: * Copyright 2002 Olexij Tkatchenko
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: */
021:
022: package com.izforge.izpack.util;
023:
024: import java.io.File;
025: import java.io.FilenameFilter;
026: import java.io.IOException;
027: import java.io.InputStreamReader;
028: import java.io.StringWriter;
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.Iterator;
032: import java.util.List;
033:
034: import com.izforge.izpack.ExecutableFile;
035:
036: /**
037: * Executes a bunch of files. This class is intended to do a system dependent installation
038: * postprocessing. Executable file can be any file installed with current package. After execution
039: * the file can be optionally removed. Before execution on Unix systems execution flag will be set
040: * on processed file.
041: *
042: * @author Olexij Tkatchenko <ot@parcs.de>
043: */
044: public class FileExecutor {
045: private static final String JAR_FILE_SUFFIX = ".jar";
046:
047: private boolean stopThread(Thread t, MonitorInputStream m) {
048: m.doStop();
049: long softTimeout = 1000;
050: try {
051: t.join(softTimeout);
052: } catch (InterruptedException e) {
053: // ignore
054: }
055:
056: if (!t.isAlive())
057: return true;
058:
059: t.interrupt();
060: long hardTimeout = 1000;
061: try {
062: t.join(hardTimeout);
063: } catch (InterruptedException e) {
064: // ignore
065: }
066: return !t.isAlive();
067: }
068:
069: /**
070: * Constructs a new executor. The executable files specified must have pretranslated paths
071: * (variables expanded and file separator characters converted if necessary).
072: *
073: * @param files the executable files to process
074: */
075: public FileExecutor(Collection<ExecutableFile> files) {
076: this .files = files;
077: }
078:
079: /**
080: * Constructs a new executor.
081: */
082: public FileExecutor() {
083: this .files = null;
084: }
085:
086: /**
087: * Gets the output of the given (console based) commandline
088: *
089: * @param aCommandLine to execute
090: * @return the result of the command
091: */
092: public static String getExecOutput(String[] aCommandLine) {
093: return getExecOutput(aCommandLine, false);
094:
095: }
096:
097: /**
098: * Executes the given Command and gets the result of StdOut, or if exec returns !=0: StdErr.
099: *
100: * @param aCommandLine aCommandLine to execute
101: * @param forceToGetStdOut if true returns stdout
102: * @return the result of the command stdout or stderr if exec returns !=0
103: */
104: public static String getExecOutput(String[] aCommandLine,
105: boolean forceToGetStdOut) {
106: FileExecutor fe = new FileExecutor();
107:
108: String[] execOut = new String[2];
109:
110: int execResult = fe.executeCommand(aCommandLine, execOut);
111:
112: if (execResult == 0)
113:
114: return execOut[0];
115:
116: else if (forceToGetStdOut)
117: return execOut[0];
118: else
119: return execOut[1];
120: }
121:
122: /**
123: * Executed a system command and waits for completion.
124: *
125: * @param params system command as string array
126: * @param output contains output of the command index 0 = standard output index 1 = standard
127: * error
128: * @return exit status of process
129: */
130: public int executeCommand(String[] params, String[] output) {
131: StringBuffer retval = new StringBuffer();
132: retval.append("executeCommand\n");
133: if (params != null) {
134: for (String param : params) {
135: retval.append("\tparams: ").append(param);
136: retval.append("\n");
137: }
138: }
139: Process process = null;
140: MonitorInputStream outMonitor = null;
141: MonitorInputStream errMonitor = null;
142: Thread t1 = null;
143: Thread t2 = null;
144: int exitStatus = -1;
145:
146: Debug.trace(retval);
147:
148: try {
149: // execute command
150: process = Runtime.getRuntime().exec(params);
151:
152: boolean console = false;// TODO: impl from xml <execute
153: // in_console=true ...>, but works already
154: // if this flag is true
155: if (console) {
156: Console c = new Console(process);
157: // save command output
158: output[0] = c.getOutputData();
159: output[1] = c.getErrorData();
160: exitStatus = process.exitValue();
161: } else {
162: StringWriter outWriter = new StringWriter();
163: StringWriter errWriter = new StringWriter();
164:
165: InputStreamReader or = new InputStreamReader(process
166: .getInputStream());
167: InputStreamReader er = new InputStreamReader(process
168: .getErrorStream());
169: outMonitor = new MonitorInputStream(or, outWriter);
170: errMonitor = new MonitorInputStream(er, errWriter);
171: t1 = new Thread(outMonitor);
172: t2 = new Thread(errMonitor);
173: t1.setDaemon(true);
174: t2.setDaemon(true);
175: t1.start();
176: t2.start();
177:
178: // wait for command to complete
179: exitStatus = process.waitFor();
180: t1.join();
181: t2.join();
182:
183: // save command output
184: output[0] = outWriter.toString();
185: Debug.trace("stdout:");
186: Debug.trace(output[0]);
187: output[1] = errWriter.toString();
188: Debug.trace("stderr:");
189: Debug.trace(output[1]);
190: }
191: Debug.trace("exit status: " + Integer.toString(exitStatus));
192: } catch (InterruptedException e) {
193: if (Debug.tracing())
194: e.printStackTrace(System.err);
195: stopThread(t1, outMonitor);
196: stopThread(t2, errMonitor);
197: output[0] = "";
198: output[1] = e.getMessage() + "\n";
199: } catch (IOException e) {
200: if (Debug.tracing())
201: e.printStackTrace(System.err);
202: output[0] = "";
203: output[1] = e.getMessage() + "\n";
204: } finally {
205: // cleans up always resources like file handles etc.
206: // else many calls (like chmods for every file) can produce
207: // too much open handles.
208: if (process != null)
209: process.destroy();
210: }
211: return exitStatus;
212: }
213:
214: /**
215: * Executes files specified at construction time.
216: *
217: * @param currentStage the stage of the installation
218: * @param handler The AbstractUIHandler to notify on errors.
219: *
220: * @return 0 on success, else the exit status of the last failed command
221: */
222: public int executeFiles(int currentStage, AbstractUIHandler handler) {
223: int exitStatus = 0;
224: String[] output = new String[2];
225: // String permissions = (System.getProperty("user.name").equals("root"))
226: // ? "a+x" : "u+x";
227: String permissions = "a+x";
228:
229: // loop through all executables
230: Iterator<ExecutableFile> efileIterator = this .files.iterator();
231: while (exitStatus == 0 && efileIterator.hasNext()) {
232: ExecutableFile efile = efileIterator.next();
233: boolean deleteAfterwards = !efile.keepFile;
234: File file = new File(efile.path);
235: Debug.trace("handling executable file " + efile);
236:
237: // skip file if not for current OS (it might not have been installed
238: // at all)
239: if (!OsConstraint.oneMatchesCurrentSystem(efile.osList))
240: continue;
241:
242: if (ExecutableFile.BIN == efile.type
243: && currentStage != ExecutableFile.UNINSTALL
244: && OsVersion.IS_UNIX) {
245: // fix executable permission for unix systems
246: Debug
247: .trace("making file executable (setting executable flag)");
248: String[] params = { "/bin/chmod", permissions,
249: file.toString() };
250: exitStatus = executeCommand(params, output);
251: if (exitStatus != 0) {
252: handler.emitError("file execution error",
253: "Error executing \n" + params[0] + " "
254: + params[1] + " " + params[2]);
255: continue;
256: }
257: }
258:
259: // execute command in POSTINSTALL stage
260: if ((exitStatus == 0)
261: && ((currentStage == ExecutableFile.POSTINSTALL && efile.executionStage == ExecutableFile.POSTINSTALL) || (currentStage == ExecutableFile.UNINSTALL && efile.executionStage == ExecutableFile.UNINSTALL))) {
262: List<String> paramList = new ArrayList<String>();
263: if (ExecutableFile.BIN == efile.type)
264: paramList.add(file.toString());
265:
266: else if (ExecutableFile.JAR == efile.type
267: && null == efile.mainClass) {
268: paramList.add(System.getProperty("java.home")
269: + "/bin/java");
270: paramList.add("-jar");
271: paramList.add(file.toString());
272: } else if (ExecutableFile.JAR == efile.type
273: && null != efile.mainClass) {
274: paramList.add(System.getProperty("java.home")
275: + "/bin/java");
276: paramList.add("-cp");
277: try {
278: paramList.add(buildClassPath(file.toString()));
279: } catch (Exception e) {
280: exitStatus = -1;
281: Debug.error(e);
282: }
283: paramList.add(efile.mainClass);
284: }
285:
286: if (null != efile.argList && !efile.argList.isEmpty())
287: paramList.addAll(efile.argList);
288:
289: String[] params = new String[paramList.size()];
290: for (int i = 0; i < paramList.size(); i++)
291: params[i] = paramList.get(i);
292:
293: exitStatus = executeCommand(params, output);
294:
295: // bring a dialog depending on return code and failure handling
296: if (exitStatus != 0) {
297: deleteAfterwards = false;
298: String message = output[0] + "\n" + output[1];
299: if (message.length() == 1)
300: message = "Failed to execute "
301: + file.toString() + ".";
302:
303: if (efile.onFailure == ExecutableFile.ABORT) {
304: // CHECKME: let the user decide or abort anyway?
305: handler.emitError("file execution error",
306: message);
307: } else if (efile.onFailure == ExecutableFile.WARN) {
308: // CHECKME: let the user decide or abort anyway?
309: handler.emitWarning("file execution error",
310: message);
311: exitStatus = 0;
312: } else if (efile.onFailure == ExecutableFile.IGNORE) {
313: // do nothing
314: exitStatus = 0;
315: } else {
316: if (handler.askQuestion("Execution Failed",
317: message + "\nContinue Installation?",
318: AbstractUIHandler.CHOICES_YES_NO) == AbstractUIHandler.ANSWER_YES)
319: exitStatus = 0;
320: }
321:
322: }
323:
324: }
325:
326: // POSTINSTALL executables will be deleted
327: if (efile.executionStage == ExecutableFile.POSTINSTALL
328: && deleteAfterwards) {
329: if (file.canWrite())
330: file.delete();
331: }
332:
333: }
334: return exitStatus;
335: }
336:
337: /**
338: * Transform classpath as specified in targetFile attribute into
339: * OS specific classpath. This method also resolves directories
340: * containing jar files. ';' and ':' are valid delimiters allowed
341: * in targetFile attribute.
342: *
343: * @param targetFile
344: * @return valid Java classpath
345: * @throws Exception
346: */
347: private String buildClassPath(String targetFile) throws Exception {
348: StringBuffer classPath = new StringBuffer();
349: List<String> jars = new ArrayList<String>();
350: String rawClassPath = targetFile.replace(':',
351: File.pathSeparatorChar).replace(';',
352: File.pathSeparatorChar);
353: String[] rawJars = rawClassPath.split(""
354: + File.pathSeparatorChar);
355: for (String rawJar : rawJars) {
356: File file = new File(rawJar);
357: jars.add(rawJar);
358:
359: if (file.isDirectory()) {
360: String[] subDirJars = FileUtil.getFileNames(rawJar,
361: new FilenameFilter() {
362: public boolean accept(File dir, String name) {
363: return name.toLowerCase().endsWith(
364: JAR_FILE_SUFFIX);
365: }
366:
367: });
368: if (subDirJars != null) {
369: for (String subDirJar : subDirJars) {
370: jars.add(rawJar + File.separator + subDirJar);
371: }
372: }
373: }
374: }
375:
376: Iterator<String> iter = jars.iterator();
377: if (iter.hasNext()) {
378: classPath.append(iter.next());
379: }
380: while (iter.hasNext()) {
381: classPath.append(File.pathSeparatorChar)
382: .append(iter.next());
383: }
384:
385: return classPath.toString();
386: }
387:
388: /** The files to execute. */
389: private Collection<ExecutableFile> files;
390: }
|