| /* |
| * Copyright (c) 2022 Contributors to the Eclipse Foundation |
| * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package com.sun.enterprise.admin.cli.remote; |
| |
| import com.sun.appserv.management.client.prefs.LoginInfo; |
| import com.sun.appserv.management.client.prefs.LoginInfoStore; |
| import com.sun.appserv.management.client.prefs.LoginInfoStoreFactory; |
| import com.sun.appserv.management.client.prefs.StoreException; |
| import com.sun.enterprise.admin.cli.CLICommand; |
| import com.sun.enterprise.admin.cli.DirectoryClassLoader; |
| import com.sun.enterprise.admin.cli.Environment; |
| import com.sun.enterprise.admin.cli.ProgramOptions; |
| import com.sun.enterprise.admin.cli.ProgramOptions.PasswordLocation; |
| import com.sun.enterprise.admin.remote.RemoteRestAdminCommand; |
| import com.sun.enterprise.admin.remote.sse.GfSseInboundEvent; |
| import com.sun.enterprise.admin.util.CachedCommandModel; |
| import com.sun.enterprise.admin.util.CommandModelData; |
| import com.sun.enterprise.admin.util.CommandModelData.ParamModelData; |
| import com.sun.enterprise.module.ModulesRegistry; |
| import com.sun.enterprise.module.single.StaticModulesRegistry; |
| import com.sun.enterprise.security.store.AsadminSecurityUtil; |
| import com.sun.enterprise.universal.i18n.LocalStringsImpl; |
| import com.sun.enterprise.util.StringUtils; |
| import com.sun.enterprise.util.SystemPropertyConstants; |
| |
| import java.io.BufferedReader; |
| import java.io.Console; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.net.CookieHandler; |
| import java.net.CookieManager; |
| import java.net.CookiePolicy; |
| import java.net.CookieStore; |
| import java.net.HttpCookie; |
| import java.net.URI; |
| import java.net.URLConnection; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.glassfish.api.ActionReport; |
| import org.glassfish.api.ActionReport.ExitCode; |
| import org.glassfish.api.admin.AdminCommandEventBroker; |
| import org.glassfish.api.admin.AdminCommandEventBroker.AdminCommandListener; |
| import org.glassfish.api.admin.AdminCommandState; |
| import org.glassfish.api.admin.CommandException; |
| import org.glassfish.api.admin.CommandModel.ParamModel; |
| import org.glassfish.api.admin.CommandProgress; |
| import org.glassfish.api.admin.CommandValidationException; |
| import org.glassfish.api.admin.ParameterMap; |
| import org.glassfish.common.util.admin.ManPageFinder; |
| import org.glassfish.hk2.api.ActiveDescriptor; |
| import org.glassfish.hk2.api.ServiceLocator; |
| import org.glassfish.hk2.utilities.BuilderHelper; |
| import org.jvnet.hk2.component.MultiMap; |
| |
| /** |
| * A remote command handled by the asadmin CLI. |
| */ |
| //Fork of RemoteCommands |
| public class RemoteCLICommand extends CLICommand { |
| |
| private static final LocalStringsImpl strings = new LocalStringsImpl(RemoteCLICommand.class); |
| // return output string rather than printing it |
| private boolean returnOutput = false; |
| private String output; |
| private boolean returnActionReport = false; |
| private ActionReport ar; |
| private String usage; |
| private String responseFormatType; |
| private OutputStream userOut; |
| private File outputDir; |
| //Options from first round if reexecuted because of CommandModel update |
| protected ParameterMap reExecutedOptions; |
| protected List<String> reExecutedOperands; |
| private RemoteCLICommand.CLIRemoteAdminCommand rac; |
| private final MultiMap<String, AdminCommandListener<GfSseInboundEvent>> listeners = new MultiMap<>(); |
| |
| /** |
| * A special RemoteAdminCommand that overrides methods so that we can handle the interactive requirements of a CLI |
| * command. |
| */ |
| private class CLIRemoteAdminCommand extends RemoteRestAdminCommand { |
| |
| private static final String JSESSIONID = "JSESSIONID"; |
| private static final String COOKIE_HEADER = "Cookie"; |
| private CookieManager cookieManager = null; |
| private File sessionCache = null; |
| private final ProgressStatusPrinter statusPrinter; |
| |
| /** |
| * Construct a new remote command object. The command and arguments are supplied later using the execute method in the |
| * superclass. |
| */ |
| public CLIRemoteAdminCommand(String name, String host, int port, boolean secure, String user, char[] password, Logger logger, |
| String authToken, boolean notify) throws CommandException { |
| super(name, host, port, secure, user, password, logger, getCommandScope(), authToken, true /* prohibitDirectoryUploads */, |
| notify); |
| |
| //TODO: Remove when fix cache problem |
| if (programOpts.getCommandName() != null && programOpts.getCommandName().contains("cadmin")) { |
| super.setEnableCommandModelCache(false); |
| } |
| |
| StringBuilder sessionFilePath = new StringBuilder(); |
| |
| // Store the cache at: $GFCLIENT/cache/{host}_{port}/session |
| sessionFilePath.append("cache").append(File.separator); |
| sessionFilePath.append(host).append("_"); |
| sessionFilePath.append(port).append(File.separator); |
| sessionFilePath.append("session"); |
| |
| sessionCache = new File(AsadminSecurityUtil.getDefaultClientDir(), sessionFilePath.toString()); |
| statusPrinter = new ProgressStatusPrinter(env.debug() || env.trace() || System.console() == null, env.debug() || env.trace(), |
| logger); |
| if (!programOpts.isTerse()) { |
| super.registerListener(CommandProgress.EVENT_PROGRESSSTATUS_CHANGE, statusPrinter); |
| super.registerListener(CommandProgress.EVENT_PROGRESSSTATUS_STATE, statusPrinter); |
| super.registerListener(AdminCommandEventBroker.EventBrokerUtils.USER_MESSAGE_NAME, statusPrinter); |
| } |
| //Readtimeout |
| String stimeout = env.getStringOption("READTIMEOUT"); |
| if (StringUtils.ok(stimeout)) { |
| super.setReadTimeout(Integer.parseInt(stimeout)); |
| } |
| } |
| |
| @Override |
| public void fetchCommandModel() throws CommandException { |
| super.fetchCommandModel(); |
| } |
| |
| /** |
| * If we're interactive, prompt for a new username and password. Return true if we're successful in collecting new |
| * information (and thus the caller should try the request again). |
| */ |
| @Override |
| protected boolean updateAuthentication() { |
| Console cons; |
| if (programOpts.isInteractive() && (cons = System.console()) != null) { |
| // if appropriate, tell the user why authentication failed |
| PasswordLocation pwloc = programOpts.getPasswordLocation(); |
| if (pwloc == PasswordLocation.PASSWORD_FILE) { |
| logger.fine(strings.get("BadPasswordFromFile", programOpts.getPasswordFile())); |
| } else if (pwloc == PasswordLocation.LOGIN_FILE) { |
| try { |
| LoginInfoStore store = LoginInfoStoreFactory.getDefaultStore(); |
| logger.fine(strings.get("BadPasswordFromLogin", store.getName())); |
| } catch (StoreException ex) { |
| // ignore it |
| } |
| } |
| |
| String user = null; |
| // only prompt for a user name if the user name is set to |
| // the default. otherwise, assume the user specified the |
| // correct username to begin with and all we need is the |
| // password. |
| if (programOpts.getUser() == null) { |
| cons.printf("%s ", strings.get("AdminUserPrompt")); |
| user = cons.readLine(); |
| if (user == null) { |
| return false; |
| } |
| } |
| char[] password; |
| String puser = ok(user) ? user : programOpts.getUser(); |
| if (ok(puser)) { |
| password = readPassword(strings.get("AdminUserPasswordPrompt", puser)); |
| } else { |
| password = readPassword(strings.get("AdminPasswordPrompt")); |
| } |
| if (password == null) { |
| return false; |
| } |
| if (ok(user)) { // if none entered, don't change |
| programOpts.setUser(user); |
| this.user = user; |
| } |
| programOpts.setPassword(password, PasswordLocation.USER); |
| this.password = password; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Get from environment. |
| */ |
| @Override |
| protected String getFromEnvironment(String name) { |
| String result = env.getStringOption(name); |
| if (!StringUtils.ok(result)) { |
| result = programOpts.getPlainOption(name); |
| } |
| return result; |
| } |
| |
| /** |
| * Called when a non-secure connection attempt fails and it appears that the server requires a secure connection. Tell |
| * the user that we're retrying. |
| */ |
| @Override |
| protected boolean retryUsingSecureConnection(String host, int port) { |
| String msg = strings.get("ServerMaybeSecure", host, port + ""); |
| logger.info(msg); |
| return true; |
| } |
| |
| /** |
| * Return the error message to be used in the AuthenticationException. Subclasses can override to provide a more |
| * detailed message, for example, indicating the source of the password that failed. |
| */ |
| @Override |
| protected String reportAuthenticationException() { |
| String msg = null; |
| PasswordLocation pwloc = programOpts.getPasswordLocation(); |
| if (pwloc == PasswordLocation.PASSWORD_FILE) { |
| msg = strings.get("InvalidCredentialsFromFile", programOpts.getUser(), programOpts.getPasswordFile()); |
| } else if (pwloc == PasswordLocation.LOGIN_FILE) { |
| try { |
| LoginInfoStore store = LoginInfoStoreFactory.getDefaultStore(); |
| msg = strings.get("InvalidCredentialsFromLogin", programOpts.getUser(), store.getName()); |
| } catch (StoreException ex) { |
| // ignore it |
| } |
| } |
| |
| if (msg == null) { |
| msg = strings.get("InvalidCredentials", programOpts.getUser()); |
| } |
| return msg; |
| } |
| |
| /** |
| * Adds cookies to the header to support session based client routing. |
| * |
| * @param urlConnection |
| */ |
| @Override |
| protected synchronized void addAdditionalHeaders(final URLConnection urlConnection) { |
| addCookieHeaders(urlConnection); |
| } |
| |
| @Override |
| protected boolean useSse() throws CommandException { |
| if (env.getBooleanOption("NOSSE")) { |
| return false; |
| } |
| return programOpts.isDetachedCommand() || listeners.size() > 0 || super.useSse(); |
| } |
| |
| @Override |
| protected boolean refetchInvalidModel() { |
| return false; |
| } |
| |
| /* |
| * Adds any cookies maintained in the clients session cookie cache. |
| */ |
| private void addCookieHeaders(final URLConnection urlConnection) { |
| |
| // Get the last modified time of the session cache file. |
| long modifiedTime = sessionCache.lastModified(); |
| if (modifiedTime == 0) { |
| // No session file so no cookies to add. |
| return; |
| } |
| |
| // Remote any Set-Cookie's in the system cookie manager otherwise |
| // they appear as cookies in the outgoing request. |
| ((CookieManager) CookieHandler.getDefault()).getCookieStore().removeAll(); |
| |
| cookieManager = new CookieManager(new ClientCookieStore(new CookieManager().getCookieStore(), sessionCache), |
| CookiePolicy.ACCEPT_ALL); |
| |
| // XXX: If this is an interactive command we don't want to |
| // keep reloading the cookie store. |
| try { |
| ((ClientCookieStore) cookieManager.getCookieStore()).load(); |
| } catch (IOException e) { |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Unable to load cookies: " + e.toString()); |
| } |
| return; |
| } |
| |
| if (isSessionCookieExpired(cookieManager, modifiedTime)) { |
| logger.finer("Cookie session file has expired."); |
| if (!sessionCache.delete()) { |
| logger.finer("Unable to delete session file."); |
| } |
| return; |
| } |
| |
| StringBuilder sb = new StringBuilder("$Version=1"); |
| boolean hasCookies = false; |
| for (HttpCookie cookie : cookieManager.getCookieStore().getCookies()) { |
| hasCookies = true; |
| sb.append("; ").append(cookie.getName()).append("=").append(cookie.getValue()); |
| } |
| if (hasCookies) { |
| urlConnection.setRequestProperty(COOKIE_HEADER, sb.toString()); |
| } |
| } |
| |
| /* |
| * Looks for the SESSIONID cookie in the cookie store and |
| * determines if the cookie has expired (based on the |
| * Max-Age and the time in which the file was last modified.) |
| * The assumption, based on how we write cookies, is that |
| * the cookie session file will only be changed when the |
| * JSESSIONID cookie changes. Therefor the last mod time of |
| * the file is a reasonable proxy for when the cookie was |
| * "created". |
| * If we can't find the JSESSIONID cookie then we return true. |
| */ |
| private boolean isSessionCookieExpired(CookieManager manager, long creationTime) { |
| for (URI uri : manager.getCookieStore().getURIs()) { |
| for (HttpCookie cookie : manager.getCookieStore().get(uri)) { |
| if (cookie.getName().equals(JSESSIONID)) { |
| if ((creationTime / 1000 + cookie.getMaxAge()) < System.currentTimeMillis() / 1000) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Processes the headers to support session based client routing. |
| * |
| * @param urlConnection |
| */ |
| @Override |
| protected synchronized void processHeaders(final URLConnection urlConnection) { |
| processCookieHeaders(urlConnection); |
| } |
| |
| private void processCookieHeaders(final URLConnection urlConnection) { |
| |
| CookieManager systemCookieManager = (CookieManager) CookieHandler.getDefault(); |
| |
| if (systemCookieManager == null) { |
| logger.finer("Assertion failed: null system CookieManager"); |
| return; |
| } |
| |
| // Using the system CookieHandler, retrieve any cookies. |
| CookieStore systemCookieJar = systemCookieManager.getCookieStore(); |
| List<HttpCookie> newCookies = systemCookieJar.getCookies(); |
| |
| if (newCookies.isEmpty()) { |
| // If there are no cookies to set in the request we |
| // have nothing to do. |
| return; |
| } |
| |
| /* |
| Console console = System.console(); |
| for (HttpCookie cookie: newCookies) { |
| console.printf("Cookie: %s%n", cookie.toString()); |
| console.printf(" MaxAge: %d%n", cookie.getMaxAge()); |
| console.printf(" Domain: %s%n", cookie.getDomain()); |
| console.printf(" Path: %s%n", cookie.getPath()); |
| } |
| * |
| */ |
| |
| // Get the last modified time of the session cache file. |
| if (sessionCache.lastModified() == 0) { |
| // No file, if we have cookies we need to save them. |
| if (cookieManager == null) { |
| cookieManager = new CookieManager(new ClientCookieStore(new CookieManager().getCookieStore(), sessionCache), |
| CookiePolicy.ACCEPT_ALL); |
| } |
| try { |
| cookieManager.put(((ClientCookieStore) cookieManager.getCookieStore()).getStaticURI(), urlConnection.getHeaderFields()); |
| } catch (IOException e) { |
| // Thrown by cookieManger.put() |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Unable to save cookies: " + e.toString()); |
| } |
| return; |
| } |
| |
| try { |
| ((ClientCookieStore) cookieManager.getCookieStore()).store(); |
| } catch (IOException e) { |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Unable to store cookies: " + e.toString()); |
| } |
| } |
| return; |
| } |
| |
| if (cookieManager == null) { |
| cookieManager = new CookieManager(new ClientCookieStore(new CookieManager().getCookieStore(), sessionCache), |
| CookiePolicy.ACCEPT_ALL); |
| try { |
| ((ClientCookieStore) cookieManager.getCookieStore()).load(); |
| } catch (IOException e) { |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Unable to load cookies: " + e.toString()); |
| } |
| return; |
| } |
| } |
| |
| boolean newCookieFound = false; |
| |
| // Check to see if any of the set cookies in the reply are |
| // different from what is already in the persistent store. |
| for (HttpCookie cookie : systemCookieJar.getCookies()) { |
| // Check to see if any of the set cookies in the reply are |
| // different from what is already in the persistent store. |
| int cookieIndex = cookieManager.getCookieStore().getCookies().indexOf(cookie); |
| if (cookieIndex == -1) { |
| newCookieFound = true; |
| break; |
| } else { |
| HttpCookie c1 = cookieManager.getCookieStore().getCookies().get(cookieIndex); |
| |
| if (!c1.getValue().equals(cookie.getValue())) { |
| newCookieFound = true; |
| break; |
| } |
| } |
| } |
| |
| // Note: This has the potential to overwrite the existing file |
| // which may contain changes that were introduced by another |
| // command's execution. Those changes will be lost. |
| // Since the cookie store is only used for optimized session |
| // routing we are only interested in preserving the last |
| // set of session/routing cookies received from the server/LB |
| // as we believe those to be the most current and accurate in |
| // regards to future request routing. |
| if (newCookieFound) { |
| try { |
| try { |
| cookieManager.put(((ClientCookieStore) cookieManager.getCookieStore()).getStaticURI(), |
| urlConnection.getHeaderFields()); |
| } catch (IOException e) { |
| // Thrown by cookieManger.put() |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Unable to save cookies: " + e.toString()); |
| } |
| return; |
| } |
| ((ClientCookieStore) cookieManager.getCookieStore()).store(); |
| } catch (IOException e) { |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Unable to store cookies: " + e.toString()); |
| } |
| } |
| } else { |
| // No cookies changed. Update the modification time on the store. |
| ((ClientCookieStore) cookieManager.getCookieStore()).touchStore(); |
| } |
| } |
| } |
| |
| /** |
| * A class loader for the "modules" directory. |
| */ |
| private static ClassLoader moduleClassLoader; |
| /** |
| * A habitat just for finding man pages. |
| */ |
| private static ServiceLocator manServiceLocator; |
| |
| /** |
| * Construct a new remote command object. The command and arguments are supplied later using the execute method in the |
| * superclass. |
| */ |
| public RemoteCLICommand() throws CommandException { |
| super(); |
| } |
| |
| /** |
| * Construct a new remote command object. The command and arguments are supplied later using the execute method in the |
| * superclass. |
| */ |
| public RemoteCLICommand(String name, ProgramOptions po, Environment env) throws CommandException { |
| super(name, po, env); |
| } |
| |
| /** |
| * Construct a new remote command object. The command and arguments are supplied later using the execute method in the |
| * superclass. This variant is used by the RemoteDeploymentFacility class to control and capture the output. |
| */ |
| public RemoteCLICommand(String name, ProgramOptions po, Environment env, String responseFormatType, OutputStream userOut) |
| throws CommandException { |
| this(name, po, env); |
| this.responseFormatType = responseFormatType; |
| this.userOut = userOut; |
| } |
| |
| /** |
| * Helper for situation, where {@code CommandModel} is from cache and something shows, that server side signature of |
| * command was changed |
| */ |
| private void reExecuteAfterMetadataUpdate() throws ReExecuted, CommandException { |
| //Check CommandModel |
| if (rac == null) { |
| return; |
| } |
| if (rac.getCommandModel() == null) { |
| return; |
| } |
| if (!rac.isCommandModelFromCache()) { |
| return; |
| } |
| //Refetch command model |
| String eTag = CachedCommandModel.computeETag(rac.getCommandModel()); |
| rac = null; |
| initializeRemoteAdminCommand(); |
| rac.fetchCommandModel(); |
| String newETag = CachedCommandModel.computeETag(rac.getCommandModel()); |
| if (eTag != null && eTag.equals(newETag)) { |
| return; //Nothing change in command model |
| } |
| //logger.log(Level.WARNING, "Command signature of {0} command was changed. Reexecuting with new metadata.", name); |
| //clean state of this instance |
| this.reExecutedOptions = this.options; |
| this.reExecutedOperands = this.operands; |
| this.options = null; |
| this.operands = null; |
| //Reexecute it |
| int result = execute(argv); |
| throw new ReExecuted(result); |
| } |
| |
| @Override |
| public int execute(String... argv) throws CommandException { |
| try { |
| logger.log(Level.FINEST, "RemoteCLICommand executes"); |
| return super.execute(argv); |
| } catch (ReExecuted reex) { |
| return reex.getExecutionResult(); |
| } |
| } |
| |
| /** |
| * Set the directory in which any returned files will be stored. The default is the user's home directory. |
| */ |
| public void setFileOutputDirectory(File dir) { |
| outputDir = dir; |
| } |
| |
| @Override |
| protected void prepare() throws CommandException, CommandValidationException { |
| try { |
| processProgramOptions(); |
| |
| initializeAuth(); |
| |
| /* |
| * Now we have all the information we need to create |
| * the remote admin command object. |
| */ |
| initializeRemoteAdminCommand(); |
| |
| if (responseFormatType != null) { |
| rac.setResponseFormatType(responseFormatType); |
| } |
| if (userOut != null) { |
| rac.setUserOut(userOut); |
| } |
| |
| /* |
| * Initialize a CookieManager so that we can retreive |
| * any cookies included in the reply. These cookies |
| * (e.g. JSESSIONID, JROUTE) are used for CLI session |
| * based routing. |
| */ |
| initializeCookieManager(); |
| |
| /* |
| * If this is a help request, we don't need the command |
| * metadata and we throw away all the other options and |
| * fake everything else. |
| */ |
| if (programOpts.isHelp()) { |
| CommandModelData cm = new CommandModelData(name); |
| cm.add(new ParamModelData("help", boolean.class, true, "false", "?")); |
| this.commandModel = cm; |
| rac.setCommandModel(cm); |
| return; |
| } |
| |
| /* |
| * Find the metadata for the command. |
| */ |
| commandModel = rac.getCommandModel(); |
| } catch (CommandException e) { |
| logger.log(Level.SEVERE, "RemoteCLICommand.prepare throws exception.", e); |
| throw e; |
| } catch (Exception e) { |
| logger.log(Level.SEVERE, "RemoteCLICommand.prepare throws exception.", e); |
| throw new CommandException(e.getMessage(), e); |
| } |
| } |
| |
| @Override |
| protected void prevalidate() throws CommandException { |
| try { |
| //Copy date from interactive part of |
| if (reExecutedOperands != null && !reExecutedOperands.isEmpty()) { |
| ParamModel operandModel = getOperandModel(); |
| if (operandModel != null && !operandModel.getParam().optional()) { |
| if (operands == null) { |
| operands = new ArrayList<>(reExecutedOperands.size()); |
| } |
| if (reExecutedOperands.size() > operands.size()) { |
| if (operandModel.getParam().multiple()) { |
| for (String str : reExecutedOperands) { |
| if (!operands.contains(str)) { |
| operands.add(str); |
| } |
| } |
| } else if (reExecutedOperands.size() == 1) { |
| operands = reExecutedOperands; |
| } |
| } |
| } |
| } |
| if (reExecutedOptions != null) { |
| for (ParamModel opt : commandModel.getParameters()) { |
| if (opt.getParam().primary()) { |
| continue; |
| } |
| if (options.get(opt.getName()) == null && reExecutedOptions.get(opt.getName()) != null) { |
| options.set(opt.getName(), reExecutedOptions.get(opt.getName())); |
| } |
| } |
| } |
| //Execute |
| super.prevalidate(); |
| } catch (CommandException ex) { |
| reExecuteAfterMetadataUpdate(); |
| throw ex; |
| } |
| } |
| |
| @Override |
| protected void parse() throws CommandException { |
| try { |
| super.parse(); |
| } catch (CommandValidationException ex) { |
| reExecuteAfterMetadataUpdate(); |
| throw ex; |
| } |
| } |
| |
| /** |
| * If it's a help request, don't prompt for any missing options. |
| */ |
| @Override |
| protected void validate() throws CommandException, CommandValidationException { |
| if (programOpts.isHelp()) { |
| return; |
| } |
| try { |
| super.validate(); |
| } catch (CommandValidationException ex) { |
| reExecuteAfterMetadataUpdate(); |
| throw ex; |
| } |
| } |
| |
| @Override |
| protected void inject() throws CommandException { |
| try { |
| super.prevalidate(); |
| } catch (CommandValidationException ex) { |
| reExecuteAfterMetadataUpdate(); |
| throw ex; |
| } |
| } |
| |
| // /** |
| // * We do all our help processing in executeCommand. |
| // */ |
| // @Override |
| // protected boolean checkHelp() |
| // throws CommandException, CommandValidationException { |
| // return false; |
| // } |
| /** |
| * Runs the command using the specified arguments. |
| */ |
| @Override |
| protected int executeCommand() throws CommandException, CommandValidationException { |
| try { |
| logger.finer("RemoteCLICommand.executeCommand()"); |
| rac.statusPrinter.reset(); |
| options.set("DEFAULT", operands); |
| if (programOpts.isDetachedCommand()) { |
| rac.registerListener(AdminCommandState.EVENT_STATE_CHANGED, new DetachListener(logger, rac, programOpts.isTerse())); |
| } |
| |
| /*if (programOpts.isNotifyCommand()) { |
| rac.registerListener(AdminCommandState.EVENT_STATE_CHANGED, |
| new NotifyListener(logger, rac, programOpts.isTerse())); |
| }*/ |
| try { |
| output = rac.executeCommand(options); |
| } finally { |
| rac.statusPrinter.deleteLastMessage(); |
| } |
| ar = rac.getActionReport(); |
| if (!returnActionReport && !returnOutput) { |
| if (output.length() > 0) { |
| logger.info(output); |
| } |
| } |
| } catch (CommandValidationException cve) { |
| reExecuteAfterMetadataUpdate(); |
| throw cve; |
| } catch (CommandException ex) { |
| // if a --help request failed, try to emulate it locally |
| if (programOpts.isHelp()) { |
| Reader r = getLocalManPage(); |
| BufferedReader br = null; |
| try { |
| if (r != null) { |
| br = new BufferedReader(r); |
| PrintWriter pw = new PrintWriter(System.out); |
| char[] buf = new char[8192]; |
| int cnt; |
| while ((cnt = br.read(buf)) > 0) { |
| pw.write(buf, 0, cnt); |
| } |
| pw.flush(); |
| return SUCCESS; |
| } |
| } catch (IOException ioex2) { |
| // ignore it and throw original exception |
| } finally { |
| try { |
| if (br != null) { |
| br.close(); |
| } |
| } catch (IOException ioex3) { |
| // ignore it |
| } |
| } |
| } |
| throw ex; |
| } |
| ActionReport ar = rac.getActionReport(); |
| if (ar != null && ExitCode.WARNING == ar.getActionExitCode()) { |
| return WARNING; |
| } |
| return SUCCESS; |
| } |
| |
| /** |
| * Execute the command and return the output as a string instead of writing it out. |
| */ |
| public String executeAndReturnOutput(String... args) throws CommandException, CommandValidationException { |
| /* |
| * Tell the low level output processing to just save the output |
| * string instead of writing it out. Yes, this is pretty gross. |
| */ |
| returnOutput = true; |
| execute(args); |
| returnOutput = false; |
| return output; |
| } |
| |
| /** |
| * Execute the command and return the main attributes from the manifest instead of writing out the output. |
| */ |
| public ActionReport executeAndReturnActionReport(String... args) throws CommandException, CommandValidationException { |
| /* |
| * Tell the low level output processing to just save the attributes |
| * instead of writing out the output. Yes, this is pretty gross. |
| */ |
| returnActionReport = true; |
| execute(args); |
| returnActionReport = false; |
| return ar; |
| } |
| |
| /** |
| * Get the usage text. If we got usage information from the server, use it. |
| * |
| * @return usage text |
| */ |
| @Override |
| public String getUsage() { |
| if (usage == null) { |
| if (rac == null) { |
| /* |
| * We weren't able to initialize the RemoteAdminCommand |
| * object, probably because we failed to parse the program |
| * options. With no ability to contact the remote server, |
| * we can't provide any command-specific usage information. |
| * Sigh. |
| */ |
| return getCommandUsage(); |
| } |
| usage = rac.getUsage(); |
| } |
| if (usage == null) { |
| return super.getUsage(); |
| } |
| |
| StringBuilder usageText = new StringBuilder(); |
| usageText.append(strings.get("Usage", getBriefCommandUsage())); |
| usageText.append(" "); |
| usageText.append(usage); |
| return usageText.toString(); |
| } |
| |
| /** |
| * Get the man page from the server. If the man page isn't available, e.g., because the server is down, try to find it |
| * locally by looking in the modules directory. |
| */ |
| @Override |
| public BufferedReader getManPage() { |
| try { |
| initializeRemoteAdminCommand(); |
| String manpage = rac.getManPage(); |
| return new BufferedReader(new StringReader(manpage)); |
| } catch (CommandException cex) { |
| // ignore |
| } |
| |
| /* |
| * Can't find the man page remotely, try to find it locally. |
| * XXX - maybe should only do this on connection failure |
| */ |
| BufferedReader r = getLocalManPage(); |
| return r != null ? r : super.getManPage(); |
| } |
| |
| public synchronized void registerListener(String regexpForName, AdminCommandListener listener) { |
| listeners.add(regexpForName, listener); |
| if (rac != null) { |
| rac.registerListener(regexpForName, listener); |
| } |
| } |
| |
| /** |
| * Try to find a local version of the man page for this command. |
| */ |
| private BufferedReader getLocalManPage() { |
| logger.fine(strings.get("NoRemoteManPage")); |
| String cmdClass = getCommandClass(getName()); |
| ClassLoader mcl = getModuleClassLoader(); |
| if (cmdClass != null && mcl != null) { |
| return ManPageFinder.getCommandManPage(getName(), cmdClass, Locale.getDefault(), mcl, logger); |
| } |
| return null; |
| } |
| |
| private void initializeRemoteAdminCommand() throws CommandException { |
| if (rac == null) { |
| rac = new RemoteCLICommand.CLIRemoteAdminCommand(name, programOpts.getHost(), programOpts.getPort(), programOpts.isSecure(), |
| programOpts.getUser(), programOpts.getPassword(), logger, programOpts.getAuthToken(), programOpts.isNotifyCommand()); |
| rac.setFileOutputDirectory(outputDir); |
| rac.setInteractive(programOpts.isInteractive()); |
| for (String key : listeners.keySet()) { |
| for (AdminCommandListener<GfSseInboundEvent> listener : listeners.get(key)) { |
| rac.registerListener(key, listener); |
| } |
| } |
| } |
| } |
| |
| private void initializeAuth() throws CommandException { |
| LoginInfo li = null; |
| |
| try { |
| LoginInfoStore store = LoginInfoStoreFactory.getDefaultStore(); |
| li = store.read(programOpts.getHost(), programOpts.getPort()); |
| if (li == null) { |
| return; |
| } |
| } catch (StoreException se) { |
| logger.finer("Login info could not be read from ~/.asadminpass file"); |
| return; |
| } |
| |
| /* |
| * If we don't have a user name, initialize it from .asadminpass. |
| * In that case, also initialize the password unless it was |
| * already specified (overriding what's in .asadminpass). |
| * |
| * If we already have a user name, and it's the same as what's |
| * in .asadminpass, and we don't have a password, use the password |
| * from .asadminpass. |
| */ |
| if (programOpts.getUser() == null) { |
| // not on command line and in .asadminpass |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Getting user name from ~/.asadminpass: " + li.getUser()); |
| } |
| programOpts.setUser(li.getUser()); |
| if (programOpts.getPassword() == null) { |
| // not in passwordfile and in .asadminpass |
| logger.finer("Getting password from ~/.asadminpass"); |
| programOpts.setPassword(li.getPassword(), ProgramOptions.PasswordLocation.LOGIN_FILE); |
| } |
| } else if (programOpts.getUser().equals(li.getUser())) { |
| if (programOpts.getPassword() == null) { |
| // not in passwordfile and in .asadminpass |
| logger.finer("Getting password from ~/.asadminpass"); |
| programOpts.setPassword(li.getPassword(), ProgramOptions.PasswordLocation.LOGIN_FILE); |
| } |
| } |
| } |
| |
| /* |
| * Initialize a CookieManager so that we can retreive |
| * any cookies included in the reply. These cookies |
| * (e.g. JSESSIONID, JROUTE) are used for CLI session |
| * based routing. |
| */ |
| private void initializeCookieManager() { |
| CookieStore defaultCookieStore = new CookieManager().getCookieStore(); |
| CookieManager manager = new CookieManager(defaultCookieStore, CookiePolicy.ACCEPT_ALL); |
| CookieHandler.setDefault(manager); |
| } |
| |
| /** |
| * Given a command name, return the name of the class that implements that command in the server. |
| */ |
| private static String getCommandClass(String cmdName) { |
| ServiceLocator h = getManHabitat(); |
| String cname = "org.glassfish.api.admin.AdminCommand"; |
| ActiveDescriptor<?> ad = h.getBestDescriptor(BuilderHelper.createNameAndContractFilter(cname, cmdName)); |
| if (ad == null) { |
| return null; |
| } |
| return ad.getImplementation(); |
| } |
| |
| /** |
| * Return a ServiceLocator used just for reading man pages from the modules in the modules directory. |
| */ |
| private static synchronized ServiceLocator getManHabitat() { |
| if (manServiceLocator != null) { |
| return manServiceLocator; |
| } |
| ModulesRegistry registry = new StaticModulesRegistry(getModuleClassLoader()); |
| manServiceLocator = registry.createServiceLocator("default"); |
| return manServiceLocator; |
| } |
| |
| /** |
| * Return a ClassLoader that loads classes from all the modules (jar files) in the <INSTALL_ROOT>/modules directory. |
| */ |
| private static synchronized ClassLoader getModuleClassLoader() { |
| if (moduleClassLoader != null) { |
| return moduleClassLoader; |
| } |
| try { |
| File installDir = new File(System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY)); |
| File modulesDir = new File(installDir, "modules"); |
| moduleClassLoader = new DirectoryClassLoader(modulesDir, CLICommand.class.getClassLoader()); |
| return moduleClassLoader; |
| } catch (IOException ioex) { |
| return null; |
| } |
| } |
| } |