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