/* * 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 2006-2007 Sun Microsystems, Inc. */ package org.opends.quicksetup.uninstaller; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.FileReader; import java.util.ArrayList; import java.util.Iterator; import java.util.Set; import org.opends.quicksetup.CurrentInstallStatus; import org.opends.quicksetup.event.UninstallProgressUpdateEvent; import org.opends.quicksetup.event.UninstallProgressUpdateListener; import org.opends.quicksetup.i18n.ResourceProvider; import org.opends.quicksetup.util.PlainTextProgressMessageFormatter; import org.opends.quicksetup.util.Utils; /** * The class used to provide some CLI interface in the uninstall. * * This class basically is in charge of parsing the data provided by the user * in the command line and displaying messages asking the user for information. * * Once the user has provided all the required information it calls Uninstaller * and launches it. * */ class UninstallCli { /** * Return code: Uninstall successful. */ static int SUCCESSFUL = 0; /** * Return code: User Cancelled uninstall. */ static int CANCELLED = 1; /** * Return code: User provided invalid data. */ static int USER_DATA_ERROR = 2; /** * Return code: Error accessing file system (reading/writing). */ static int ERROR_ACCESSING_FILE_SYSTEM = 3; /** * Return code: Error stopping server. */ static int ERROR_STOPPING_SERVER = 4; /** * Return code: Bug. */ static int BUG = 5; private static String LINE_SEPARATOR = System.getProperty("line.separator"); private String[] args; /** * The constructor for this object. * @param args the arguments of the uninstall command line. */ UninstallCli(String[] args) { this.args = args; } /** * Parses the user data and prompts the user for data if required. If the * user provides all the required data it launches the Uninstaller. * * @return the return code (SUCCESSFUL, CANCELLED, USER_DATA_ERROR, * ERROR_ACCESSING_FILE_SYSTEM, ERROR_STOPPING_SERVER or BUG. */ int run() { int returnValue; System.out.println(getMsg("uninstall-launcher-launching-cli")); // Parse the arguments try { CurrentInstallStatus installStatus = new CurrentInstallStatus(); UserUninstallData userData = getUserUninstallData(args, installStatus); if (userData != null) { Uninstaller uninstaller = new Uninstaller(userData, new PlainTextProgressMessageFormatter()); uninstaller.addProgressUpdateListener( new UninstallProgressUpdateListener() { /** * UninstallProgressUpdateListener implementation. * @param ev the UninstallProgressUpdateEvent we receive. * */ public void progressUpdate(UninstallProgressUpdateEvent ev) { System.out.print( org.opends.server.util.StaticUtils.wrapText(ev.getNewLogs(), Utils.getCommandLineMaxLineWidth())); } }); uninstaller.start(); while (!uninstaller.isFinished()) { try { Thread.sleep(100); } catch (Exception ex) { } } UninstallException ue = uninstaller.getException(); if (ue != null) { switch (ue.getType()) { case FILE_SYSTEM_ERROR: returnValue = ERROR_ACCESSING_FILE_SYSTEM; break; case STOP_ERROR: returnValue = ERROR_STOPPING_SERVER; break; case BUG: returnValue = BUG; break; default: throw new IllegalStateException( "Unknown UninstallException type: "+ue.getType()); } } else { returnValue = SUCCESSFUL; } } else { // User cancelled installation. returnValue = CANCELLED; } } catch (UserUninstallDataException uude) { System.err.println(LINE_SEPARATOR+uude.getLocalizedMessage()+ LINE_SEPARATOR); returnValue = USER_DATA_ERROR; } return returnValue; } /** * Creates a UserUninstallData based in the arguments provided. It asks * user for additional information if what is provided in the arguments is not * enough. * @param args the arguments provided in the command line. * @param installStatus the current install status. * @return the UserUninstallData object with what the user wants to uninstall * and null if the user cancels the uninstallation. * @throws UserUninstallDataException if there is an error parsing the data * in the arguments. */ private UserUninstallData getUserUninstallData(String[] args, CurrentInstallStatus installStatus) throws UserUninstallDataException { UserUninstallData userData = new UserUninstallData(); boolean silentUninstall = false; boolean isCancelled = false; /* Step 1: validate the arguments */ validateArguments(userData, args); silentUninstall = isSilent(args); /* Step 2: If this is not a silent install ask for confirmation to delete * the different parts of the installation */ Set outsideDbs = getOutsideDbs(installStatus); Set outsideLogs = getOutsideLogs(installStatus); if (silentUninstall) { userData.setRemoveBackups(true); userData.setRemoveConfigurationAndSchema(true); userData.setRemoveDatabases(true); userData.setRemoveLDIFs(true); userData.setRemoveLibrariesAndTools(true); userData.setRemoveLogs(true); userData.setExternalDbsToRemove(outsideDbs); userData.setExternalLogsToRemove(outsideLogs); } else { isCancelled = askWhatToDelete(userData, outsideDbs, outsideLogs); } /* * Step 3: check if server is running. Depending if it is running and the * OS we are running, ask for authentication information. */ if (!isCancelled) { isCancelled = askConfirmationToStop(userData, installStatus, silentUninstall); } if (isCancelled) { userData = null; } return userData; } /** * Interactively prompts (on standard output) the user to provide a string * value. Any non-empty string will be allowed (the empty string will * indicate that the default should be used). The method will display the * message until the user provides one of the values in the validValues * parameter. * * @param prompt The prompt to present to the user. * @param defaultValue The default value returned if the user clicks enter. * @param validValues The valid values that can be accepted as user input. * * @return The string value read from the user. */ private String promptConfirm(String prompt, String defaultValue, String[] validValues) { System.out.println(); boolean isValid = false; String response = null; while (!isValid) { String msg = getMsg("cli-uninstall-confirm-prompt", new String[] {prompt, defaultValue}); System.out.print(msg); System.out.flush(); response = readLine(); if (response.equals("")) { response = defaultValue; } for (int i=0; inull * if the end of the stream is reached or an error occurs while * attempting to read the response. */ private String readLine() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); while (true) { int b = System.in.read(); if ((b < 0) || (b == '\n')) { break; } else if (b == '\r') { int b2 = System.in.read(); if (b2 == '\n') { break; } else { baos.write(b); baos.write(b2); } } else { baos.write(b); } } return new String(baos.toByteArray(), "UTF-8"); } catch (Exception e) { System.err.println(getMsg("cli-uninstall-error-reading-stdin")); return null; } } /** * Returns a Set of relative paths containing the db paths outside the * installation. * @param installStatus the Current Install Status object. * @return a Set of relative paths containing the db paths outside the * installation. */ private Set getOutsideDbs(CurrentInstallStatus installStatus) { return Utils.getOutsideDbs(installStatus); } /** * Returns a Set of relative paths containing the log paths outside the * installation. * @param installStatus the Current Install Status object. * @return a Set of relative paths containing the log paths outside the * installation. */ private Set getOutsideLogs(CurrentInstallStatus installStatus) { return Utils.getOutsideLogs(installStatus); } /** * Commodity method used to ask the user to confirm the deletion of certain * parts of the server. It updates the provided UserUninstallData object * accordingly. Returns true if the user cancels and * false otherwise. * @param userData the UserUninstallData object to be updated. * @param outsideDbs the set of relative paths of databases located outside * the installation path of the server. * @param outsideLogs the set of relative paths of log files located outside * the installation path of the server. * @returns true if the user cancels and false * otherwise. */ private boolean askWhatToDelete(UserUninstallData userData, Set outsideDbs, Set outsideLogs) { boolean cancelled = false; String answer = promptConfirm(getMsg("cli-uninstall-what-to-delete"), "1", new String[] {"1", "2", "3"}); if ("3".equals(answer)) { cancelled = true; } else if ("1".equals(answer)) { userData.setRemoveBackups(true); userData.setRemoveConfigurationAndSchema(true); userData.setRemoveDatabases(true); userData.setRemoveLDIFs(true); userData.setRemoveLibrariesAndTools(true); userData.setRemoveLogs(true); userData.setExternalDbsToRemove(outsideDbs); userData.setExternalLogsToRemove(outsideLogs); } else { boolean somethingSelected = false; while (!somethingSelected) { // Ask for confirmation for the different items String[] keys = { "cli-uninstall-confirm-libraries-binaries", "cli-uninstall-confirm-databases", "cli-uninstall-confirm-logs", "cli-uninstall-confirm-configuration-schema", "cli-uninstall-confirm-backups", "cli-uninstall-confirm-ldifs", "cli-uninstall-confirm-outsidedbs", "cli-uninstall-confirm-outsidelogs" }; String[] validValues = { getMsg("cli-uninstall-yes-long"), getMsg("cli-uninstall-no-long"), getMsg("cli-uninstall-yes-short"), getMsg("cli-uninstall-no-short") }; boolean[] answers = new boolean[keys.length]; for (int i=0; itrue if the user wants to continue with uninstall and * false otherwise. * @throws UserUninstallDataException if there is a problem with the data * provided by the user (in the particular case where we are on silent * uninstall and some data is missing or not valid). */ private boolean askConfirmationToStop(UserUninstallData userData, CurrentInstallStatus installStatus, boolean silentUninstall) throws UserUninstallDataException { boolean cancelled = false; String errorMsg = null; if (installStatus.isServerRunning()) { if (Utils.isWindows()) { if (silentUninstall) { String dn = userData.getDirectoryManagerDn(); String pwd = userData.getDirectoryManagerPwd(); if ((dn == null) || (pwd == null)) { errorMsg = getMsg("cli-uninstall-data-missing-to-shutdown-server"); } else { if (!canConnectAsAdministrativeUser(installStatus.getLdapUrl(), userData.getDirectoryManagerDn(), userData.getDirectoryManagerPwd())) { errorMsg = getErrorMsgConnecting(dn, pwd, installStatus); } } if (errorMsg != null) { throw new UserUninstallDataException(null, errorMsg); } } else { /* Ask for the Directory Manager Dn and password if they were not * provided. */ boolean askForDn = userData.getDirectoryManagerDn() == null; boolean askForPwd = userData.getDirectoryManagerPwd() == null; boolean prompted = false; if (!askForDn && !askForPwd) { String dn = userData.getDirectoryManagerDn(); String pwd = userData.getDirectoryManagerPwd(); if (!canConnectAsAdministrativeUser(installStatus.getLdapUrl(), dn, pwd)) { System.out.println(LINE_SEPARATOR+LINE_SEPARATOR+getMsg( "cli-uninstall-stop-authentication-generic-prompt1")); System.out.println(getErrorMsgConnecting(dn, pwd, installStatus)); askForDn = true; askForPwd = true; } else { String[] validValues = { getMsg("cli-uninstall-yes-short"), getMsg("cli-uninstall-no-short"), getMsg("cli-uninstall-yes-long"), getMsg("cli-uninstall-no-long") }; String answer = promptConfirm( getMsg("cli-uninstall-confirm-stop"), getMsg("cli-uninstall-yes-long"), validValues); if (getMsg("cli-uninstall-no-short").equalsIgnoreCase(answer) || getMsg("cli-uninstall-no-long").equalsIgnoreCase(answer)) { cancelled = true; } } } else { System.out.println( getMsg("cli-uninstall-stop-authentication-generic-prompt1")); } while (askForDn || askForPwd) { if (!prompted) { System.out.println( getMsg("cli-uninstall-stop-authentication-generic-prompt2")); } if (askForDn) { String defaultDn = userData.getDirectoryManagerDn(); if ((defaultDn == null) || !Utils.isDn(defaultDn)) { Set dns = installStatus.getDirectoryManagerDns(); if (dns.size() > 0) { defaultDn = dns.iterator().next(); } else { defaultDn = "cn=Directory Manager"; } } userData.setDirectoryManagerDn( promptForString(getMsg("cli-uninstall-prompt-dn"), defaultDn)); prompted = true; } if (askForPwd) { userData.setDirectoryManagerPwd(promptForPassword( getMsg("cli-uninstall-prompt-pwd"))); prompted = true; } String dn = userData.getDirectoryManagerDn(); String pwd = userData.getDirectoryManagerPwd(); if (!canConnectAsAdministrativeUser(installStatus.getLdapUrl(), dn, pwd)) { if (installStatus.isServerRunning()) { System.out.println(LINE_SEPARATOR+getErrorMsgConnecting(dn, pwd, installStatus)); askForDn = true; askForPwd = true; } else { /* The server was stopped while we asked the user to provide * authentication. Inform of this and return. */ System.out.println( getMsg("cli-uninstall-server-stopped")); askForDn = false; askForPwd = false; /* Ask for confirmation to delete files */ cancelled = !confirmDeleteFiles(); } } else { askForDn = false; askForPwd = false; /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } } } } else { if (!silentUninstall) { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } } if (!cancelled) { /* During all the confirmations, the server might be stopped. */ userData.setStopServer(installStatus.isServerRunning()); } } else { userData.setStopServer(false); if (!silentUninstall) { /* Ask for confirmation to delete files */ cancelled = !confirmDeleteFiles(); } } return cancelled; } /** * Commodity method providing a localized message when we cannot connect to * the server. * @param dn the DN used to connect to the server. * @param pwd the password used to connect to the server. * @param installStatus the CurrentInstallStatus object describing the * status of the installation. * @return a localized message when we cannot connect to the server. */ private String getErrorMsgConnecting(String dn, String pwd, CurrentInstallStatus installStatus) { String msg; ArrayList possibleCauses = new ArrayList(); if ("".equals(dn.trim())) { possibleCauses.add(getMsg("empty-directory-manager-dn")); } else if (!Utils.isDn(dn)) { possibleCauses.add(getMsg("not-a-directory-manager-dn")); } else { boolean found = false; Iterator it = installStatus.getDirectoryManagerDns().iterator(); while (it.hasNext() && !found) { found = Utils.areDnsEqual(dn, it.next()); } if (!found) { possibleCauses.add(getMsg("not-a-directory-manager-in-config")); } } if ("".equals(pwd)) { possibleCauses.add(getMsg("empty-pwd")); } if (possibleCauses.size() > 0) { // Message with causes String[] arg = { Utils.getStringFromCollection(possibleCauses, "\n") }; msg = getMsg("cli-uninstall-cannot-connect-to-shutdown-with-cause", arg); } else { // Generic message msg = getMsg("cli-uninstall-cannot-connect-to-shutdown-without-cause"); } return msg; } /** * Returns true if this is a silent uninstall and * false otherwise. * @param args the arguments passed in the command line. * @return true if this is a silent uninstall and * false otherwise. */ private boolean isSilent(String[] args) { boolean isSilent = false; for (int i=0; i errors = new ArrayList(); for (int i=0; i= args.length) { errors.add(getMsg("cli-uninstall-root-user-dn-not-provided")); } else { if (args[i+1].indexOf("-") == 0) { errors.add(getMsg("cli-uninstall-root-user-dn-not-provided")); } else { userData.setDirectoryManagerDn(args[i+1]); i++; } } } else if (Utils.isWindows() && (args[i].equals("-w") || args[i].equalsIgnoreCase("--rootUserPassword"))) { if (i+1 >= args.length) { errors.add(getMsg("cli-uninstall-root-user-pwd-not-provided")); } else { if (args[i+1].indexOf("-") == 0) { errors.add(getMsg("cli-uninstall-root-user-pwd-not-provided")); } else { directoryManagerPwd = args[i+1]; i++; } } } else if (Utils.isWindows() && (args[i].equals("-W") || args[i].equalsIgnoreCase("--rootUserPasswordFile"))) { if (i+1 >= args.length) { errors.add(getMsg("cli-uninstall-root-user-pwd-file-not-provided")); } else { if (args[i+1].indexOf("-") == 0) { errors.add(getMsg("cli-uninstall-root-user-pwd-file-not-provided")); } else { directoryManagerPwdFile = args[i+1]; i++; } } } else { String[] arg = {args[i]}; errors.add(getMsg("cli-uninstall-unknown-argument", arg)); } } if ((directoryManagerPwdFile != null) && (directoryManagerPwd != null)) { errors.add(getMsg("cli-uninstall-pwd-and-pwd-file-provided")); } else { String pwd; if (directoryManagerPwdFile != null) { pwd = readPwdFromFile(directoryManagerPwdFile); if (pwd == null) { String[] arg = {directoryManagerPwdFile}; errors.add(getMsg("cli-uninstall-error-reading-pwd-file", arg)); } } else { pwd = directoryManagerPwd; } userData.setDirectoryManagerPwd(pwd); } if (errors.size() > 0) { String msg = Utils.getStringFromCollection(errors, LINE_SEPARATOR+LINE_SEPARATOR); throw new UserUninstallDataException(null, msg); } } /** * Interactively prompts (on standard output) the user to provide a string * value. * * @param prompt The prompt to present to the user. * * @return The string value read from the user. */ private String promptForPassword(String prompt) { char[] password = null; while ((password == null) || (password.length == 0)) { System.out.println(); System.out.print(prompt); System.out.flush(); password = org.opends.server.util.PasswordReader.readPassword(); } return new String(password); } /** * Returns the password stored in a file. Returns null if no * password is found. * @param path the path of the file containing the password. * @return the password stored in a file. Returns null if no * password is found. */ private String readPwdFromFile(String path) { String pwd = null; BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(path)); pwd = reader.readLine(); } catch (Exception e) { } finally { try { if (reader != null) { reader.close(); } } catch (Exception e) {} } return pwd; } /** * Method used to know if we can connect as administrator in a server with a * given password and dn. * @param ldapUrl the ldap URL of the server. * @param dn the dn to be used. * @param pwd the password to be used. * @return true if we can connect and read the configuration and * false otherwise. */ private boolean canConnectAsAdministrativeUser(String ldapUrl, String dn, String pwd) { return Utils.canConnectAsAdministrativeUser(ldapUrl, dn, pwd); } /** * Ask for confirmation to stop server. * @return true if the user wants to continue and stop the * server. false otherwise. */ private boolean confirmToStopServer() { boolean confirm = true; String[] validValues = { getMsg("cli-uninstall-yes-short"), getMsg("cli-uninstall-no-short"), getMsg("cli-uninstall-yes-long"), getMsg("cli-uninstall-no-long") }; String answer = promptConfirm( getMsg("cli-uninstall-confirm-stop"), getMsg("cli-uninstall-yes-long"), validValues); if (getMsg("cli-uninstall-no-short").equalsIgnoreCase(answer) || getMsg("cli-uninstall-no-long").equalsIgnoreCase(answer)) { confirm = false; } return confirm; } /** * Ask for confirmation to delete files. * @return true if the user wants to continue and delete the * files. false otherwise. */ private boolean confirmDeleteFiles() { boolean confirm = true; String[] validValues = { getMsg("cli-uninstall-yes-short"), getMsg("cli-uninstall-no-short"), getMsg("cli-uninstall-yes-long"), getMsg("cli-uninstall-no-long") }; String answer = promptConfirm( getMsg("cli-uninstall-confirm-delete-files"), getMsg("cli-uninstall-yes-long"), validValues); if (getMsg("cli-uninstall-no-short").equalsIgnoreCase(answer) || getMsg("cli-uninstall-no-long").equalsIgnoreCase(answer)) { confirm = false; } return confirm; } /** * The following three methods are just commodity methods to get localized * messages. */ private static String getMsg(String key) { return org.opends.server.util.StaticUtils.wrapText(getI18n().getMsg(key), Utils.getCommandLineMaxLineWidth()); } private static String getMsg(String key, String[] args) { return org.opends.server.util.StaticUtils.wrapText( getI18n().getMsg(key, args), Utils.getCommandLineMaxLineWidth()); } private static ResourceProvider getI18n() { return ResourceProvider.getInstance(); } }