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