/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
*/
package org.opends.guitools.uninstaller;
import org.opends.server.admin.client.cli.DsFrameworkCliReturnCode;
import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
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.controlpanel.datamodel.ConnectionProtocolPolicy;
import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
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.tools.ClientException;
import org.opends.server.tools.ToolConstants;
import org.opends.server.tools.dsconfig.LDAPManagementContextFactory;
import org.opends.server.util.args.ArgumentException;
import org.opends.server.util.cli.CLIException;
import org.opends.server.util.cli.ConsoleApplication;
import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
import org.opends.server.util.cli.Menu;
import org.opends.server.util.cli.MenuBuilder;
import org.opends.server.util.cli.MenuResult;
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.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.net.ssl.TrustManager;
/**
* 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.
*
*/
public class UninstallCliHelper extends ConsoleApplication {
static private final Logger LOG =
Logger.getLogger(UninstallCliHelper.class.getName());
private UninstallerArgumentParser parser;
private boolean forceNonInteractive;
private LDAPConnectionConsoleInteraction ci = null;
private ControlPanelInfo info;
// This CLI is always using the administration connector with SSL
private final boolean alwaysSSL = true;
private boolean useSSL = true;
private boolean useStartTLS = false;
/**
* Default constructor.
*/
public UninstallCliHelper()
{
super(System.in, System.out, System.err);
}
/**
* 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. The code assumes that the arguments have already been parsed.
* @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 with the data
* in the arguments.
* @throws ApplicationException if there is an error processing data in
* non-interactive mode and an error must be thrown (not in force on error
* mode).
*/
public UninstallUserData createUserData(UninstallerArgumentParser args,
String[] rawArguments)
throws UserDataException, ApplicationException
{
parser = args;
UninstallUserData userData = new UninstallUserData();
try
{
boolean isInteractive;
boolean isQuiet;
boolean isVerbose;
boolean isCanceled = false;
/* Step 1: analyze the arguments.
*/
isInteractive = args.isInteractive();
isQuiet = args.isQuiet();
isVerbose = args.isVerbose();
userData.setQuiet(isQuiet);
userData.setVerbose(isVerbose);
userData.setForceOnError(args.isForceOnError());
userData.setTrustManager(args.getTrustManager());
userData.setConnectTimeout(getConnectTimeout());
/*
* 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<String> outsideDbs;
Set<String> 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
{
isCanceled = 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();
}
try
{
UninstallData d = new UninstallData(Installation.getLocal());
userData.setReplicationServer(
referencedHostName+":"+d.getReplicationServerPort());
}
catch (Throwable t)
{
LOG.log(Level.SEVERE, "Could not create UninstallData: "+t, t);
userData.setReplicationServer(
referencedHostName+":8989");
}
info = ControlPanelInfo.getInstance();
info.setTrustManager(userData.getTrustManager());
info.setConnectTimeout(getConnectTimeout());
info.regenerateDescriptor();
info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
String adminConnectorUrl = info.getAdminConnectorURL();
if (adminConnectorUrl == null)
{
LOG.log(Level.WARNING,
"Error retrieving a valid LDAP URL in conf file.");
if (!parser.isInteractive())
{
Message msg = ERR_COULD_NOT_FIND_VALID_LDAPURL.get();
throw new ApplicationException(ReturnCode.APPLICATION_ERROR, msg,
null);
}
}
userData.setLocalServerUrl(adminConnectorUrl);
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 (!isCanceled)
{
isCanceled = checkServerState(userData);
}
if (isCanceled && !userData.isForceOnError())
{
LOG.log(Level.INFO, "User cancelled uninstall.");
userData = null;
}
if ((userData != null) && !args.isQuiet())
{
println();
}
}
catch (Throwable t)
{
LOG.log(Level.WARNING, "Exception: "+t, t);
if (t instanceof UserDataException)
{
throw (UserDataException)t;
}
else if (t instanceof ApplicationException)
{
throw (ApplicationException)t;
}
else
{
throw new IllegalStateException("Unexpected error: "+t, t);
}
}
LOG.log(Level.INFO, "Successfully created user data");
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 <CODE>true</CODE> if the user cancels and <CODE>
* false</CODE> 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 <CODE>true</CODE> if the user cancels and <CODE>false</CODE>
* otherwise.
*/
private boolean askWhatToDelete(UninstallUserData userData,
Set<String> outsideDbs, Set<String> outsideLogs) throws UserDataException
{
boolean cancelled = false;
final int REMOVE_ALL = 1;
final int SPECIFY_TO_REMOVE = 2;
int[] indexes = {REMOVE_ALL, SPECIFY_TO_REMOVE};
Message[] msgs = new Message[] {
INFO_CLI_UNINSTALL_REMOVE_ALL.get(),
INFO_CLI_UNINSTALL_SPECIFY_WHAT_REMOVE.get()
};
MenuBuilder<Integer> builder = new MenuBuilder<Integer>(this);
builder.setPrompt(INFO_CLI_UNINSTALL_WHAT_TO_DELETE.get());
for (int i=0; i<indexes.length; i++)
{
builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i]));
}
builder.addQuitOption();
builder.setDefault(Message.raw(String.valueOf(REMOVE_ALL)),
MenuResult.success(REMOVE_ALL));
builder.setMaxTries(CONFIRMATION_MAX_TRIES);
Menu<Integer> menu = builder.toMenu();
int choice;
try
{
MenuResult<Integer> m = menu.run();
if (m.isSuccess())
{
choice = m.getValue();
}
else if (m.isQuit())
{
choice = REMOVE_ALL;
cancelled = true;
}
else
{
// Should never happen.
throw new RuntimeException();
}
}
catch (CLIException ce)
{
LOG.log(Level.WARNING, "Error reading input: "+ce, ce);
throw new UserDataException(null, ce.getMessageObject(), ce);
}
if (cancelled)
{
// Nothing to do
}
else if (choice == REMOVE_ALL)
{
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 && !cancelled)
{
println();
// Ask for confirmation for the different items
msgs = new Message [] {
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)
)
};
boolean[] answers = new boolean[msgs.length];
try
{
for (int i=0; i<msgs.length; i++)
{
boolean ignore = ((i == 6) && (outsideDbs.size() == 0)) ||
((i == 7) && (outsideLogs.size() == 0));
if (!ignore)
{
answers[i] = askConfirmation(msgs[i], true, LOG);
}
else
{
answers[i] = false;
}
}
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
if (!cancelled)
{
for (int i=0; i<answers.length; i++)
{
switch (i)
{
case 0:
userData.setRemoveLibrariesAndTools(answers[i]);
break;
case 1:
userData.setRemoveDatabases(answers[i]);
break;
case 2:
userData.setRemoveLogs(answers[i]);
break;
case 3:
userData.setRemoveConfigurationAndSchema(answers[i]);
break;
case 4:
userData.setRemoveBackups(answers[i]);
break;
case 5:
userData.setRemoveLDIFs(answers[i]);
break;
case 6:
if (answers[i])
{
userData.setExternalDbsToRemove(outsideDbs);
}
break;
case 7:
if (answers[i])
{
userData.setExternalLogsToRemove(outsideLogs);
}
break;
}
}
if ((userData.getExternalDbsToRemove().size() == 0) &&
(userData.getExternalLogsToRemove().size() == 0) &&
!userData.getRemoveLibrariesAndTools() &&
!userData.getRemoveDatabases() &&
!userData.getRemoveConfigurationAndSchema() &&
!userData.getRemoveBackups() &&
!userData.getRemoveLDIFs() &&
!userData.getRemoveLogs())
{
somethingSelected = false;
println();
printErrorMessage(
ERR_CLI_UNINSTALL_NOTHING_TO_BE_UNINSTALLED.get());
}
else
{
somethingSelected = true;
}
}
}
}
return cancelled;
}
/**
* Commodity method used to ask the user (when necessary) if the server must
* be stopped or not. It also prompts (if required) for authentication.
* @param userData the UserData object to be updated with the
* authentication of the user.
* @return <CODE>true</CODE> if the user wants to continue with uninstall and
* <CODE>false</CODE> otherwise.
* @throws UserDataException if there is a problem with the data
* provided by the user (in the particular case where we are on
* non-interactive uninstall and some data is missing or not valid).
* @throws ApplicationException if there is an error processing data in
* non-interactive mode and an error must be thrown (not in force on error
* mode).
*/
private boolean checkServerState(UninstallUserData userData)
throws UserDataException, ApplicationException
{
boolean cancelled = false;
boolean interactive = parser.isInteractive();
boolean forceOnError = parser.isForceOnError();
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));
}
LOG.log(Level.INFO, "interactive: "+interactive);
LOG.log(Level.INFO, "forceOnError: "+forceOnError);
LOG.log(Level.INFO, "conf.isADS(): "+conf.isADS());
LOG.log(Level.INFO, "conf.isReplicationServer(): "+
conf.isReplicationServer());
LOG.log(Level.INFO, "conf.isServerRunning(): "+conf.isServerRunning());
if (conf.isADS() && conf.isReplicationServer())
{
if (conf.isServerRunning())
{
if (interactive)
{
try
{
if (confirmToUpdateRemote())
{
println();
cancelled = !askForAuthenticationIfNeeded(userData);
if (cancelled)
{
/* Ask for confirmation to stop server */
println();
cancelled = !confirmToStopServer();
}
else
{
cancelled = !updateUserUninstallDataWithRemoteServers(userData);
if (cancelled)
{
println();
/* Ask for confirmation to stop server */
cancelled = !confirmToStopServer();
}
}
}
else
{
println();
/* Ask for confirmation to stop server */
cancelled = !confirmToStopServer();
}
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
else
{
boolean errorWithRemote =
!updateUserUninstallDataWithRemoteServers(userData);
cancelled = errorWithRemote && !parser.isForceOnError();
LOG.log(Level.INFO, "Non interactive mode. errorWithRemote: "+
errorWithRemote);
}
}
else
{
if (interactive)
{
println();
try
{
if (confirmToUpdateRemoteAndStart())
{
boolean startWorked = startServer(userData.isQuiet());
// Ask for authentication if needed, etc.
if (startWorked)
{
cancelled = !askForAuthenticationIfNeeded(userData);
if (cancelled)
{
println();
/* Ask for confirmation to stop server */
cancelled = !confirmToStopServer();
}
else
{
cancelled =
!updateUserUninstallDataWithRemoteServers(userData);
if (cancelled)
{
println();
/* Ask for confirmation to stop server */
cancelled = !confirmToStopServer();
}
}
userData.setStopServer(true);
}
else
{
userData.setStopServer(false);
println();
/* Ask for confirmation to delete files */
cancelled = !confirmDeleteFiles();
}
}
else
{
println();
/* Ask for confirmation to delete files */
cancelled = !confirmDeleteFiles();
}
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
else
{
boolean startWorked = startServer(userData.isQuiet());
// Ask for authentication if needed, etc.
if (startWorked)
{
userData.setStopServer(true);
boolean errorWithRemote =
!updateUserUninstallDataWithRemoteServers(userData);
cancelled = errorWithRemote && !parser.isForceOnError();
}
else
{
cancelled = !forceOnError;
userData.setStopServer(false);
}
}
}
if (!cancelled || parser.isForceOnError())
{
/* During all the confirmations, the server might be stopped. */
userData.setStopServer(
Installation.getLocal().getStatus().isServerRunning());
LOG.log(Level.INFO, "Must stop the server after confirmations? "+
userData.getStopServer());
}
}
else
{
if (conf.isServerRunning())
{
try
{
if (interactive)
{
println();
/* 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());
LOG.log(Level.INFO, "Must stop the server after confirmations? "+
userData.getStopServer());
}
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
else
{
userData.setStopServer(false);
if (interactive)
{
println();
/* Ask for confirmation to delete files */
try
{
cancelled = !confirmDeleteFiles();
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
}
}
LOG.log(Level.INFO, "cancelled: "+cancelled);
return cancelled;
}
/**
* Ask for confirmation to stop server.
* @return <CODE>true</CODE> if the user wants to continue and stop the
* server. <CODE>false</CODE> otherwise.
* @throws CLIException if the user reached the confirmation limit.
*/
private boolean confirmToStopServer() throws CLIException
{
return askConfirmation(INFO_CLI_UNINSTALL_CONFIRM_STOP.get(), true, LOG);
}
/**
* Ask for confirmation to delete files.
* @return <CODE>true</CODE> if the user wants to continue and delete the
* files. <CODE>false</CODE> otherwise.
* @throws CLIException if the user reached the confirmation limit.
*/
private boolean confirmDeleteFiles() throws CLIException
{
return askConfirmation(INFO_CLI_UNINSTALL_CONFIRM_DELETE_FILES.get(), true,
LOG);
}
/**
* Ask for confirmation to update configuration on remote servers.
* @return <CODE>true</CODE> if the user wants to continue and stop the
* server. <CODE>false</CODE> otherwise.
* @throws CLIException if the user reached the confirmation limit.
*/
private boolean confirmToUpdateRemote() throws CLIException
{
return askConfirmation(INFO_CLI_UNINSTALL_CONFIRM_UPDATE_REMOTE.get(), true,
LOG);
}
/**
* Ask for confirmation to update configuration on remote servers.
* @return <CODE>true</CODE> if the user wants to continue and stop the
* server. <CODE>false</CODE> otherwise.
* @throws CLIException if the user reached the confirmation limit.
*/
private boolean confirmToUpdateRemoteAndStart() throws CLIException
{
return askConfirmation(
INFO_CLI_UNINSTALL_CONFIRM_UPDATE_REMOTE_AND_START.get(), true, LOG);
}
/**
* Ask for confirmation to provide again authentication.
* @return <CODE>true</CODE> if the user wants to provide authentication
* again. <CODE>false</CODE> otherwise.
* @throws CLIException if the user reached the confirmation limit.
*/
private boolean promptToProvideAuthenticationAgain() throws CLIException
{
return askConfirmation(
INFO_UNINSTALL_CONFIRM_PROVIDE_AUTHENTICATION_AGAIN.get(), true, LOG);
}
/**
* 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 <CODE>true</CODE> if the user wants to continue and update the
* remote servers. <CODE>false</CODE> otherwise.
* @throws UserDataException if there is a problem with the information
* provided by the user.
* @throws ApplicationException if there is an error processing data.
*/
private boolean askForAuthenticationIfNeeded(UninstallUserData userData)
throws UserDataException, ApplicationException
{
boolean accepted = true;
String uid = userData.getAdminUID();
String pwd = userData.getAdminPwd();
boolean couldConnect = false;
while (!couldConnect && accepted)
{
// This is done because we do not need to ask the user about these
// parameters. If we force their presence the class
// LDAPConnectionConsoleInteraction will not prompt the user for
// them.
SecureConnectionCliArgs secureArgsList = parser.getSecureArgsList();
secureArgsList.hostNameArg.setPresent(true);
secureArgsList.portArg.setPresent(true);
secureArgsList.hostNameArg.clearValues();
secureArgsList.hostNameArg.addValue(
secureArgsList.hostNameArg.getDefaultValue());
secureArgsList.portArg.clearValues();
secureArgsList.portArg.addValue(
secureArgsList.portArg.getDefaultValue());
secureArgsList.bindDnArg.clearValues();
if (uid != null)
{
secureArgsList.bindDnArg.addValue(ADSContext.getAdministratorDN(uid));
secureArgsList.bindDnArg.setPresent(true);
}
else
{
secureArgsList.bindDnArg.setPresent(false);
}
secureArgsList.bindPasswordArg.clearValues();
if (pwd != null)
{
secureArgsList.bindPasswordArg.addValue(pwd);
secureArgsList.bindPasswordArg.setPresent(true);
}
else
{
secureArgsList.bindPasswordArg.setPresent(false);
}
if (ci == null)
{
ci =
new LDAPConnectionConsoleInteraction(this, parser.getSecureArgsList());
ci.setDisplayLdapIfSecureParameters(true);
}
InitialLdapContext ctx = null;
try
{
ci.run(true, false);
userData.setAdminUID(ci.getAdministratorUID());
userData.setAdminPwd(ci.getBindPassword());
info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
String adminConnectorUrl = info.getAdminConnectorURL();
if (adminConnectorUrl == null)
{
LOG.log(Level.WARNING,
"Error retrieving a valid Administration Connector URL in conf file.");
Message msg = ERR_COULD_NOT_FIND_VALID_LDAPURL.get();
throw new ApplicationException(ReturnCode.APPLICATION_ERROR, msg,
null);
}
try
{
URI uri = new URI(adminConnectorUrl);
int port = uri.getPort();
secureArgsList.portArg.clearValues();
secureArgsList.portArg.addValue(String.valueOf(port));
ci.setPortNumber(port);
}
catch (Throwable t)
{
LOG.log(Level.SEVERE, "Error parsing url: "+adminConnectorUrl);
}
LDAPManagementContextFactory factory =
new LDAPManagementContextFactory(alwaysSSL);
factory.getManagementContext(this, ci);
updateTrustManager(userData, ci);
info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
adminConnectorUrl = info.getAdminConnectorURL();
if (adminConnectorUrl == null)
{
LOG.log(Level.WARNING,
"Error retrieving a valid Administration Connector URL in conf file.");
Message msg = ERR_COULD_NOT_FIND_VALID_LDAPURL.get();
throw new ApplicationException(ReturnCode.APPLICATION_ERROR, msg,
null);
}
userData.setLocalServerUrl(adminConnectorUrl);
couldConnect = true;
}
catch (ArgumentException e) {
printErrorMessage(e.getMessageObject());
println();
}
catch (ClientException e) {
printErrorMessage(e.getMessageObject());
println();
}
finally
{
if (ctx != null)
{
try
{
ctx.close();
}
catch (Throwable t)
{
LOG.log(Level.INFO, "Error closing connection: "+t, t);
}
}
}
if (!couldConnect)
{
try
{
accepted = promptToProvideAuthenticationAgain();
if (accepted)
{
uid = null;
pwd = null;
}
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
}
if (accepted)
{
String referencedHostName = parser.getReferencedHostName();
while (referencedHostName == null)
{
println();
referencedHostName = askForReferencedHostName(userData.getHostName());
}
try
{
UninstallData d = new UninstallData(Installation.getLocal());
userData.setReplicationServer(
referencedHostName+":"+d.getReplicationServerPort());
userData.setReferencedHostName(referencedHostName);
}
catch (Throwable t)
{
LOG.log(Level.SEVERE, "Could not create UninstallData: "+t, t);
}
}
userData.setUpdateRemoteReplication(accepted);
return accepted;
}
private String askForReferencedHostName(String defaultHostName)
{
String s = defaultHostName;
try
{
s = readInput(INFO_UNINSTALL_CLI_REFERENCED_HOSTNAME_PROMPT.get(),
defaultHostName);
}
catch (CLIException ce)
{
LOG.log(Level.WARNING, "Error reading input: "+ce, ce);
}
return s;
}
private boolean startServer(boolean supressOutput)
{
LOG.log(Level.INFO, "startServer, supressOutput: "+supressOutput);
boolean serverStarted = false;
Application application = new Application()
{
/**
* {@inheritDoc}
*/
public String getInstallationPath()
{
return Installation.getLocal().getRootDirectory().getAbsolutePath();
}
/**
* {@inheritDoc}
*/
public String getInstancePath()
{
String installPath = getInstallationPath();
// look for <installPath>/lib/resource.loc
String instancePathFileName = installPath + File.separator + "lib"
+ File.separator + "resource.loc";
File f = new File(instancePathFileName);
if (! f.exists())
{
return installPath;
}
BufferedReader reader;
try
{
reader = new BufferedReader(new FileReader(instancePathFileName));
}
catch (Exception e)
{
return installPath;
}
// Read the first line and close the file.
String line;
try
{
line = reader.readLine();
return new File(line).getAbsolutePath();
}
catch (Exception e)
{
return installPath;
}
finally
{
try
{
reader.close();
} catch (Exception e) {}
}
}
/**
* {@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)
{
printlnProgress();
}
controller.startServer(supressOutput);
if (!supressOutput)
{
printlnProgress();
}
serverStarted = Installation.getLocal().getStatus().isServerRunning();
LOG.log(Level.INFO, "server started successfully. serverStarted: "+
serverStarted);
}
catch (ApplicationException ae)
{
LOG.log(Level.WARNING, "ApplicationException: "+ae, ae);
if (!supressOutput)
{
printErrorMessage(ae.getMessageObject());
}
}
catch (Throwable t)
{
LOG.log(Level.SEVERE, "Unexpected error: "+t, t);
throw new IllegalStateException("Unexpected error: "+t, t);
}
return serverStarted;
}
/**
* Updates the contents of the UninstallUserData while trying to connect
* to the remote servers. It returns <CODE>true</CODE> if we could connect
* to the remote servers and all the presented certificates were accepted and
* <CODE>false</CODE> otherwise.
* continue if
* @param userData the user data to be updated.
* @return <CODE>true</CODE> if we could connect
* to the remote servers and all the presented certificates were accepted and
* <CODE>false</CODE> otherwise.
* @throws UserDataException if were are not in interactive mode and not in
* force on error mode and the operation must be stopped.
* @throws ApplicationException if there is an error processing data in
* non-interactive mode and an error must be thrown (not in force on error
* mode).
*/
private boolean updateUserUninstallDataWithRemoteServers(
UninstallUserData userData) throws UserDataException, ApplicationException
{
boolean accepted = false;
boolean interactive = parser.isInteractive();
boolean forceOnError = parser.isForceOnError();
boolean exceptionOccurred = true;
Message exceptionMsg = null;
LOG.log(Level.INFO, "Updating user data with remote servers.");
InitialLdapContext ctx = null;
try
{
info.setTrustManager(userData.getTrustManager());
info.setConnectTimeout(getConnectTimeout());
String host = "localhost";
int port = 389;
String adminUid = userData.getAdminUID();
String pwd = userData.getAdminPwd();
String dn = ADSContext.getAdministratorDN(adminUid);
info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
String adminConnectorUrl = info.getAdminConnectorURL();
try
{
URI uri = new URI(adminConnectorUrl);
host = uri.getHost();
port = uri.getPort();
}
catch (Throwable t)
{
LOG.log(Level.SEVERE, "Error parsing url: "+adminConnectorUrl);
}
ctx = createAdministrativeContext(host, port, useSSL, useStartTLS, dn,
pwd, getConnectTimeout(),
userData.getTrustManager());
ADSContext adsContext = new ADSContext(ctx);
if (interactive && (userData.getTrustManager() == null))
{
// This is required when the user did connect to the server using SSL
// or Start TLS in interactive mode. In this case
// LDAPConnectionInteraction.run does not initialize the keystore and
// the trust manager is null.
forceTrustManagerInitialization();
updateTrustManager(userData, ci);
}
LOG.log(Level.INFO, "Reloading topology");
TopologyCache cache = new TopologyCache(adsContext,
userData.getTrustManager(), getConnectTimeout());
cache.getFilter().setSearchMonitoringInformation(false);
cache.reloadTopology();
accepted = handleTopologyCache(cache, userData);
exceptionOccurred = false;
}
catch (NamingException ne)
{
LOG.log(Level.WARNING, "Error connecting to server: "+ne, ne);
if (Utils.isCertificateException(ne))
{
String details = ne.getMessage() != null ?
ne.getMessage() : ne.toString();
exceptionMsg =
INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE.get(details);
}
else
{
exceptionMsg = Utils.getThrowableMsg(
INFO_ERROR_CONNECTING_TO_LOCAL.get(), ne);
}
} catch (TopologyCacheException te)
{
LOG.log(Level.WARNING, "Error connecting to server: "+te, te);
exceptionMsg = Utils.getMessage(te);
} catch (ApplicationException ae)
{
throw ae;
} catch (Throwable t)
{
LOG.log(Level.WARNING, "Error connecting to server: "+t, t);
exceptionMsg = 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);
}
}
}
if (exceptionOccurred)
{
if (!interactive)
{
if (forceOnError)
{
println();
printErrorMessage(ERR_UNINSTALL_ERROR_UPDATING_REMOTE_FORCE.get(
"--"+parser.getSecureArgsList().adminUidArg.getLongIdentifier(),
"--"+ToolConstants.OPTION_LONG_BINDPWD,
"--"+ToolConstants.OPTION_LONG_BINDPWD_FILE,
String.valueOf(exceptionMsg)));
}
else
{
println();
throw new UserDataException(null,
ERR_UNINSTALL_ERROR_UPDATING_REMOTE_NO_FORCE.get(
"--"+
parser.getSecureArgsList().adminUidArg.getLongIdentifier(),
"--"+ToolConstants.OPTION_LONG_BINDPWD,
"--"+ToolConstants.OPTION_LONG_BINDPWD_FILE,
"--"+parser.forceOnErrorArg.getLongIdentifier(),
String.valueOf(exceptionMsg)));
}
}
else
{
try
{
accepted = askConfirmation(
ERR_UNINSTALL_NOT_UPDATE_REMOTE_PROMPT.get(),
false, LOG);
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
}
userData.setUpdateRemoteReplication(accepted);
LOG.log(Level.INFO, "accepted: "+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 <CODE>true</CODE> if the user accepts all the problems encountered
* and <CODE>false</CODE> otherwise.
* @param userData the user data.
* @throws UserDataException if there is an error with the information
* provided by the user when we are in non-interactive mode.
* @throws ApplicationException if there is an error processing data in
* non-interactive mode and an error must be thrown (not in force on error
* mode).
*/
private boolean handleTopologyCache(TopologyCache cache,
UninstallUserData userData) throws UserDataException, ApplicationException
{
boolean returnValue;
boolean stopProcessing = false;
boolean reloadTopologyCache = false;
boolean interactive = parser.isInteractive();
LOG.log(Level.INFO, "Handle topology cache.");
Set<TopologyCacheException> exceptions =
new HashSet<TopologyCacheException>();
/* 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<ServerDescriptor> servers = cache.getServers();
userData.setRemoteServers(servers);
for (ServerDescriptor server : servers)
{
TopologyCacheException e = server.getLastException();
if (e != null)
{
exceptions.add(e);
}
}
Set<Message> exceptionMsgs = new LinkedHashSet<Message>();
/* Check the exceptions and see if we throw them or not. */
for (TopologyCacheException e : exceptions)
{
LOG.log(Level.INFO, "Analyzing exception: "+e, e);
if (stopProcessing)
{
break;
}
switch (e.getType())
{
case NOT_GLOBAL_ADMINISTRATOR:
println();
printErrorMessage(INFO_NOT_GLOBAL_ADMINISTRATOR_PROVIDED.get());
stopProcessing = true;
break;
case GENERIC_CREATING_CONNECTION:
if ((e.getCause() != null) &&
Utils.isCertificateException(e.getCause()))
{
if (interactive)
{
println();
if (ci.promptForCertificateConfirmation(e.getCause(),
e.getTrustManager(), e.getLdapUrl(), true, LOG))
{
stopProcessing = true;
reloadTopologyCache = true;
updateTrustManager(userData, ci);
}
else
{
stopProcessing = true;
}
}
else
{
exceptionMsgs.add(
INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER.get(
e.getHostPort(), e.getCause().getMessage()));
}
}
else
{
exceptionMsgs.add(Utils.getMessage(e));
}
break;
default:
exceptionMsgs.add(Utils.getMessage(e));
}
}
if (interactive)
{
if (!stopProcessing && (exceptionMsgs.size() > 0))
{
println();
try
{
returnValue = askConfirmation(
ERR_UNINSTALL_READING_REGISTERED_SERVERS_CONFIRM_UPDATE_REMOTE.get(
Utils.getMessageFromCollection(exceptionMsgs,
Constants.LINE_SEPARATOR).toString()), true, LOG);
}
catch (CLIException ce)
{
throw new UserDataException(null, ce.getMessageObject(), ce);
}
}
else if (reloadTopologyCache)
{
returnValue = updateUserUninstallDataWithRemoteServers(userData);
}
else
{
returnValue = !stopProcessing;
}
}
else
{
LOG.log(Level.INFO, "exceptionMsgs: "+exceptionMsgs);
if (exceptionMsgs.size() > 0)
{
if (parser.isForceOnError())
{
Message msg = Utils.getMessageFromCollection(exceptionMsgs,
Constants.LINE_SEPARATOR);
println();
printErrorMessage(msg);
returnValue = false;
}
else
{
Message msg =
ERR_UNINSTALL_ERROR_UPDATING_REMOTE_NO_FORCE.get(
"--"+
parser.getSecureArgsList().adminUidArg.getLongIdentifier(),
"--"+ToolConstants.OPTION_LONG_BINDPWD,
"--"+ToolConstants.OPTION_LONG_BINDPWD_FILE,
"--"+parser.forceOnErrorArg.getLongIdentifier(),
Utils.getMessageFromCollection(exceptionMsgs,
Constants.LINE_SEPARATOR).toString());
throw new ApplicationException(ReturnCode.APPLICATION_ERROR, msg,
null);
}
}
else
{
returnValue = true;
}
}
LOG.log(Level.INFO, "Return value: "+returnValue);
return returnValue;
}
/**
* {@inheritDoc}
*/
public boolean isAdvancedMode() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isInteractive() {
if (forceNonInteractive)
{
return false;
}
else
{
return parser.isInteractive();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMenuDrivenMode() {
return true;
}
/**
* {@inheritDoc}
*/
public boolean isQuiet() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isScriptFriendly() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isVerbose() {
return true;
}
/**
* Commodity method to update the user data with the trust manager in the
* LDAPConnectionConsoleInteraction object.
* @param userData the user data to be updated.
* @param ci the LDAPConnectionConsoleInteraction object to be used to update
* the user data object.
*/
private void updateTrustManager(UninstallUserData userData,
LDAPConnectionConsoleInteraction ci)
{
ApplicationTrustManager trust = null;
TrustManager t = ci.getTrustManager();
if (t != null)
{
if (t instanceof ApplicationTrustManager)
{
trust = (ApplicationTrustManager)t;
}
else
{
trust = new ApplicationTrustManager(ci.getKeyStore());
}
}
userData.setTrustManager(trust);
}
/**
* Forces the initialization of the trust manager in the
* LDAPConnectionInteraction object.
*/
private void forceTrustManagerInitialization()
{
forceNonInteractive = true;
try
{
ci.initializeTrustManagerIfRequired();
}
catch (ArgumentException ae)
{
LOG.log(Level.WARNING, "Error initializing trust store: "+ae, ae);
}
forceNonInteractive = false;
}
private void printErrorMessage(Message msg)
{
super.println(msg);
LOG.log(Level.WARNING, msg.toString());
}
/**
* Returns the timeout to be used to connect in milliseconds. The method
* must be called after parsing the arguments.
* @return the timeout to be used to connect in milliseconds. Returns
* {@code 0} if there is no timeout.
* @throw {@code IllegalStateException} if the method is called before
* parsing the arguments.
*/
private int getConnectTimeout()
{
try
{
return parser.getSecureArgsList().connectTimeoutArg.getIntValue();
}
catch (ArgumentException ae)
{
throw new IllegalStateException("Argument parser is not parsed: "+ae,
ae);
}
}
}