/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.quicksetup; import org.opends.admin.ads.util.ApplicationTrustManager; import org.opends.quicksetup.event.ProgressNotifier; import org.opends.quicksetup.event.ProgressUpdateListener; import org.opends.quicksetup.event.ProgressUpdateEvent; import org.opends.quicksetup.util.ServerController; import org.opends.quicksetup.util.Utils; import org.opends.quicksetup.util.ProgressMessageFormatter; import org.opends.quicksetup.i18n.ResourceProvider; import org.opends.quicksetup.ui.GuiApplication; import java.io.PrintStream; import java.io.ByteArrayOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.HashSet; /** * This class represents an application that can be run in the context of * QuickSetup. Examples of applications might be 'installer', 'uninstaller' * and 'upgrader'. */ public abstract class Application implements ProgressNotifier, Runnable { static private final Logger LOG = Logger.getLogger(Application.class.getName()); /** Represents current install state. */ protected CurrentInstallStatus installStatus; private HashSet listeners = new HashSet(); private UserData userData; private Installation installation; private ServerController serverController; private ApplicationTrustManager trustManager; /** Formats progress messages. */ protected ProgressMessageFormatter formatter; /** * Creates an application by instantiating the Application class * denoted by the System property * org.opends.quicksetup.Application.class. * @return Application object that was newly instantiated * @throws RuntimeException if there was a problem * creating the new Application object */ static public GuiApplication create() throws RuntimeException { GuiApplication app; String appClassName = System.getProperty("org.opends.quicksetup.Application.class"); if (appClassName != null) { Class appClass = null; try { appClass = Class.forName(appClassName); app = (GuiApplication) appClass.newInstance(); } catch (ClassNotFoundException e) { LOG.log(Level.INFO, "error creating quicksetup application", e); String msg = "Application class " + appClass + " not found"; throw new RuntimeException(msg, e); } catch (IllegalAccessException e) { LOG.log(Level.INFO, "error creating quicksetup application", e); String msg = "Could not access class " + appClass; throw new RuntimeException(msg, e); } catch (InstantiationException e) { LOG.log(Level.INFO, "error creating quicksetup application", e); String msg = "Error instantiating class " + appClass; throw new RuntimeException(msg, e); } catch (ClassCastException e) { String msg = "The class indicated by the system property " + "'org.opends.quicksetup.Application.class' must " + " must be of type Application"; throw new RuntimeException(msg, e); } } else { String msg = "System property 'org.opends.quicksetup.Application.class'" + " must specify class quicksetup application"; throw new RuntimeException(msg); } return app; } /** * Sets this instances user data. * @param userData UserData this application will use * when executing */ public void setUserData(UserData userData) { this.userData = userData; } /** * Creates a set of user data with default values. * @return UserData empty set of UserData */ public UserData createUserData() { return new UserData(); } /** * Adds a ProgressUpdateListener that will be notified of updates in * the install progress. * @param l the ProgressUpdateListener to be added. */ public void addProgressUpdateListener(ProgressUpdateListener l) { listeners.add(l); } /** * Removes a ProgressUpdateListener. * @param l the ProgressUpdateListener to be removed. */ public void removeProgressUpdateListener(ProgressUpdateListener l) { listeners.remove(l); } /** * Gets the OpenDS installation associated with the execution of this * command. * @return Installation object representing the current OpenDS installation */ public Installation getInstallation() { if (installation == null) { String installPath = getInstallationPath(); if (installPath != null) { installation = new Installation(installPath); } } return installation; } /** * Sets the application's installation. * @param installation describing the application's OpenDS installation */ public void setInstallation(Installation installation) { this.installation = installation; } /** * Gets a server controller for use by this application. * @return ServerController that can be used to start and stop the server. */ public ServerController getServerController() { if (serverController == null) { serverController = new ServerController(this); } return serverController; } /** * Returns the UserData object representing the parameters provided by * the user to do the installation. * * @return the UserData object representing the parameters provided * by the user to do the installation. */ public UserData getUserData() { if (userData == null) { userData = createUserData(); } return userData; } /** * This method notifies the ProgressUpdateListeners that there was an * update in the installation progress. * @param ratioWhenCompleted the integer that specifies which percentage of * the whole installation has been completed. */ public void notifyListenersDone(Integer ratioWhenCompleted) { notifyListeners(ratioWhenCompleted, getSummary(getCurrentProgressStep()), formatter.getFormattedDone() + formatter.getLineBreak()); } /** * This method notifies the ProgressUpdateListeners that there was an * update in the installation progress. * @param ratio the integer that specifies which percentage of * the whole installation has been completed. * @param currentPhaseSummary the localized summary message for the * current installation progress in formatted form. * @param newLogDetail the new log messages that we have for the * installation in formatted form. */ public void notifyListeners(Integer ratio, String currentPhaseSummary, String newLogDetail) { ProgressUpdateEvent ev = new ProgressUpdateEvent(getCurrentProgressStep(), ratio, currentPhaseSummary, newLogDetail); for (ProgressUpdateListener l : listeners) { l.progressUpdate(ev); } } /** * This method notifies the ProgressUpdateListeners that there was an * update in the installation progress. * @param ratio the integer that specifies which percentage of * the whole installation has been completed. * @param currentPhaseSummary the localized summary message for the * current installation progress in formatted form. */ public void notifyListeners(Integer ratio, String currentPhaseSummary) { notifyListeners(ratio, getSummary(getCurrentProgressStep()), formatter.getFormattedWithPoints(currentPhaseSummary)); } /** * Returns a localized message for a key value. In the properties file we * have something of type: * key=value * * @see org.opends.quicksetup.i18n.ResourceProvider#getMsg(String) * @param key the key in the properties file. * @return the value associated to the key in the properties file. * properties file. */ public String getMsg(String key) { return getI18n().getMsg(key); } /** * Returns a localized message for a key value. In the properties file we * have something of type: * key=value * * For instance if we pass as key "mykey" and as arguments {"value1"} and * in the properties file we have: * mykey=value with argument {0}. * * This method will return "value with argument value1". * @see org.opends.quicksetup.i18n.ResourceProvider#getMsg(String, String[]) * @param key the key in the properties file. * @param args the arguments to be passed to generate the resulting value. * @return the value associated to the key in the properties file. */ public String getMsg(String key, String... args) { return getI18n().getMsg(key, args); } /** * Returns a ResourceProvider instance. * @return a ResourceProvider instance. */ protected ResourceProvider getI18n() { return ResourceProvider.getInstance(); } /** * Returns a localized message for a given properties key and throwable. * @param key the key of the message in the properties file. * @param t the throwable for which we want to get a message. * @return a localized message for a given properties key and throwable. */ public String getThrowableMsg(String key, Throwable t) { return getThrowableMsg(key, null, t); } /** * Returns a localized message for a given properties key and throwable. * @param key the key of the message in the properties file. * @param args the arguments of the message in the properties file. * @param t the throwable for which we want to get a message. * * @return a localized message for a given properties key and throwable. */ protected String getThrowableMsg(String key, String[] args, Throwable t) { return Utils.getThrowableMsg(getI18n(), key, args, t); } /** * Sets the formatter this instance should use to used * to format progress messages. * @param formatter ProgressMessageFormatter for formatting * progress messages */ public void setProgressMessageFormatter(ProgressMessageFormatter formatter) { this.formatter = formatter; } /** * Gets the formatter this instance is currently using. * @return the progress message formatter currently used by this * application */ public ProgressMessageFormatter getProgressMessageFormatter() { return formatter; } /** * Returns the formatted representation of the text that is the summary of the * installation process (the one that goes in the UI next to the progress * bar). * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of an error for the given text. */ protected String getFormattedSummary(String text) { return formatter.getFormattedSummary(text); } /** * Returns the formatted representation of an error for a given text. * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of an error for the given text. */ protected String getFormattedError(String text) { return formatter.getFormattedError(text, false); } /** * Returns the formatted representation of an warning for a given text. * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of an warning for the given text. */ public String getFormattedWarning(String text) { return formatter.getFormattedWarning(text, false); } /** * Returns the formatted representation of a success message for a given text. * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of an success message for the given * text. */ protected String getFormattedSuccess(String text) { return formatter.getFormattedSuccess(text); } /** * Returns the formatted representation of a log error message for a given * text. * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of a log error message for the given * text. */ public String getFormattedLogError(String text) { return formatter.getFormattedLogError(text); } /** * Returns the formatted representation of a log message for a given text. * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of a log message for the given text. */ public String getFormattedLog(String text) { return formatter.getFormattedLog(text); } /** * Returns the formatted representation of the 'Done' text string. * @return the formatted representation of the 'Done' text string. */ public String getFormattedDone() { return formatter.getFormattedDone(); } /** * Returns the formatted representation of the argument text to which we add * points. For instance if we pass as argument 'Configuring Server' the * return value will be 'Configuring Server .....'. * @param text the String to which add points. * @return the formatted representation of the '.....' text string. */ public String getFormattedWithPoints(String text) { return formatter.getFormattedWithPoints(text); } /** * Returns the formatted representation of a progress message for a given * text. * @param text the source text from which we want to get the formatted * representation * @return the formatted representation of a progress message for the given * text. */ public String getFormattedProgress(String text) { return formatter.getFormattedProgress(text); } /** * Returns the formatted representation of an error message for a given * exception. * This method applies a margin if the applyMargin parameter is * true. * @param t the exception. * @param applyMargin specifies whether we apply a margin or not to the * resulting formatted text. * @return the formatted representation of an error message for the given * exception. */ protected String getFormattedError(Throwable t, boolean applyMargin) { return formatter.getFormattedError(t, applyMargin); } /** * Returns the line break formatted. * @return the line break formatted. */ public String getLineBreak() { return formatter.getLineBreak(); } /** * Returns the task separator formatted. * @return the task separator formatted. */ protected String getTaskSeparator() { return formatter.getTaskSeparator(); } /** * This method is called when a new log message has been received. It will * notify the ProgressUpdateListeners of this fact. * @param newLogDetail the new log detail. */ public void notifyListeners(String newLogDetail) { Integer ratio = getRatio(getCurrentProgressStep()); String currentPhaseSummary = getSummary(getCurrentProgressStep()); notifyListeners(ratio, currentPhaseSummary, newLogDetail); } /** * Returns the installation path. * @return the installation path. */ public abstract String getInstallationPath(); /** * Gets the current step. * @return ProgressStep representing the current step */ public abstract ProgressStep getCurrentProgressStep(); /** * Gets an integer representing the amount of processing * this application still needs to perform as a ratio * out of 100. * @param step ProgressStop for which a summary is needed * @return ProgressStep representing the current step */ public abstract Integer getRatio(ProgressStep step); /** * Gets an i18n'd string representing the summary of * a give ProgressStep. * @param step ProgressStop for which a summary is needed * @return String representing the summary */ public abstract String getSummary(ProgressStep step); /** * Sets the current install status for this application. * @param installStatus for the current installation. */ public void setCurrentInstallStatus(CurrentInstallStatus installStatus) { this.installStatus = installStatus; } /** * Returns whether the installer has finished or not. * @return true if the install is finished or false * if not. */ abstract public boolean isFinished(); /** * Returns the trust manager that can be used to establish secure connections. * @return the trust manager that can be used to establish secure connections. */ protected ApplicationTrustManager getTrustManager() { if (trustManager == null) { trustManager = new ApplicationTrustManager(null); } return trustManager; } /** * Indicates whether or not this application is capable of cancelling * the operation performed in the run method. A cancellable operation * should leave its environment in the same state as it was prior to * running the operation (files deleted, changes backed out etc.). * * Marking an Application as cancellable may control UI * elements like the presense of a cancel button while the operation * is being performed. * * Applications marked as cancellable should override the * cancel method in such a way as to undo whatever * actions have taken place in the run method up to that point. * * @return boolean where true inidcates that the operation is cancellable */ abstract public boolean isCancellable(); /** * Signals that the application should cancel a currently running * operation as soon as possible and return the environment to the * state prior to running the operation. When finished backing * out changes the application should make sure that isFinished * returns true so that the application can complete. */ abstract public void cancel(); /** * Makes available a UserInteraction class that can be used * by the application to interact with the user. If the user has requested * a silent session this method returns null. * @return UserInteraction object */ protected UserInteraction userInteraction() { // Note: overridden in GuiApplication UserInteraction ui = null; if (!getUserData().isSilent()) { ui = new CliUserInteraction(); } return ui; } static private String getMessage(String key, String... args) { return ResourceProvider.getInstance().getMsg(key, args); } /** * This class is used to notify the ProgressUpdateListeners of events * that are written to the standard error. It is used in WebStartInstaller * and in OfflineInstaller. These classes just create a ErrorPrintStream and * then they do a call to System.err with it. * * The class just reads what is written to the standard error, obtains an * formatted representation of it and then notifies the * ProgressUpdateListeners with the formatted messages. * */ protected class ErrorPrintStream extends PrintStream { private boolean isFirstLine; /** * Default constructor. * */ public ErrorPrintStream() { super(new ByteArrayOutputStream(), true); isFirstLine = true; } /** * {@inheritDoc} */ public void println(String msg) { if (isFirstLine) { notifyListeners(getFormattedLogError(msg)); } else { notifyListeners(formatter.getLineBreak() + getFormattedLogError(msg)); } isFirstLine = false; } /** * {@inheritDoc} */ public void write(byte[] b, int off, int len) { if (b == null) { throw new NullPointerException("b is null"); } if (off + len > b.length) { throw new IndexOutOfBoundsException( "len + off are bigger than the length of the byte array"); } println(new String(b, off, len)); } } /** * This class is used to notify the ProgressUpdateListeners of events * that are written to the standard output. It is used in WebStartInstaller * and in OfflineInstaller. These classes just create a OutputPrintStream and * then they do a call to System.out with it. * * The class just reads what is written to the standard output, obtains an * formatted representation of it and then notifies the * ProgressUpdateListeners with the formatted messages. * */ protected class OutputPrintStream extends PrintStream { private boolean isFirstLine; /** * Default constructor. * */ public OutputPrintStream() { super(new ByteArrayOutputStream(), true); isFirstLine = true; } /** * {@inheritDoc} */ public void println(String msg) { if (isFirstLine) { notifyListeners(getFormattedLog(msg)); } else { notifyListeners(formatter.getLineBreak() + getFormattedLog(msg)); } isFirstLine = false; } /** * {@inheritDoc} */ public void write(byte[] b, int off, int len) { if (b == null) { throw new NullPointerException("b is null"); } if (off + len > b.length) { throw new IndexOutOfBoundsException( "len + off are bigger than the length of the byte array"); } println(new String(b, off, len)); } } }