/* * 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.guitools.uninstaller; import org.opends.server.admin.client.cli.DsFrameworkCliReturnCode; import org.opends.admin.ads.ADSContext; import org.opends.admin.ads.ServerDescriptor; import org.opends.admin.ads.TopologyCache; import org.opends.admin.ads.TopologyCacheException; import org.opends.admin.ads.util.ApplicationTrustManager; import org.opends.guitools.statuspanel.ConfigFromFile; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import static org.opends.messages.AdminToolMessages.*; import static org.opends.messages.QuickSetupMessages.*; import org.opends.quicksetup.*; import org.opends.quicksetup.event.ProgressUpdateEvent; import org.opends.quicksetup.event.ProgressUpdateListener; import org.opends.quicksetup.util.PlainTextProgressMessageFormatter; import org.opends.quicksetup.util.ServerController; import org.opends.quicksetup.util.Utils; import org.opends.server.util.args.ArgumentException; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import java.io.IOException; import javax.naming.NamingException; import javax.naming.ldap.InitialLdapContext; /** * 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 UninstallCliHelper extends CliApplicationHelper { static private final Logger LOG = Logger.getLogger(UninstallCliHelper.class.getName()); private UninstallerArgumentParser parser; /** * Creates a UserData based in the arguments provided. It asks * user for additional information if what is provided in the arguments is not * enough. * @param args the ArgumentParser with the allowed arguments of the command * line. * @param rawArguments the arguments provided in the command line. * @return the UserData object with what the user wants to uninstall * and null if the user cancels the uninstallation. * @throws UserDataException if there is an error parsing the data * in the arguments. */ public UninstallUserData createUserData(UninstallerArgumentParser args, String[] rawArguments) throws UserDataException { parser = args; UninstallUserData userData = new UninstallUserData(); boolean isInteractive; boolean isQuiet; boolean isCancelled = false; /* Step 1: analyze the arguments. */ try { args.parseArguments(rawArguments); } catch (ArgumentException ae) { throw new UserDataException(null, ae.getMessageObject()); } isInteractive = args.isInteractive(); isQuiet = args.isQuiet(); userData.setQuiet(isQuiet); userData.setForceOnError(args.isForceOnError()); userData.setTrustManager(args.getTrustManager()); /* Step 2: check that the provided parameters are compatible. */ MessageBuilder buf = new MessageBuilder(); int v = args.validateGlobalOptions(buf); if (v != DsFrameworkCliReturnCode.SUCCESSFUL_NOP.getReturnCode()) { throw new UserDataException(null, buf.toMessage()); } /* Step 3: If this is an interactive uninstall ask for confirmation to * delete the different parts of the installation if the user did not * specify anything to delete. If we are not in interactive mode * check that the user specified something to be deleted. */ Set outsideDbs; Set outsideLogs; Configuration config = Installation.getLocal().getCurrentConfiguration(); try { outsideDbs = config.getOutsideDbs(); } catch (IOException ioe) { outsideDbs = Collections.emptySet(); LOG.log(Level.INFO, "error determining outside databases", ioe); } try { outsideLogs = config.getOutsideLogs(); } catch (IOException ioe) { outsideLogs = Collections.emptySet(); LOG.log(Level.INFO, "error determining outside logs", ioe); } boolean somethingSpecifiedToDelete = args.removeAll() || args.removeBackupFiles() || args.removeDatabases() || args.removeLDIFFiles() || args.removeConfigurationFiles() || args.removeLogFiles() || args.removeServerLibraries(); if (somethingSpecifiedToDelete) { userData.setRemoveBackups(args.removeAll() || args.removeBackupFiles()); userData.setRemoveConfigurationAndSchema(args.removeAll() || args.removeConfigurationFiles()); userData.setRemoveDatabases(args.removeAll() || args.removeDatabases()); userData.setRemoveLDIFs(args.removeAll() || args.removeLDIFFiles()); userData.setRemoveLibrariesAndTools(args.removeAll() || args.removeServerLibraries()); userData.setRemoveLogs(args.removeAll() || args.removeLogFiles()); userData.setExternalDbsToRemove(outsideDbs); userData.setExternalLogsToRemove(outsideLogs); } else { if (!isInteractive) { throw new UserDataException(null, ERR_CLI_UNINSTALL_NOTHING_TO_BE_UNINSTALLED_NON_INTERACTIVE.get()); } else { isCancelled = askWhatToDelete(userData, outsideDbs, outsideLogs); } } String adminUid = args.getAdministratorUID(); if ((adminUid == null) && !args.isInteractive()) { adminUid = args.getDefaultAdministratorUID(); } userData.setAdminUID(adminUid); userData.setAdminPwd(args.getBindPassword()); String referencedHostName = args.getReferencedHostName(); if ((referencedHostName == null) && !args.isInteractive()) { referencedHostName = args.getDefaultReferencedHostName(); } userData.setReferencedHostName(referencedHostName); /* * Step 4: check if server is running. Depending if it is running and the * OS we are running, ask for authentication information. */ if (!isCancelled) { isCancelled = checkServerState(userData, isInteractive); } if (isCancelled && !userData.isForceOnError()) { userData = null; } return userData; } /** * Commodity method used to ask the user to confirm the deletion of certain * parts of the server. It updates the provided UserData object * accordingly. Returns true if the user cancels and * false otherwise. * @param userData the UserData 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. * @return true if the user cancels and false * otherwise. */ private boolean askWhatToDelete(UninstallUserData userData, Set outsideDbs, Set outsideLogs) { boolean cancelled = false; Message[] options = new Message[] { Message.raw("1"), Message.raw("2"), Message.raw("3") }; Message answer = promptConfirm( INFO_CLI_UNINSTALL_WHAT_TO_DELETE.get(), options[0], options); if (options[2].toString().equals(answer.toString())) { cancelled = true; } else if (options[0].toString().equals(answer.toString())) { 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 Message[] keys = { INFO_CLI_UNINSTALL_CONFIRM_LIBRARIES_BINARIES.get(), INFO_CLI_UNINSTALL_CONFIRM_DATABASES.get(), INFO_CLI_UNINSTALL_CONFIRM_LOGS.get(), INFO_CLI_UNINSTALL_CONFIRM_CONFIGURATION_SCHEMA.get(), INFO_CLI_UNINSTALL_CONFIRM_BACKUPS.get(), INFO_CLI_UNINSTALL_CONFIRM_LDIFS.get(), INFO_CLI_UNINSTALL_CONFIRM_OUTSIDEDBS.get( Utils.getStringFromCollection(outsideDbs, Constants.LINE_SEPARATOR)), INFO_CLI_UNINSTALL_CONFIRM_OUTSIDELOGS.get( Utils.getStringFromCollection(outsideLogs, Constants.LINE_SEPARATOR) ) }; Message[] validValues = { INFO_CLI_YES_LONG.get(), INFO_CLI_NO_LONG.get(), INFO_CLI_YES_SHORT.get(), INFO_CLI_NO_SHORT.get() }; boolean[] answers = new boolean[keys.length]; for (int i=0; itrue if the user wants to continue with uninstall and * false otherwise. * @throws UserDataException if there is a problem with the data * provided by the user (in the particular case where we are on quiet * uninstall and some data is missing or not valid). */ private boolean checkServerState(UninstallUserData userData, boolean interactive) throws UserDataException { boolean cancelled = false; UninstallData conf = null; try { conf = new UninstallData(Installation.getLocal()); } catch (Throwable t) { LOG.log(Level.WARNING, "Error processing task: "+t, t); throw new UserDataException(Step.CONFIRM_UNINSTALL, Utils.getThrowableMsg(INFO_BUG_MSG.get(), t)); } if (conf.isADS() && conf.isReplicationServer()) { if (conf.isServerRunning()) { if (interactive) { if (confirmToUpdateRemote()) { cancelled = !askForAuthenticationIfNeeded(userData); if (cancelled) { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } else { cancelled = !updateUserUninstallDataWithRemoteServers(userData, interactive); if (cancelled) { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } } } else { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } } else { cancelled = !updateUserUninstallDataWithRemoteServers(userData, interactive); } } else { if (interactive) { if (confirmToUpdateRemoteAndStart()) { boolean startWorked = startServer(userData.isQuiet()); // Ask for authentication if needed, etc. if (startWorked) { cancelled = !askForAuthenticationIfNeeded(userData); if (cancelled) { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } else { cancelled = !updateUserUninstallDataWithRemoteServers(userData, interactive); if (cancelled) { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } } } else { userData.setStopServer(false); if (interactive) { /* Ask for confirmation to delete files */ cancelled = !confirmDeleteFiles(); } } } else { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } } else { boolean startWorked = startServer(userData.isQuiet()); // Ask for authentication if needed, etc. if (startWorked) { userData.setStopServer(true); cancelled = !updateUserUninstallDataWithRemoteServers(userData, interactive); } else { cancelled = !userData.isForceOnError(); userData.setStopServer(false); } } } } else { if (conf.isServerRunning()) { if (interactive) { /* Ask for confirmation to stop server */ cancelled = !confirmToStopServer(); } if (!cancelled) { /* During all the confirmations, the server might be stopped. */ userData.setStopServer( Installation.getLocal().getStatus().isServerRunning()); } } else { userData.setStopServer(false); if (interactive) { /* Ask for confirmation to delete files */ cancelled = !confirmDeleteFiles(); } } } return cancelled; } /** * Ask for confirmation to stop server. * @return true if the user wants to continue and stop the * server. false otherwise. */ private boolean confirmToStopServer() { return confirm(INFO_CLI_UNINSTALL_CONFIRM_STOP.get()); } /** * Ask for confirmation to delete files. * @return true if the user wants to continue and delete the * files. false otherwise. */ private boolean confirmDeleteFiles() { return confirm(INFO_CLI_UNINSTALL_CONFIRM_DELETE_FILES.get()); } private boolean confirm(Message msg) { boolean confirm = true; Message[] validValues = { INFO_CLI_YES_SHORT.get(), INFO_CLI_NO_SHORT.get(), INFO_CLI_YES_LONG.get(), INFO_CLI_NO_LONG.get(), }; Message answer = promptConfirm(msg, validValues[2], validValues); if (INFO_CLI_NO_SHORT.get().toString() .equalsIgnoreCase(answer.toString()) || INFO_CLI_NO_LONG.get().toString() .equalsIgnoreCase(answer.toString())) { confirm = false; } return confirm; } /** * Ask for confirmation to update configuration on remote servers. * @return true if the user wants to continue and stop the * server. false otherwise. */ private boolean confirmToUpdateRemote() { return confirm(INFO_CLI_UNINSTALL_CONFIRM_UPDATE_REMOTE.get()); } /** * Ask for confirmation to update configuration on remote servers. * @return true if the user wants to continue and stop the * server. false otherwise. */ private boolean confirmToUpdateRemoteAndStart() { return confirm(INFO_CLI_UNINSTALL_CONFIRM_UPDATE_REMOTE_AND_START.get()); } /** * Ask for confirmation to provide again authentication. * @return true if the user wants to provide authentication * againr. false otherwise. */ private boolean promptToProvideAuthenticationAgain() { return confirm(INFO_UNINSTALL_CONFIRM_PROVIDE_AUTHENTICATION_AGAIN.get()); } /** * Ask for data required to update configuration on remote servers. If * all the data is provided and validated, we assume that the user wants * to update the remote servers. * @return true if the user wants to continue and update the * remote servers. false otherwise. */ private boolean askForAuthenticationIfNeeded(UninstallUserData userData) { boolean accepted = true; String uid = userData.getAdminUID(); String pwd = userData.getAdminPwd(); boolean couldConnect = false; ConfigFromFile conf = new ConfigFromFile(); conf.readConfiguration(); String ldapUrl = conf.getLDAPURL(); String startTlsUrl = conf.getStartTLSURL(); String ldapsUrl = conf.getLDAPSURL(); while (!couldConnect && accepted) { while (uid == null) { uid = askForAdministratorUID(); } while (pwd == null) { pwd = askForAdministratorPwd(); } userData.setAdminUID(uid); userData.setAdminPwd(pwd); InitialLdapContext ctx = null; String usedUrl = null; try { String dn = ADSContext.getAdministratorDN(uid); if ((ldapsUrl != null) && (parser.useSSL() || !parser.startTLS())) { usedUrl = ldapsUrl; ctx = Utils.createLdapsContext(ldapsUrl, dn, pwd, Utils.getDefaultLDAPTimeout(), null, userData.getTrustManager()); } else if ((startTlsUrl != null) && (!parser.useSSL() || parser.startTLS())) { usedUrl = startTlsUrl; ctx = Utils.createStartTLSContext(startTlsUrl, dn, pwd, Utils.getDefaultLDAPTimeout(), null, userData.getTrustManager(), null); } else if ((ldapUrl != null) && !parser.useSSL() && !parser.startTLS()) { usedUrl = ldapUrl; ctx = Utils.createLdapContext(ldapUrl, dn, pwd, Utils.getDefaultLDAPTimeout(), null); } else { LOG.log(Level.WARNING, "Error retrieving a valid LDAP URL in conf file"); printErrorMessage(ERR_COULD_NOT_FIND_VALID_LDAPURL.get()); } if (usedUrl != null) { userData.setLocalServerUrl(usedUrl); couldConnect = true; } } catch (NamingException ne) { LOG.log(Level.WARNING, "Error connecting to server: "+ne, ne); if (Utils.isCertificateException(ne)) { accepted = promptForCertificateConfirmation(ne, userData.getTrustManager(), usedUrl); } else { uid = null; pwd = null; printLineBreak(); printErrorMessage( Utils.getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), ne)); accepted = promptToProvideAuthenticationAgain(); } } catch (Throwable t) { LOG.log(Level.WARNING, "Error connecting to server: "+t, t); uid = null; pwd = null; printErrorMessage(Utils.getThrowableMsg(INFO_BUG_MSG.get(), t)); accepted = promptToProvideAuthenticationAgain(); } finally { if (ctx != null) { try { ctx.close(); } catch (Throwable t) { LOG.log(Level.INFO, "Error closing connection: "+t, t); } } } } if (accepted) { String referencedHostName = userData.getReferencedHostName(); while (referencedHostName == null) { referencedHostName = askForReferencedHostName(userData.getHostName()); } userData.setReferencedHostName(referencedHostName); } userData.setUpdateRemoteReplication(accepted); return accepted; } private String askForAdministratorUID() { return promptForString(INFO_UNINSTALL_CLI_ADMINISTRATOR_UID_PROMPT.get(), Constants.GLOBAL_ADMIN_UID); } private String askForAdministratorPwd() { return promptForPassword(INFO_UNINSTALL_CLI_ADMINISTRATOR_PWD_PROMPT.get()); } private String askForReferencedHostName(String defaultHostName) { return promptForString(INFO_UNINSTALL_CLI_REFERENCED_HOSTNAME_PROMPT.get(), defaultHostName); } private boolean startServer(boolean supressOutput) { boolean serverStarted = false; Application application = new Application() { /** * {@inheritDoc} */ public String getInstallationPath() { return Installation.getLocal().getRootDirectory().getAbsolutePath(); } /** * {@inheritDoc} */ public ProgressStep getCurrentProgressStep() { return UninstallProgressStep.NOT_STARTED; } /** * {@inheritDoc} */ public Integer getRatio(ProgressStep step) { return 0; } /** * {@inheritDoc} */ public Message getSummary(ProgressStep step) { return null; } /** * {@inheritDoc} */ public boolean isFinished() { return false; } /** * {@inheritDoc} */ public boolean isCancellable() { return false; } /** * {@inheritDoc} */ public void cancel() { } /** * {@inheritDoc} */ public void run() { } }; application.setProgressMessageFormatter( new PlainTextProgressMessageFormatter()); if (!supressOutput) { application.addProgressUpdateListener( new ProgressUpdateListener() { public void progressUpdate(ProgressUpdateEvent ev) { System.out.print(ev.getNewLogs().toString()); System.out.flush(); } }); } ServerController controller = new ServerController(application, Installation.getLocal()); try { if (!supressOutput) { printLineBreak(); } controller.startServer(supressOutput); if (!supressOutput) { printLineBreak(); } serverStarted = Installation.getLocal().getStatus().isServerRunning(); } catch (ApplicationException ae) { if (!supressOutput) { printErrorMessage(ae.getMessage()); } } return serverStarted; } /** * Updates the contents of the UninstallUserData while trying to connect * to the remote servers. It returns true if we could connect * to the remote servers and all the presented certificates were accepted and * false otherwise. * continue if * @param userData the user data to be updated. * @param interactive whether we are in interactive mode or not. * @return true if we could connect * to the remote servers and all the presented certificates were accepted and * false otherwise. */ private boolean updateUserUninstallDataWithRemoteServers( UninstallUserData userData, boolean interactive) { boolean accepted = false; InitialLdapContext ctx = null; try { ConfigFromFile conf = new ConfigFromFile(); conf.readConfiguration(); String ldapUrl = conf.getLDAPURL(); String startTlsUrl = conf.getStartTLSURL(); String ldapsUrl = conf.getLDAPSURL(); String adminUid = userData.getAdminUID(); String pwd = userData.getAdminPwd(); String dn = ADSContext.getAdministratorDN(adminUid); if ((ldapsUrl != null) && (parser.useSSL() || !parser.startTLS())) { ctx = Utils.createLdapsContext(ldapsUrl, dn, pwd, Utils.getDefaultLDAPTimeout(), null, userData.getTrustManager()); } else if ((startTlsUrl != null) && (!parser.useSSL() || parser.startTLS())) { ctx = Utils.createStartTLSContext(startTlsUrl, dn, pwd, Utils.getDefaultLDAPTimeout(), null, userData.getTrustManager(), null); } else if ((ldapUrl != null) && !parser.useSSL() && !parser.startTLS()) { ctx = Utils.createLdapContext(ldapUrl, dn, pwd, Utils.getDefaultLDAPTimeout(), null); } else { LOG.log(Level.WARNING, "Error retrieving a valid LDAP URL in conf file"); printErrorMessage(ERR_COULD_NOT_FIND_VALID_LDAPURL.get()); } ADSContext adsContext = new ADSContext(ctx); TopologyCache cache = new TopologyCache(adsContext, userData.getTrustManager()); cache.reloadTopology(); accepted = handleTopologyCache(cache, interactive, userData); userData.setRemoteServers(cache.getServers()); } catch (NamingException ne) { LOG.log(Level.WARNING, "Error connecting to server: "+ne, ne); if (Utils.isCertificateException(ne)) { printLineBreak(); printErrorMessage(INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE.get( ne.getMessage())); } else { printLineBreak(); printErrorMessage( Utils.getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), ne)); } } catch (TopologyCacheException te) { LOG.log(Level.WARNING, "Error connecting to server: "+te, te); printErrorMessage(Utils.getStringRepresentation(te)); } catch (Throwable t) { LOG.log(Level.WARNING, "Error connecting to server: "+t, t); printErrorMessage(Utils.getThrowableMsg(INFO_BUG_MSG.get(), t)); } finally { if (ctx != null) { try { ctx.close(); } catch (Throwable t) { LOG.log(Level.INFO, "Error closing connection: "+t, t); } } } userData.setUpdateRemoteReplication(accepted); return accepted; } /** * Method that interacts with the user depending on what errors where * encountered in the TopologyCache object. This method assumes that the * TopologyCache has been reloaded. * Returns true if the user accepts all the problems encountered * and false otherwise. * @param userData the user data. * @param interactive if we are in interactive mode or not. */ private boolean handleTopologyCache(TopologyCache cache, boolean interactive, UninstallUserData userData) { boolean returnValue; boolean stopProcessing = false; boolean reloadTopologyCache = false; ApplicationTrustManager trustManager = userData.getTrustManager(); Set exceptions = new HashSet(); /* Analyze if we had any exception while loading servers. For the moment * only throw the exception found if the user did not provide the * Administrator DN and this caused a problem authenticating in one server * or if there is a certificate problem. */ Set servers = cache.getServers(); for (ServerDescriptor server : servers) { TopologyCacheException e = server.getLastException(); if (e != null) { exceptions.add(e); } } Set exceptionMsgs = new LinkedHashSet(); /* Check the exceptions and see if we throw them or not. */ for (TopologyCacheException e : exceptions) { if (stopProcessing) { break; } switch (e.getType()) { case NOT_GLOBAL_ADMINISTRATOR: printErrorMessage(INFO_NOT_GLOBAL_ADMINISTRATOR_PROVIDED.get()); stopProcessing = true; break; case GENERIC_CREATING_CONNECTION: if ((e.getCause() != null) && Utils.isCertificateException(e.getCause())) { if (interactive) { if (promptForCertificateConfirmation(e.getCause(), trustManager, e.getLdapUrl())) { stopProcessing = true; reloadTopologyCache = true; } else { stopProcessing = true; } } else { stopProcessing = true; String url = e.getLdapUrl(); int index = url.indexOf("//"); String hostPort = url.substring(index + 2); printErrorMessage( INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER.get( hostPort, e.getCause().getMessage())); } } else { exceptionMsgs.add(Utils.getStringRepresentation(e)); } break; default: exceptionMsgs.add(Utils.getStringRepresentation(e)); } } if (interactive) { if (!stopProcessing && (exceptionMsgs.size() > 0)) { returnValue = confirm( ERR_READING_REGISTERED_SERVERS_CONFIRM_UPDATE_REMOTE.get( Utils.getStringFromCollection(exceptionMsgs, Constants.LINE_SEPARATOR))); } else if (reloadTopologyCache) { returnValue = updateUserUninstallDataWithRemoteServers(userData, interactive); } else { returnValue = !stopProcessing; } } else { if (exceptionMsgs.size() > 0) { printErrorMessage(Utils.getStringFromCollection(exceptionMsgs, Constants.LINE_SEPARATOR)); returnValue = false; } else { returnValue = true; } } return returnValue; } }