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