001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/
005: * http://izpack.codehaus.org/
006: *
007: * Copyright 2004 Tino Schwarze
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.installer;
023:
024: import java.io.BufferedReader;
025: import java.io.File;
026: import java.io.FileOutputStream;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.InputStreamReader;
030: import java.io.PrintWriter;
031: import java.lang.reflect.InvocationTargetException;
032: import java.lang.reflect.Method;
033: import java.text.SimpleDateFormat;
034: import java.util.ArrayList;
035: import java.util.Date;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.Vector;
039:
040: import net.n3.nanoxml.NonValidator;
041: import net.n3.nanoxml.StdXMLParser;
042: import net.n3.nanoxml.StdXMLReader;
043: import net.n3.nanoxml.XMLElement;
044: import net.n3.nanoxml.XMLBuilderFactory;
045:
046: import com.izforge.izpack.Pack;
047: import com.izforge.izpack.rules.Condition;
048: import com.izforge.izpack.rules.RulesEngine;
049: import com.izforge.izpack.util.AbstractUIProcessHandler;
050: import com.izforge.izpack.util.Debug;
051: import com.izforge.izpack.util.IoHelper;
052: import com.izforge.izpack.util.OsConstraint;
053: import com.izforge.izpack.util.VariableSubstitutor;
054:
055: /**
056: * This class does alle the work for the process panel.
057: *
058: * It responsible for
059: * <ul>
060: * <li>parsing the process spec XML file
061: * <li>performing the actions described therein
062: * </ul>
063: *
064: * @author Tino Schwarze
065: */
066: public class ProcessPanelWorker implements Runnable {
067:
068: /** Name of resource for specifying processing parameters. */
069: private static final String SPEC_RESOURCE_NAME = "ProcessPanel.Spec.xml";
070:
071: private VariableSubstitutor vs;
072:
073: protected AbstractUIProcessHandler handler;
074:
075: private ArrayList<ProcessingJob> jobs = new ArrayList<ProcessingJob>();
076:
077: private boolean result = true;
078:
079: private static PrintWriter logfile = null;
080:
081: private String logfiledir = null;
082:
083: protected AutomatedInstallData idata;
084:
085: /**
086: * The constructor.
087: *
088: * @param idata The installation data.
089: * @param handler The handler to notify of progress.
090: */
091: public ProcessPanelWorker(AutomatedInstallData idata,
092: AbstractUIProcessHandler handler) throws IOException {
093: this .handler = handler;
094: this .idata = idata;
095: this .vs = new VariableSubstitutor(idata.getVariables());
096:
097: // Removed this test in order to move out of the CTOR (ExecuteForPack
098: // Patch)
099: // if (!readSpec())
100: // throw new IOException("Error reading processing specification");
101: }
102:
103: private boolean readSpec() throws IOException {
104: InputStream input;
105: try {
106: input = ResourceManager.getInstance().getInputStream(
107: SPEC_RESOURCE_NAME);
108: } catch (Exception e) {
109: e.printStackTrace();
110: return false;
111: }
112:
113: StdXMLParser parser = new StdXMLParser();
114: parser.setBuilder(XMLBuilderFactory.createXMLBuilder());
115: parser.setValidator(new NonValidator());
116:
117: XMLElement spec;
118: try {
119: parser.setReader(new StdXMLReader(input));
120:
121: spec = (XMLElement) parser.parse();
122: } catch (Exception e) {
123: System.err
124: .println("Error parsing XML specification for processing.");
125: System.err.println(e.toString());
126: return false;
127: }
128:
129: if (!spec.hasChildren())
130: return false;
131:
132: // Handle logfile
133: XMLElement lfd = spec.getFirstChildNamed("logfiledir");
134: if (lfd != null) {
135: logfiledir = lfd.getContent();
136: }
137:
138: for (XMLElement job_el : spec.getChildrenNamed("job")) {
139: String conditionid = job_el.getAttribute("conditionid");
140: if (conditionid != null) {
141: Debug.trace("Condition for job.");
142: Condition cond = RulesEngine.getCondition(conditionid);
143: if ((cond != null) && !cond.isTrue()) {
144: Debug.trace("condition is not fulfilled.");
145: // skip, if there is a condition and this condition isn't true
146: continue;
147: }
148: }
149: Debug.trace("Condition is fulfilled or not existent.");
150: // ExecuteForPack Patch
151: // Check if processing required for pack
152: Vector<XMLElement> forPacks = job_el
153: .getChildrenNamed("executeForPack");
154: if (!jobRequiredFor(forPacks)) {
155: continue;
156: }
157:
158: // first check OS constraints - skip jobs not suited for this OS
159: List<OsConstraint> constraints = OsConstraint
160: .getOsList(job_el);
161:
162: if (OsConstraint.oneMatchesCurrentSystem(constraints)) {
163: List<Processable> ef_list = new ArrayList<Processable>();
164:
165: String job_name = job_el.getAttribute("name", "");
166:
167: for (XMLElement ef : job_el
168: .getChildrenNamed("executefile")) {
169: String ef_name = ef.getAttribute("name");
170:
171: if ((ef_name == null) || (ef_name.length() == 0)) {
172: System.err
173: .println("missing \"name\" attribute for <executefile>");
174: return false;
175: }
176:
177: List<String> args = new ArrayList<String>();
178:
179: for (XMLElement arg_el : ef.getChildrenNamed("arg")) {
180: String arg_val = arg_el.getContent();
181:
182: args.add(arg_val);
183: }
184:
185: ef_list.add(new ExecutableFile(ef_name, args));
186: }
187:
188: for (XMLElement ef : job_el
189: .getChildrenNamed("executeclass")) {
190: String ef_name = ef.getAttribute("name");
191: if ((ef_name == null) || (ef_name.length() == 0)) {
192: System.err
193: .println("missing \"name\" attribute for <executeclass>");
194: return false;
195: }
196:
197: List<String> args = new ArrayList<String>();
198: for (XMLElement arg_el : ef.getChildrenNamed("arg")) {
199: String arg_val = arg_el.getContent();
200: args.add(arg_val);
201: }
202:
203: ef_list.add(new ExecutableClass(ef_name, args));
204: }
205: this .jobs.add(new ProcessingJob(job_name, ef_list));
206: }
207:
208: }
209:
210: return true;
211: }
212:
213: /**
214: * This is called when the processing thread is activated.
215: *
216: * Can also be called directly if asynchronous processing is not desired.
217: */
218: public void run() {
219: // ExecuteForPack patch
220: // Read spec only here... not before, cause packs are otherwise
221: // all selected or de-selected
222: try {
223: if (!readSpec()) {
224: System.err
225: .println("Error parsing XML specification for processing.");
226: return;
227: }
228: } catch (java.io.IOException ioe) {
229: System.err.println(ioe.toString());
230: return;
231: }
232:
233: // Create logfile if needed. Do it at this point because
234: // variable substitution needs selected install path.
235: if (logfiledir != null) {
236: logfiledir = IoHelper.translatePath(logfiledir,
237: new VariableSubstitutor(idata.getVariables()));
238:
239: File lf;
240:
241: String appVersion = idata.getVariable("APP_VER");
242:
243: if (appVersion != null)
244: appVersion = "V" + appVersion;
245: else
246: appVersion = "undef";
247:
248: String identifier = (new SimpleDateFormat("yyyyMMddHHmmss"))
249: .format(new Date());
250:
251: identifier = appVersion.replace(' ', '_') + "_"
252: + identifier;
253:
254: try {
255: lf = File.createTempFile("Install_" + identifier + "_",
256: ".log", new File(logfiledir));
257: logfile = new PrintWriter(new FileOutputStream(lf),
258: true);
259: } catch (IOException e) {
260: Debug.error(e);
261: // TODO throw or throw not, that's the question...
262: }
263: }
264:
265: this .handler.startProcessing(this .jobs.size());
266:
267: for (ProcessingJob pj : this .jobs) {
268: this .handler.startProcess(pj.name);
269:
270: this .result = pj.run(this .handler, this .vs);
271:
272: this .handler.finishProcess();
273:
274: if (!this .result) {
275: break;
276: }
277: }
278:
279: this .handler.finishProcessing();
280: if (logfile != null)
281: logfile.close();
282: }
283:
284: /** Start the compilation in a separate thread. */
285: public void startThread() {
286: Thread processingThread = new Thread(this , "processing thread");
287: // will call this.run()
288: processingThread.start();
289: }
290:
291: /**
292: * Return the result of the process execution.
293: *
294: * @return true if all processes succeeded, false otherwise.
295: */
296: public boolean getResult() {
297: return this .result;
298: }
299:
300: interface Processable {
301:
302: /**
303: * @param handler The UI handler for user interaction and to send output to.
304: * @return true on success, false if processing should stop
305: */
306: public boolean run(AbstractUIProcessHandler handler,
307: VariableSubstitutor vs);
308: }
309:
310: private static class ProcessingJob implements Processable {
311:
312: public String name;
313:
314: private List<Processable> processables;
315:
316: public ProcessingJob(String name, List<Processable> processables) {
317: this .name = name;
318: this .processables = processables;
319: }
320:
321: public boolean run(AbstractUIProcessHandler handler,
322: VariableSubstitutor vs) {
323: for (Processable pr : this .processables) {
324: if (!pr.run(handler, vs)) {
325: return false;
326: }
327: }
328:
329: return true;
330: }
331:
332: }
333:
334: private static class ExecutableFile implements Processable {
335:
336: private String filename;
337:
338: private List<String> arguments;
339:
340: protected AbstractUIProcessHandler handler;
341:
342: public ExecutableFile(String fn, List<String> args) {
343: this .filename = fn;
344: this .arguments = args;
345: }
346:
347: public boolean run(AbstractUIProcessHandler handler,
348: VariableSubstitutor vs) {
349: this .handler = handler;
350:
351: String params[] = new String[this .arguments.size() + 1];
352:
353: params[0] = vs.substitute(this .filename, "plain");
354:
355: int i = 1;
356: for (String argument : this .arguments) {
357: params[i++] = vs.substitute(argument, "plain");
358: }
359:
360: try {
361: Process p = Runtime.getRuntime().exec(params);
362:
363: OutputMonitor stdoutMon = new OutputMonitor(
364: this .handler, p.getInputStream(), false);
365: OutputMonitor stderrMon = new OutputMonitor(
366: this .handler, p.getErrorStream(), true);
367: Thread stdoutThread = new Thread(stdoutMon);
368: Thread stderrThread = new Thread(stderrMon);
369: stdoutThread.setDaemon(true);
370: stderrThread.setDaemon(true);
371: stdoutThread.start();
372: stderrThread.start();
373:
374: try {
375: int exitStatus = p.waitFor();
376:
377: stopMonitor(stdoutMon, stdoutThread);
378: stopMonitor(stderrMon, stderrThread);
379:
380: if (exitStatus != 0) {
381: // New bahavior: make it fail
382: this .handler.emitError(
383: "Process execution failure",
384: "The process has returned an error.");
385: return false;
386: /*if (this.handler.askQuestion("process execution failed",
387: "Continue anyway?", AbstractUIHandler.CHOICES_YES_NO,
388: AbstractUIHandler.ANSWER_YES) == AbstractUIHandler.ANSWER_NO)
389: {
390: return false;
391: }*/
392: }
393: } catch (InterruptedException ie) {
394: p.destroy();
395: this .handler.emitError("process interrupted", ie
396: .toString());
397: return false;
398: }
399: } catch (IOException ioe) {
400: this .handler.emitError("I/O error", ioe.toString());
401: return false;
402: }
403:
404: return true;
405: }
406:
407: private void stopMonitor(OutputMonitor m, Thread t) {
408: // taken from com.izforge.izpack.util.FileExecutor
409: m.doStop();
410: long softTimeout = 500;
411: try {
412: t.join(softTimeout);
413: } catch (InterruptedException e) {
414: }
415:
416: if (!t.isAlive())
417: return;
418:
419: t.interrupt();
420: long hardTimeout = 500;
421: try {
422: t.join(hardTimeout);
423: } catch (InterruptedException e) {
424: }
425: }
426:
427: static public class OutputMonitor implements Runnable {
428:
429: private boolean stderr = false;
430:
431: private AbstractUIProcessHandler handler;
432:
433: private BufferedReader reader;
434:
435: private Boolean stop = false;
436:
437: public OutputMonitor(AbstractUIProcessHandler handler,
438: InputStream is, boolean stderr) {
439: this .stderr = stderr;
440: this .reader = new BufferedReader(new InputStreamReader(
441: is));
442: this .handler = handler;
443: }
444:
445: public void run() {
446: try {
447: String line;
448: while ((line = reader.readLine()) != null) {
449: this .handler.logOutput(line, stderr);
450:
451: // log output also to file given in ProcessPanelSpec
452:
453: if (logfile != null)
454: logfile.println(line);
455:
456: synchronized (this .stop) {
457: if (stop)
458: return;
459: }
460: }
461: } catch (IOException ioe) {
462: this .handler.logOutput(ioe.toString(), true);
463:
464: // log errors also to file given in ProcessPanelSpec
465:
466: if (logfile != null)
467: logfile.println(ioe.toString());
468:
469: }
470:
471: }
472:
473: public void doStop() {
474: synchronized (this .stop) {
475: this .stop = true;
476: }
477: }
478:
479: }
480:
481: }
482:
483: /**
484: * Tries to create a class that has an empty contstructor and a method
485: * run(AbstractUIProcessHandler, String[]) If found, it calls the method and processes all
486: * returned exceptions
487: */
488: private static class ExecutableClass implements Processable {
489:
490: final private String myClassName;
491:
492: final private List<String> myArguments;
493:
494: protected AbstractUIProcessHandler myHandler;
495:
496: public ExecutableClass(String className, List<String> args) {
497: myClassName = className;
498: myArguments = args;
499: }
500:
501: public boolean run(AbstractUIProcessHandler aHandler,
502: VariableSubstitutor varSubstitutor) {
503: boolean result = false;
504: myHandler = aHandler;
505:
506: String params[] = new String[myArguments.size()];
507:
508: int i = 0;
509: for (String myArgument : myArguments) {
510: params[i++] = varSubstitutor.substitute(myArgument,
511: "plain");
512: }
513:
514: try {
515: ClassLoader loader = this .getClass().getClassLoader();
516: Class procClass = loader.loadClass(myClassName);
517:
518: Object o = procClass.newInstance();
519: Method m = procClass.getMethod("run",
520: new Class[] { AbstractUIProcessHandler.class,
521: String[].class });
522:
523: m.invoke(o, new Object[] { myHandler, params });
524: result = true;
525: } catch (SecurityException e) {
526: myHandler.emitError("Post Processing Error",
527: "Security exception thrown when processing class: "
528: + myClassName);
529: } catch (ClassNotFoundException e) {
530: myHandler.emitError("Post Processing Error",
531: "Cannot find processing class: " + myClassName);
532: } catch (NoSuchMethodException e) {
533: myHandler.emitError("Post Processing Error",
534: "Processing class does not have 'run' method: "
535: + myClassName);
536: } catch (IllegalAccessException e) {
537: myHandler.emitError("Post Processing Error",
538: "Error accessing processing class: "
539: + myClassName);
540: } catch (InvocationTargetException e) {
541: myHandler.emitError("Post Processing Error",
542: "Invocation Problem calling : " + myClassName
543: + ", " + e.getCause().getMessage());
544: } catch (Exception e) {
545: myHandler.emitError("Post Processing Error",
546: "Exception when running processing class: "
547: + myClassName + ", " + e.getMessage());
548: } catch (Error e) {
549: myHandler.emitError("Post Processing Error",
550: "Error when running processing class: "
551: + myClassName + ", " + e.getMessage());
552: } catch (Throwable e) {
553: myHandler.emitError("Post Processing Error",
554: "Error when running processing class: "
555: + myClassName + ", " + e.getMessage());
556: }
557: return result;
558: }
559: }
560:
561: /*------------------------ ExecuteForPack PATCH -------------------------*/
562: /*
563: * Verifies if the job is required for any of the packs listed. The job is required for a pack
564: * in the list if that pack is actually selected for installation. <br><br> <b>Note:</b><br>
565: * If the list of selected packs is empty then <code>true</code> is always returned. The same
566: * is true if the <code>packs</code> list is empty.
567: *
568: * @param packs a <code>Vector</code> of <code>String</code>s. Each of the strings denotes
569: * a pack for which the schortcut should be created if the pack is actually installed.
570: *
571: * @return <code>true</code> if the shortcut is required for at least on pack in the list,
572: * otherwise returns <code>false</code>.
573: */
574: /*--------------------------------------------------------------------------*/
575: /*
576: * @design
577: *
578: * The information about the installed packs comes from InstallData.selectedPacks. This assumes
579: * that this panel is presented to the user AFTER the PacksPanel.
580: *
581: * /*--------------------------------------------------------------------------
582: */
583:
584: private boolean jobRequiredFor(Vector<XMLElement> packs) {
585: String selected;
586: String required;
587:
588: if (packs.size() == 0) {
589: return (true);
590: }
591:
592: // System.out.println ("Number of selected packs is "
593: // +idata.selectedPacks.size () );
594:
595: for (int i = 0; i < idata.selectedPacks.size(); i++) {
596: selected = ((Pack) idata.selectedPacks.get(i)).name;
597:
598: // System.out.println ("Selected pack is " + selected);
599:
600: for (int k = 0; k < packs.size(); k++) {
601: required = (packs.elementAt(k))
602: .getAttribute("name", "");
603: // System.out.println ("Attribute name is " + required);
604: if (selected.equals(required)) {
605: // System.out.println ("Return true");
606: return (true);
607: }
608: }
609: }
610: return (false);
611: }
612:
613: }
|