001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017 018package org.opends.quicksetup; 019 020import static com.forgerock.opendj.cli.Utils.*; 021 022import static org.opends.messages.QuickSetupMessages.*; 023 024import java.io.ByteArrayOutputStream; 025import java.io.PrintStream; 026import java.util.Map; 027import java.util.Set; 028 029import javax.naming.NamingException; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.i18n.LocalizableMessageBuilder; 033import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2; 034import org.forgerock.i18n.slf4j.LocalizedLogger; 035import org.opends.admin.ads.ADSContext; 036import org.opends.admin.ads.ServerDescriptor; 037import org.opends.admin.ads.TopologyCacheException; 038import org.opends.admin.ads.TopologyCacheFilter; 039import org.opends.admin.ads.util.ApplicationTrustManager; 040import org.opends.admin.ads.util.ConnectionWrapper; 041import org.opends.admin.ads.util.PreferredConnection; 042import org.opends.admin.ads.util.ServerLoader; 043import org.opends.quicksetup.event.ProgressNotifier; 044import org.opends.quicksetup.event.ProgressUpdateListener; 045import org.opends.quicksetup.ui.GuiApplication; 046import org.opends.quicksetup.util.ProgressMessageFormatter; 047import org.opends.quicksetup.util.UIKeyStore; 048import org.opends.quicksetup.util.Utils; 049 050/** 051 * This class represents an application that can be run in the context of 052 * QuickSetup. Examples of applications might be 'installer' and 'uninstaller'. 053 */ 054public abstract class Application implements ProgressNotifier, Runnable { 055 056 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 057 058 /** Represents current install state. */ 059 protected CurrentInstallStatus installStatus; 060 061 private UserData userData; 062 063 private Installation installation; 064 065 private ApplicationTrustManager trustManager; 066 067 private boolean notifyListeners = true; 068 069 /** Formats progress messages. */ 070 protected ProgressMessageFormatter formatter; 071 072 /** Handler for listeners and event firing. */ 073 private ProgressUpdateListenerDelegate listenerDelegate; 074 075 private final ErrorPrintStream err = new ErrorPrintStream(); 076 private final OutputPrintStream out = new OutputPrintStream(); 077 078 /** Temporary log file where messages will be logged. */ 079 protected TempLogFile tempLogFile; 080 081 /** 082 * Creates an application by instantiating the Application class 083 * denoted by the System property 084 * <code>org.opends.quicksetup.Application.class</code>. 085 * @return Application object that was newly instantiated 086 * @throws RuntimeException if there was a problem 087 * creating the new Application object 088 */ 089 public static GuiApplication create() throws RuntimeException { 090 GuiApplication app; 091 String appClassName = 092 System.getProperty("org.opends.quicksetup.Application.class"); 093 if (appClassName != null) { 094 Class<?> appClass = null; 095 try { 096 appClass = Class.forName(appClassName); 097 app = (GuiApplication) appClass.newInstance(); 098 } catch (ClassNotFoundException e) { 099 logger.info(LocalizableMessage.raw("error creating quicksetup application", e)); 100 String msg = "Application class " + appClass + " not found"; 101 throw new RuntimeException(msg, e); 102 } catch (IllegalAccessException e) { 103 logger.info(LocalizableMessage.raw("error creating quicksetup application", e)); 104 String msg = "Could not access class " + appClass; 105 throw new RuntimeException(msg, e); 106 } catch (InstantiationException e) { 107 logger.info(LocalizableMessage.raw("error creating quicksetup application", e)); 108 String msg = "Error instantiating class " + appClass; 109 throw new RuntimeException(msg, e); 110 } catch (ClassCastException e) { 111 String msg = "The class indicated by the system property " + 112 "'org.opends.quicksetup.Application.class' must " + 113 " must be of type Application"; 114 throw new RuntimeException(msg, e); 115 } 116 } else { 117 String msg = "System property 'org.opends.quicksetup.Application.class'" + 118 " must specify class quicksetup application"; 119 throw new RuntimeException(msg); 120 } 121 return app; 122 } 123 124 /** 125 * Sets this instances user data. 126 * @param userData UserData this application will use 127 * when executing 128 */ 129 public void setUserData(UserData userData) { 130 this.userData = userData; 131 } 132 133 /** 134 * Creates a set of user data with default values. 135 * @return UserData empty set of UserData 136 */ 137 public UserData createUserData() { 138 return new UserData(); 139 } 140 141 /** 142 * Adds a ProgressUpdateListener that will be notified of updates in 143 * the install progress. 144 * @param l the ProgressUpdateListener to be added. 145 */ 146 @Override 147 public void addProgressUpdateListener(ProgressUpdateListener l) 148 { 149 listenerDelegate.addProgressUpdateListener(l); 150 } 151 152 /** 153 * Removes a ProgressUpdateListener. 154 * @param l the ProgressUpdateListener to be removed. 155 */ 156 @Override 157 public void removeProgressUpdateListener(ProgressUpdateListener l) 158 { 159 listenerDelegate.removeProgressUpdateListener(l); 160 } 161 162 /** 163 * Gets the OpenDJ installation associated with the execution of this 164 * command. 165 * @return Installation object representing the current OpenDS installation 166 */ 167 public Installation getInstallation() { 168 if (installation == null) { 169 String installPath = getInstallationPath(); 170 String instancePath = getInstancePath(); 171 if (installPath != null) { 172 if (instancePath != null) 173 { 174 installation = new Installation(installPath, instancePath); 175 } 176 else 177 { 178 installation = new Installation(installPath, installPath); 179 } 180 } 181 } 182 return installation; 183 } 184 185 /** 186 * Sets the application's installation. 187 * @param installation describing the application's OpenDS installation 188 */ 189 public void setInstallation(Installation installation) { 190 this.installation = installation; 191 } 192 193 194 /** 195 * Returns the UserData object representing the parameters provided by 196 * the user to do the installation. 197 * 198 * @return the UserData object representing the parameters provided 199 * by the user to do the installation. 200 */ 201 public UserData getUserData() 202 { 203 if (userData == null) { 204 userData = createUserData(); 205 } 206 return userData; 207 } 208 209 /** 210 * This method notifies the ProgressUpdateListeners that there was an 211 * update in the installation progress. 212 * @param ratio the integer that specifies which percentage of the whole 213 * installation has been completed. 214 */ 215 public void notifyListenersDone(Integer ratio) { 216 notifyListeners(ratio, 217 getSummary(getCurrentProgressStep()), 218 getFormattedDoneWithLineBreak()); 219 } 220 221 /** 222 * This method notifies the ProgressUpdateListeners that there was an 223 * update in the installation progress. 224 * @param ratio the integer that specifies which percentage of the whole 225 * installation has been completed. 226 */ 227 public void notifyListenersRatioChange(Integer ratio) { 228 notifyListeners(ratio, 229 getSummary(getCurrentProgressStep()), 230 null); 231 } 232 233 /** 234 * This method notifies the ProgressUpdateListeners that there was an 235 * update in the installation progress. 236 * @param ratio the integer that specifies which percentage of 237 * the whole installation has been completed. 238 * @param currentPhaseSummary the localized summary message for the 239 * current installation progress in formatted form. 240 * @param newLogDetail the new log messages that we have for the 241 * installation in formatted form. 242 */ 243 @Override 244 public void notifyListeners(Integer ratio, LocalizableMessage currentPhaseSummary, 245 LocalizableMessage newLogDetail) 246 { 247 if (notifyListeners) 248 { 249 listenerDelegate.notifyListeners(getCurrentProgressStep(), 250 ratio, currentPhaseSummary, newLogDetail); 251 } 252 } 253 254 /** 255 * This method notifies the ProgressUpdateListeners that there was an 256 * update in the installation progress. 257 * @param ratio the integer that specifies which percentage of 258 * the whole installation has been completed. 259 * @param newLogDetail the localized additional log message. 260 */ 261 public void notifyListenersWithPoints(Integer ratio, 262 LocalizableMessage newLogDetail) { 263 notifyListeners(ratio, getSummary(getCurrentProgressStep()), 264 formatter.getFormattedWithPoints(newLogDetail)); 265 } 266 267 /** 268 * Sets the formatter this instance should use to used 269 * to format progress messages. 270 * @param formatter ProgressMessageFormatter for formatting 271 * progress messages 272 */ 273 public void setProgressMessageFormatter(ProgressMessageFormatter formatter) { 274 this.formatter = formatter; 275 this.listenerDelegate = new ProgressUpdateListenerDelegate(); 276 } 277 278 /** 279 * Gets the formatter this instance is currently using. 280 * @return the progress message formatter currently used by this 281 * application 282 */ 283 public ProgressMessageFormatter getProgressMessageFormatter() { 284 return formatter; 285 } 286 287 /** 288 * Returns the formatted representation of the text that is the summary of the 289 * installation process (the one that goes in the UI next to the progress 290 * bar). 291 * @param text the source text from which we want to get the formatted 292 * representation 293 * @return the formatted representation of an error for the given text. 294 */ 295 protected LocalizableMessage getFormattedSummary(LocalizableMessage text) 296 { 297 return formatter.getFormattedSummary(text); 298 } 299 300 /** 301 * Returns the formatted representation of an error for a given text. 302 * @param text the source text from which we want to get the formatted 303 * representation 304 * @return the formatted representation of an error for the given text. 305 */ 306 protected LocalizableMessage getFormattedError(LocalizableMessage text) 307 { 308 return formatter.getFormattedError(text, false); 309 } 310 311 /** 312 * Returns the formatted representation of an warning for a given text. 313 * @param text the source text from which we want to get the formatted 314 * representation 315 * @return the formatted representation of an warning for the given text. 316 */ 317 public LocalizableMessage getFormattedWarning(LocalizableMessage text) 318 { 319 return formatter.getFormattedWarning(text, false); 320 } 321 322 /** 323 * Returns the formatted representation of a success message for a given text. 324 * @param text the source text from which we want to get the formatted 325 * representation 326 * @return the formatted representation of an success message for the given 327 * text. 328 */ 329 protected LocalizableMessage getFormattedSuccess(LocalizableMessage text) 330 { 331 return formatter.getFormattedSuccess(text); 332 } 333 334 /** 335 * Returns the formatted representation of a log error message for a given 336 * text. 337 * @param text the source text from which we want to get the formatted 338 * representation 339 * @return the formatted representation of a log error message for the given 340 * text. 341 */ 342 public LocalizableMessage getFormattedLogError(LocalizableMessage text) 343 { 344 return formatter.getFormattedLogError(text); 345 } 346 347 /** 348 * Returns the formatted representation of a log message for a given text. 349 * @param text the source text from which we want to get the formatted 350 * representation 351 * @return the formatted representation of a log message for the given text. 352 */ 353 public LocalizableMessage getFormattedLog(LocalizableMessage text) 354 { 355 return formatter.getFormattedLog(text); 356 } 357 358 /** 359 * Returns the formatted representation of the 'Done' text string. 360 * @return the formatted representation of the 'Done' text string. 361 */ 362 public LocalizableMessage getFormattedDone() 363 { 364 return LocalizableMessage.raw(formatter.getFormattedDone()); 365 } 366 367 /** 368 * Returns the formatted representation of the 'Done' text string 369 * with a line break at the end. 370 * @return the formatted representation of the 'Done' text string. 371 */ 372 public LocalizableMessage getFormattedDoneWithLineBreak() { 373 return new LocalizableMessageBuilder(formatter.getFormattedDone()) 374 .append(formatter.getLineBreak()).toMessage(); 375 } 376 377 /** 378 * Returns the formatted representation of the argument text to which we add 379 * points. For instance if we pass as argument 'Configuring Server' the 380 * return value will be 'Configuring Server .....'. 381 * @param text the String to which add points. 382 * @return the formatted representation of the '.....' text string. 383 */ 384 public LocalizableMessage getFormattedWithPoints(LocalizableMessage text) 385 { 386 return formatter.getFormattedWithPoints(text); 387 } 388 389 /** 390 * Returns the formatted representation of a progress message for a given 391 * text. 392 * @param text the source text from which we want to get the formatted 393 * representation 394 * @return the formatted representation of a progress message for the given 395 * text. 396 */ 397 public LocalizableMessage getFormattedProgress(LocalizableMessage text) 398 { 399 return formatter.getFormattedProgress(text); 400 } 401 402 /** 403 * Returns the formatted representation of a progress message for a given 404 * text with a line break. 405 * @param text the source text from which we want to get the formatted 406 * representation 407 * @return the formatted representation of a progress message for the given 408 * text. 409 */ 410 public LocalizableMessage getFormattedProgressWithLineBreak(LocalizableMessage text) 411 { 412 return new LocalizableMessageBuilder(formatter.getFormattedProgress(text)) 413 .append(getLineBreak()).toMessage(); 414 } 415 416 /** 417 * Returns the formatted representation of an error message for a given 418 * exception. 419 * This method applies a margin if the applyMargin parameter is 420 * <CODE>true</CODE>. 421 * @param t the exception. 422 * @param applyMargin specifies whether we apply a margin or not to the 423 * resulting formatted text. 424 * @return the formatted representation of an error message for the given 425 * exception. 426 */ 427 protected LocalizableMessage getFormattedError(Throwable t, boolean applyMargin) 428 { 429 return formatter.getFormattedError(t, applyMargin); 430 } 431 432 /** 433 * Returns the line break formatted. 434 * @return the line break formatted. 435 */ 436 public LocalizableMessage getLineBreak() 437 { 438 return formatter.getLineBreak(); 439 } 440 441 /** 442 * Returns the task separator formatted. 443 * @return the task separator formatted. 444 */ 445 protected LocalizableMessage getTaskSeparator() 446 { 447 return formatter.getTaskSeparator(); 448 } 449 450 /** 451 * This method is called when a new log message has been received. It will 452 * notify the ProgressUpdateListeners of this fact. 453 * @param newLogDetail the new log detail. 454 */ 455 public void notifyListeners(LocalizableMessage newLogDetail) 456 { 457 Integer ratio = getRatio(getCurrentProgressStep()); 458 LocalizableMessage currentPhaseSummary = getSummary(getCurrentProgressStep()); 459 notifyListeners(ratio, currentPhaseSummary, newLogDetail); 460 } 461 462 /** 463 * Returns the installation path. 464 * @return the installation path. 465 */ 466 public abstract String getInstallationPath(); 467 468 /** 469 * Returns the instance path. 470 * @return the instance path. 471 */ 472 public abstract String getInstancePath(); 473 474 475 /** 476 * Gets the current step. 477 * @return ProgressStep representing the current step 478 */ 479 public abstract ProgressStep getCurrentProgressStep(); 480 481 /** 482 * Gets an integer representing the amount of processing 483 * this application still needs to perform as a ratio 484 * out of 100. 485 * @param step ProgressStop for which a summary is needed 486 * @return ProgressStep representing the current step 487 */ 488 public abstract Integer getRatio(ProgressStep step); 489 490 /** 491 * Gets an i18n'd string representing the summary of 492 * a give ProgressStep. 493 * @param step ProgressStop for which a summary is needed 494 * @return String representing the summary 495 */ 496 public abstract LocalizableMessage getSummary(ProgressStep step); 497 498 /** 499 * Sets the current install status for this application. 500 * @param installStatus for the current installation. 501 */ 502 public void setCurrentInstallStatus(CurrentInstallStatus installStatus) { 503 this.installStatus = installStatus; 504 } 505 506 /** 507 * Returns whether the installer has finished or not. 508 * @return <CODE>true</CODE> if the install is finished or <CODE>false 509 * </CODE> if not. 510 */ 511 public abstract boolean isFinished(); 512 513 /** 514 * Returns the trust manager that can be used to establish secure connections. 515 * @return the trust manager that can be used to establish secure connections. 516 */ 517 public ApplicationTrustManager getTrustManager() 518 { 519 if (trustManager == null) 520 { 521 if (!Utils.isCli()) 522 { 523 try 524 { 525 trustManager = new ApplicationTrustManager(UIKeyStore.getInstance()); 526 } 527 catch (Throwable t) 528 { 529 logger.warn(LocalizableMessage.raw("Error retrieving UI key store: "+t, t)); 530 trustManager = new ApplicationTrustManager(null); 531 } 532 } 533 else 534 { 535 trustManager = new ApplicationTrustManager(null); 536 } 537 } 538 return trustManager; 539 } 540 541 542 543 /** 544 * Indicates whether this application is capable of cancelling 545 * the operation performed in the run method. A cancellable operation 546 * should leave its environment in the same state as it was prior to 547 * running the operation (files deleted, changes backed out etc.). 548 * 549 * Marking an <code>Application</code> as cancellable may control UI 550 * elements like the presense of a cancel button while the operation 551 * is being performed. 552 * 553 * Applications marked as cancellable should override the 554 * <code>cancel</code> method in such a way as to undo whatever 555 * actions have taken place in the run method up to that point. 556 * 557 * @return boolean where true inidcates that the operation is cancellable 558 */ 559 public abstract boolean isCancellable(); 560 561 /** 562 * Signals that the application should cancel a currently running 563 * operation as soon as possible and return the environment to the 564 * state prior to running the operation. When finished backing 565 * out changes the application should make sure that <code>isFinished</code> 566 * returns true so that the application can complete. 567 */ 568 public abstract void cancel(); 569 570 /** 571 * Checks whether the operation has been aborted. If it has throws an 572 * ApplicationException. All the applications that support abort must 573 * provide their implementation as the default implementation is empty. 574 * 575 * @throws ApplicationException thrown if the application was aborted. 576 */ 577 public void checkAbort() throws ApplicationException 578 { 579 // no-op 580 } 581 582 /** 583 * Returns a localized representation of a TopologyCacheException object. 584 * @param e the exception we want to obtain the representation from. 585 * @return a localized representation of a TopologyCacheException object. 586 */ 587 protected LocalizableMessage getMessage(TopologyCacheException e) 588 { 589 return Utils.getMessage(e); 590 } 591 592 /** 593 * Gets a connection based on the information that appears on the 594 * provided ServerDescriptor object. Note that the server is assumed to be 595 * registered and that contains a Map with ADSContext.ServerProperty keys. 596 * @param server the object describing the server. 597 * @param dn the dn to be used to authenticate. 598 * @param pwd the pwd to be used to authenticate. 599 * @param timeout the timeout to establish the connection in milliseconds. 600 * Use {@code 0} to express no timeout. 601 * @param cnx the ordered list of preferred connections to connect to the 602 * server. 603 * @return the InitialLdapContext to the remote server. 604 * @throws ApplicationException if something goes wrong. 605 */ 606 protected ConnectionWrapper getRemoteConnection(ServerDescriptor server, String dn, String pwd, int timeout, 607 Set<PreferredConnection> cnx) throws ApplicationException 608 { 609 Map<ADSContext.ServerProperty, Object> adsProperties = 610 server.getAdsProperties(); 611 TopologyCacheFilter filter = new TopologyCacheFilter(); 612 filter.setSearchMonitoringInformation(false); 613 filter.setSearchBaseDNInformation(false); 614 ServerLoader loader = new ServerLoader(adsProperties, dn, pwd, getTrustManager(), timeout, cnx, filter); 615 616 ConnectionWrapper connection; 617 try 618 { 619 connection = loader.createConnectionWrapper(); 620 } 621 catch (NamingException ne) 622 { 623 Arg2<Object, Object> arg2 = isCertificateException(ne) 624 ? INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER 625 : INFO_CANNOT_CONNECT_TO_REMOTE_GENERIC; 626 LocalizableMessage msg = arg2.get(server.getHostPort(true), ne.toString(true)); 627 throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, msg, ne); 628 } 629 return connection; 630 } 631 632 /** 633 * Returns <CODE>true</CODE> if the application is running in verbose mode and 634 * <CODE>false</CODE> otherwise. 635 * @return <CODE>true</CODE> if the application is running in verbose mode and 636 * <CODE>false</CODE> otherwise. 637 */ 638 public boolean isVerbose() 639 { 640 return getUserData().isVerbose(); 641 } 642 643 /** 644 * Returns the error stream to be used by the application when launching 645 * command lines. 646 * @return the error stream to be used by the application when launching 647 * command lines. 648 */ 649 public ErrorPrintStream getApplicationErrorStream() 650 { 651 return err; 652 } 653 654 /** 655 * Returns the output stream to be used by the application when launching 656 * command lines. 657 * @return the output stream to be used by the application when launching 658 * command lines. 659 */ 660 public OutputPrintStream getApplicationOutputStream() 661 { 662 return out; 663 } 664 665 666 /** 667 * Tells whether we must notify the listeners or not of the message 668 * received. 669 * @param notifyListeners the boolean that informs of whether we have 670 * to notify the listeners or not. 671 */ 672 public void setNotifyListeners(boolean notifyListeners) 673 { 674 this.notifyListeners = notifyListeners; 675 } 676 677 /** 678 * Method that is invoked by the printstreams with the messages received 679 * on operations such as start or import. This is done so that the 680 * application can parse this messages and display them. 681 * @param message the message that has been received 682 */ 683 protected void applicationPrintStreamReceived(String message) 684 { 685 // no-op 686 } 687 688 /** 689 * Sets the temporary log file where messages will be logged. 690 * 691 * @param tempLogFile 692 * temporary log file where messages will be logged. 693 */ 694 public void setTempLogFile(final TempLogFile tempLogFile) 695 { 696 this.tempLogFile = tempLogFile; 697 } 698 699 /** 700 * This class is used to notify the ProgressUpdateListeners of events 701 * that are written to the standard error. It is used in Installer. 702 * These classes just create a ErrorPrintStream and 703 * then they do a call to System.err with it. 704 * 705 * The class just reads what is written to the standard error, obtains an 706 * formatted representation of it and then notifies the 707 * ProgressUpdateListeners with the formatted messages. 708 */ 709 public class ErrorPrintStream extends ApplicationPrintStream { 710 711 /** Default constructor. */ 712 public ErrorPrintStream() { 713 super(); 714 } 715 716 @Override 717 protected LocalizableMessage formatString(String s) { 718 return getFormattedLogError(LocalizableMessage.raw(s)); 719 } 720 } 721 722 /** 723 * This class is used to notify the ProgressUpdateListeners of events 724 * that are written to the standard output. It is used in WebStartInstaller 725 * and in Installer. These classes just create a OutputPrintStream and 726 * then they do a call to System.out with it. 727 * 728 * The class just reads what is written to the standard output, obtains an 729 * formatted representation of it and then notifies the 730 * ProgressUpdateListeners with the formatted messages. 731 */ 732 public class OutputPrintStream extends ApplicationPrintStream 733 { 734 /** Default constructor. */ 735 public OutputPrintStream() { 736 super(); 737 } 738 739 @Override 740 protected LocalizableMessage formatString(String s) { 741 return getFormattedLog(LocalizableMessage.raw(s)); 742 } 743 } 744 745 /** 746 * This class is used to notify the ProgressUpdateListeners of events 747 * that are written to the standard streams. 748 */ 749 private abstract class ApplicationPrintStream extends PrintStream 750 { 751 private boolean isFirstLine; 752 753 /** 754 * Format a string before sending a listener notification. 755 * @param string to format 756 * @return formatted message 757 */ 758 protected abstract LocalizableMessage formatString(String string); 759 760 /** Default constructor. */ 761 public ApplicationPrintStream() 762 { 763 super(new ByteArrayOutputStream(), true); 764 isFirstLine = true; 765 } 766 767 @Override 768 public void println(String msg) 769 { 770 LocalizableMessageBuilder mb = new LocalizableMessageBuilder(); 771 if (!isFirstLine && !Utils.isCli()) 772 { 773 mb.append(getLineBreak()); 774 } 775 mb.append(formatString(msg)); 776 777 notifyListeners(mb.toMessage()); 778 applicationPrintStreamReceived(msg); 779 logger.info(LocalizableMessage.raw(msg)); 780 isFirstLine = false; 781 } 782 783 @Override 784 public void write(byte[] b, int off, int len) 785 { 786 if (b == null) 787 { 788 throw new NullPointerException("b is null"); 789 } 790 791 if (off + len > b.length) 792 { 793 throw new IndexOutOfBoundsException( 794 "len + off are bigger than the length of the byte array"); 795 } 796 println(new String(b, off, len)); 797 } 798 } 799 800 801 802 /** Class used to add points periodically to the end of the logs. */ 803 protected class PointAdder implements Runnable 804 { 805 private Thread t; 806 private boolean stopPointAdder; 807 private boolean pointAdderStopped; 808 809 /** Default constructor. */ 810 public PointAdder() 811 { 812 } 813 814 /** Starts the PointAdder: points are added at the end of the logs periodically. */ 815 public void start() 816 { 817 LocalizableMessageBuilder mb = new LocalizableMessageBuilder(); 818 mb.append(formatter.getSpace()); 819 for (int i=0; i< 5; i++) 820 { 821 mb.append(formatter.getFormattedPoint()); 822 } 823 Integer ratio = getRatio(getCurrentProgressStep()); 824 LocalizableMessage currentPhaseSummary = getSummary(getCurrentProgressStep()); 825 listenerDelegate.notifyListeners(getCurrentProgressStep(), 826 ratio, currentPhaseSummary, mb.toMessage()); 827 t = new Thread(this); 828 t.start(); 829 } 830 831 /** Stops the PointAdder: points are no longer added at the end of the logs periodically. */ 832 public synchronized void stop() 833 { 834 stopPointAdder = true; 835 while (!pointAdderStopped) 836 { 837 try 838 { 839 t.interrupt(); 840 // To allow the thread to set the boolean. 841 Thread.sleep(100); 842 } 843 catch (Throwable t) 844 { 845 // do nothing 846 } 847 } 848 } 849 850 @Override 851 public void run() 852 { 853 while (!stopPointAdder) 854 { 855 try 856 { 857 Thread.sleep(3000); 858 Integer ratio = getRatio(getCurrentProgressStep()); 859 LocalizableMessage currentPhaseSummary = getSummary(getCurrentProgressStep()); 860 listenerDelegate.notifyListeners(getCurrentProgressStep(), 861 ratio, currentPhaseSummary, formatter.getFormattedPoint()); 862 } 863 catch (Throwable t) 864 { 865 // do nothing 866 } 867 } 868 pointAdderStopped = true; 869 870 Integer ratio = getRatio(getCurrentProgressStep()); 871 LocalizableMessage currentPhaseSummary = getSummary(getCurrentProgressStep()); 872 listenerDelegate.notifyListeners(getCurrentProgressStep(), 873 ratio, currentPhaseSummary, formatter.getSpace()); 874 } 875 } 876}