com.sun.enterprise.v3.admin.CommandRunnerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2008-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2017] [Payara Foundation and/or its affiliates]
package com.sun.enterprise.v3.admin;
import com.sun.enterprise.admin.event.AdminCommandEventBrokerImpl;
import com.sun.enterprise.admin.util.*;
import com.sun.enterprise.config.serverbeans.Cluster;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.universal.collections.ManifestUtils;
import com.sun.enterprise.universal.glassfish.AdminCommandResponse;
import com.sun.enterprise.util.AnnotationUtil;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.StringUtils;
import com.sun.enterprise.v3.common.XMLContentActionReporter;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Scope;
import javax.inject.Singleton;
import javax.security.auth.Subject;
import javax.validation.*;
import org.glassfish.admin.payload.PayloadFilesManager;
import org.glassfish.api.ActionReport;
import org.glassfish.api.Param;
import org.glassfish.api.admin.*;
import org.glassfish.api.admin.AdminCommandEventBroker.AdminCommandListener;
import org.glassfish.api.admin.Payload;
import org.glassfish.api.admin.ProcessEnvironment;
import org.glassfish.api.admin.SupplementalCommandExecutor.SupplementalCommand;
import org.glassfish.api.logging.LogHelper;
import org.glassfish.common.util.admin.CommandModelImpl;
import org.glassfish.common.util.admin.ManPageFinder;
import org.glassfish.common.util.admin.MapInjectionResolver;
import org.glassfish.common.util.admin.UnacceptableValueException;
import org.glassfish.config.support.CommandTarget;
import org.glassfish.config.support.GenericCrudCommand;
import org.glassfish.config.support.TargetType;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.internal.api.*;
import org.glassfish.internal.deployment.DeploymentTargetResolver;
import org.glassfish.kernel.KernelLoggerInfo;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.component.*;
import org.jvnet.hk2.config.InjectionManager;
import org.jvnet.hk2.config.InjectionResolver;
import org.jvnet.hk2.config.MessageInterpolatorImpl;
import org.jvnet.hk2.config.UnsatisfiedDependencyException;
/**
* Encapsulates the logic needed to execute a server-side command (for example,
* a descendant of AdminCommand) including injection of argument values into the
* command.
*
* @author dochez
* @author tjquinn
* @author Bill Shannon
*/
@Service
public class CommandRunnerImpl implements CommandRunner {
private static final Logger logger = KernelLoggerInfo.getLogger();
// This is used only for backword compatibility with old behavior
private static final String OLD_PASSWORD_PARAM_PREFIX = "AS_ADMIN_";
private static final InjectionManager injectionMgr = new InjectionManager();
@Inject
private ServiceLocator habitat;
@Inject
private ServerContext sc;
@Inject
private Domain domain;
@Inject
private ServerEnvironment serverEnv;
@Inject
private ProcessEnvironment processEnv;
@Inject
private InstanceStateService state;
@Inject
private AdminCommandLock adminLock;
@Inject @Named("SupplementalCommandExecutorImpl")
SupplementalCommandExecutor supplementalExecutor;
//private final Map, String> commandModelEtagMap = new WeakHashMap, String>();
private final Map commandModelEtagMap = new IdentityHashMap();
@Inject
private CommandSecurityChecker commandSecurityChecker;
private static final LocalStringManagerImpl adminStrings =
new LocalStringManagerImpl(CommandRunnerImpl.class);
private static volatile Validator beanValidator = null;
/**
* Returns an initialized ActionReport instance for the passed type or
* null if it cannot be found.
*
* @param name action report type name
* @return uninitialized action report or null
*/
@Override
public ActionReport getActionReport(String name) {
return habitat.getService(ActionReport.class, name);
}
/**
* Returns the command model for a command name.
*
* @param commandName command name
* @param logger logger to log any error messages
* @return model for this command (list of parameters,etc...),
* or null if command is not found
*/
@Override
public CommandModel getModel(String commandName, Logger logger) {
return getModel(null, commandName, logger);
}
/**
* Returns the command model for a command name.
*
* @param commandName command name
* @param logger logger to log any error messages
* @return model for this command (list of parameters,etc...),
* or null if command is not found
*/
@Override
public CommandModel getModel(String scope, String commandName, Logger logger) {
AdminCommand command;
try {
String commandServiceName = (scope != null) ? scope + commandName : commandName;
command = habitat.getService(AdminCommand.class, commandServiceName);
} catch (MultiException e) {
LogHelper.log(logger, Level.SEVERE, KernelLoggerInfo.cantInstantiateCommand,
e, commandName);
return null;
}
return command == null ? null : getModel(command);
}
@Override
public boolean validateCommandModelETag(AdminCommand command, String eTag) {
if (command == null) {
return true; //Everithing is ok for unexisting command
}
if (eTag == null || eTag.isEmpty()) {
return false;
}
CommandModel model = getModel(command);
return validateCommandModelETag(model, eTag);
}
@Override
public boolean validateCommandModelETag(CommandModel model, String eTag) {
if (model == null) {
return true; //Unexisting model => it is ok (but weard in fact)
}
if (eTag == null || eTag.isEmpty()) {
return false;
}
String actualETag = CachedCommandModel.computeETag(model);
return eTag.equals(actualETag);
}
/**
* Obtain and return the command implementation defined by
* the passed commandName for the null scope.
*
* @param commandName command name as typed by users
* @param report report used to communicate command status back to the user
* @param logger logger to log
* @return command registered under commandName or null if not found
*/
@Override
public AdminCommand getCommand(String commandName,
ActionReport report, Logger logger) {
return getCommand(null, commandName, report, logger);
}
private static Class getScope(Class onMe) {
for (Annotation anno : onMe.getAnnotations()) {
if (anno.annotationType().isAnnotationPresent(Scope.class)) {
return anno.annotationType();
}
}
return null;
}
/**
* Obtain and return the command implementation defined by
* the passed commandName.
*
* @param commandName command name as typed by users
* @param report report used to communicate command status back to the user
* @param logger logger to log
* @return command registered under commandName or null if not found
*/
@Override
public AdminCommand getCommand(String scope, String commandName,
ActionReport report, Logger logger) {
AdminCommand command = null;
String commandServiceName = (scope != null) ? scope + commandName : commandName;
try {
command = habitat.getService(AdminCommand.class, commandServiceName);
} catch (MultiException e) {
report.setFailureCause(e);
}
if (command == null) {
String msg;
if (!ok(commandName)) {
msg = adminStrings.getLocalString("adapter.command.nocommand",
"No command was specified.");
} else {
// this means either a non-existent command or
// an ill-formed command
if (habitat.getServiceHandle(AdminCommand.class, commandServiceName)
== null) // somehow it's in habitat
{
msg = adminStrings.getLocalString("adapter.command.notfound", "Command {0} not found", commandName);
} else {
msg = adminStrings.getLocalString("adapter.command.notcreated",
"Implementation for the command {0} exists in "
+ "the system, but it has some errors, "
+ "check server.log for details", commandName);
}
}
report.setMessage(msg);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
KernelLoggerInfo.getLogger().fine(msg);
return null;
}
Class myScope = getScope(command.getClass());
if (myScope == null) {
String msg = adminStrings.getLocalString("adapter.command.noscope",
"Implementation for the command {0} exists in the "
+ "system,\nbut it has no @Scoped annotation", commandName);
report.setMessage(msg);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
KernelLoggerInfo.getLogger().fine(msg);
command = null;
} else if (Singleton.class.equals(myScope)) {
// check that there are no parameters for this command
CommandModel model = getModel(command);
if (model.getParameters().size() > 0) {
String msg =
adminStrings.getLocalString("adapter.command.hasparams",
"Implementation for the command {0} exists in the "
+ "system,\nbut it's a singleton that also has "
+ "parameters", commandName);
report.setMessage(msg);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
KernelLoggerInfo.getLogger().fine(msg);
command = null;
}
}
return command;
}
@Override
public CommandInvocation getCommandInvocation(String name, ActionReport report, Subject subject) {
return getCommandInvocation(name,report,subject,false);
}
@Override
public CommandInvocation getCommandInvocation(String scope, String name, ActionReport report, Subject subject) {
return getCommandInvocation(scope,name,report,subject,false);
}
/**
* Obtain a new command invocation object for the null scope.
* Command invocations can be configured and used
* to trigger a command execution.
*
* @param name name of the requested command to invoke
* @param report where to place the status of the command execution
* @param subject the Subject under which to execute the command
* @return a new command invocation for that command name
*/
@Override
public CommandInvocation getCommandInvocation(String name,
ActionReport report, Subject subject,boolean isNotify) {
return getCommandInvocation(null, name, report, subject,false);
}
/**
* Obtain a new command invocation object.
* Command invocations can be configured and used
* to trigger a command execution.
*
* @param scope the scope (or name space) for the command
* @param name name of the requested command to invoke
* @param report where to place the status of the command execution
* @param subject the Subject under which to execute the command
* @param isNotify Should notification be enabled
* @return a new command invocation for that command name
*/
@Override
public CommandInvocation getCommandInvocation(String scope, String name,
ActionReport report, Subject subject, boolean isNotify) {
return new ExecutionContext(scope, name, report, subject, isNotify);
}
public static boolean injectParameters(final CommandModel model, final Object injectionTarget,
final InjectionResolver injector,
final ActionReport report) {
if (injectionTarget instanceof GenericCrudCommand) {
GenericCrudCommand c = GenericCrudCommand.class.cast(injectionTarget);
c.setInjectionResolver(injector);
}
// inject
try {
injectionMgr.inject(injectionTarget, injector);
} catch (UnsatisfiedDependencyException e) {
Param param = e.getAnnotation(Param.class);
CommandModel.ParamModel paramModel = null;
for (CommandModel.ParamModel pModel : model.getParameters()) {
if (pModel.getParam().equals(param)) {
paramModel = pModel;
break;
}
}
String errorMsg;
final String usage = getUsageText(model);
if (paramModel != null) {
String paramName = paramModel.getName();
String paramDesc = paramModel.getLocalizedDescription();
if (param.primary()) {
errorMsg = adminStrings.getLocalString("commandrunner.operand.required",
"Operand required.");
} else if (param.password()) {
errorMsg = adminStrings.getLocalString("adapter.param.missing.passwordfile",
"{0} command requires the passwordfile "
+ "parameter containing {1} entry.",
model.getCommandName(), paramName);
} else if (paramDesc != null) {
errorMsg = adminStrings.getLocalString("admin.param.missing",
"{0} command requires the {1} parameter ({2})",
model.getCommandName(), paramName, paramDesc);
} else {
errorMsg = adminStrings.getLocalString("admin.param.missing.nodesc",
"{0} command requires the {1} parameter",
model.getCommandName(), paramName);
}
} else {
errorMsg = adminStrings.getLocalString("admin.param.missing.nofound",
"Cannot find {1} in {0} command model, file a bug",
model.getCommandName(), e.getUnsatisfiedName());
}
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(errorMsg);
report.setFailureCause(e);
ActionReport.MessagePart childPart =
report.getTopMessagePart().addChild();
childPart.setMessage(usage);
return false;
}
catch (MultiException e) {
// If the cause is UnacceptableValueException -- we want the message
// from it. It is wrapped with a less useful Exception.
Exception exception = null;
for (Throwable th : e.getErrors()) {
Throwable cause = th;
while (cause != null) {
if ((cause instanceof UnacceptableValueException) ||
(cause instanceof IllegalArgumentException)) {
exception = (Exception) th;
break;
}
cause = cause.getCause();
}
}
if (exception == null) {
// Not an UnacceptableValueException or IllegalArgumentException
exception = e;
}
logger.log(Level.SEVERE, KernelLoggerInfo.invocationException, exception);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(exception.getMessage());
report.setFailureCause(exception);
ActionReport.MessagePart childPart =
report.getTopMessagePart().addChild();
childPart.setMessage(getUsageText(model));
return false;
}
checkAgainstBeanConstraints(injectionTarget, model.getCommandName());
return true;
}
private static synchronized void initBeanValidator() {
if (beanValidator != null) {
return;
}
ClassLoader cl = System.getSecurityManager() == null ?
Thread.currentThread().getContextClassLoader():
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
try {
Thread.currentThread().setContextClassLoader(Validation.class.getClassLoader());
Configuration configuration = Validation.byDefaultProvider().providerResolver(
new HibernateValidationProviderResolver()
).configure();
ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
ValidatorContext validatorContext = validatorFactory.usingContext();
validatorContext.messageInterpolator(new MessageInterpolatorImpl());
beanValidator = validatorContext.getValidator();
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
private static void checkAgainstBeanConstraints(Object component, String cname) {
initBeanValidator();
Set> constraintViolations = beanValidator.validate(component);
if (constraintViolations == null || constraintViolations.isEmpty()) {
return;
}
StringBuilder msg = new StringBuilder(adminStrings.getLocalString("commandrunner.unacceptableBV",
"Parameters for command {0} violate the following constraints: ",
cname));
boolean addc = false;
String violationMsg = adminStrings.getLocalString("commandrunner.unacceptableBV.reason",
"on parameter [ {1} ] violation reason [ {0} ]");
for (ConstraintViolation cv : constraintViolations) {
if (addc) {
msg.append(", ");
}
msg.append(MessageFormat.format(violationMsg, cv.getMessage(), cv.getPropertyPath()));
addc = true;
}
throw new UnacceptableValueException(msg.toString());
}
/**
* Executes the provided command object.
*
* @param model model of the command (used for logging and reporting)
* @param command the command service to execute
* @param context the AdminCommandcontext that has the payload and report
*/
private ActionReport doCommand(
final CommandModel model,
final AdminCommand command,
final AdminCommandContext context,
final CommandRunnerProgressHelper progressHelper) {
ActionReport report = context.getActionReport();
report.setActionDescription(model.getCommandName() + " AdminCommand");
// We need to set context CL to common CL before executing
// the command. See issue #5596
final Thread thread = Thread.currentThread();
final ClassLoader origCL = thread.getContextClassLoader();
final ClassLoader ccl = sc.getCommonClassLoader();
AdminCommand wrappedCommand = new WrappedAdminCommand(command) {
@Override
public void execute(final AdminCommandContext context) {
try {
if (origCL != ccl) {
thread.setContextClassLoader(ccl);
}
/*
* Execute the command in the security context of the
* previously-authenticated subject.
*/
Subject.doAs(context.getSubject(),
new PrivilegedAction () {
@Override
public Void run() {
command.execute(context);
return null;
}
});
} finally {
if (origCL != ccl) {
thread.setContextClassLoader(origCL);
}
}
}
};
// look for other wrappers using CommandAspect annotation
final AdminCommand otherWrappedCommand = CommandSupport.createWrappers(habitat, model, wrappedCommand, report);
try {
Subject.doAs(context.getSubject(),
new PrivilegedAction () {
@Override
public Void run() {
try {
if (origCL != ccl) {
thread.setContextClassLoader(ccl);
}
otherWrappedCommand.execute(progressHelper.wrapContext4MainCommand(context));
return null;
} finally {
if (origCL != ccl) {
thread.setContextClassLoader(origCL);
}
}
}
});
} catch (Throwable e) {
logger.log(Level.SEVERE, KernelLoggerInfo.invocationException, e);
report.setMessage(e.toString());
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setFailureCause(e);
}
return context.getActionReport();
}
/**
* Get the usage-text of the command.
* Check if .usagetext is defined in LocalString.properties.
* If defined, then use the usagetext from LocalString.properties else
* generate the usagetext from Param annotations in the command class.
*
* @param model command model
* @return usagetext
*/
static String getUsageText(CommandModel model) {
StringBuilder usageText = new StringBuilder();
String usage;
if (ok(usage = model.getUsageText())) {
usageText.append(
adminStrings.getLocalString("adapter.usage", "Usage: "));
usageText.append(usage);
return usageText.toString();
} else {
return generateUsageText(model);
}
}
/**
* Generate the usage-text from the annotated Param in the command class.
*
* @param model command model
* @return generated usagetext
*/
private static String generateUsageText(CommandModel model) {
StringBuffer usageText = new StringBuffer();
usageText.append(
adminStrings.getLocalString("adapter.usage", "Usage: "));
usageText.append(model.getCommandName());
usageText.append(" ");
StringBuffer operand = new StringBuffer();
for (CommandModel.ParamModel pModel : model.getParameters()) {
final Param param = pModel.getParam();
final String paramName =
pModel.getName().toLowerCase(Locale.ENGLISH);
// skip "hidden" options
if (paramName.startsWith("_")) {
continue;
}
// do not want to display password as an option
if (param.password()) {
continue;
}
// do not want to display obsolete options
if (param.obsolete()) {
continue;
}
final boolean optional = param.optional();
final Class ftype = pModel.getType();
Object fvalue = null;
String fvalueString = null;
try {
fvalue = param.defaultValue();
if (fvalue != null) {
fvalueString = fvalue.toString();
}
} catch (Exception e) {
// just leave it as null...
}
// this is a param.
if (param.primary()) {
if (optional) {
operand.append("[").append(paramName).append("] ");
} else {
operand.append(paramName).append(" ");
}
continue;
}
if (optional) {
usageText.append("[");
}
usageText.append("--").append(paramName);
if (ok(param.defaultValue())) {
usageText.append("=").append(param.defaultValue());
} else if (ftype.isAssignableFrom(String.class)) {
// check if there is a default value assigned
if (ok(fvalueString)) {
usageText.append("=").append(fvalueString);
} else {
usageText.append("=").append(paramName);
}
} else if (ftype.isAssignableFrom(Boolean.class)) {
// note: There is no defaultValue for this param. It might
// hava value -- but we don't care -- it isn't an official
// default value.
usageText.append("=").append("true|false");
} else {
usageText.append("=").append(paramName);
}
if (optional) {
usageText.append("] ");
} else {
usageText.append(" ");
}
}
usageText.append(operand);
return usageText.toString();
}
@Override
public BufferedReader getHelp(CommandModel model) throws CommandNotFoundException {
BufferedReader manPage = getManPage(model.getCommandName(), model);
if (manPage != null) {
return manPage;
} else {
StringBuilder hlp = new StringBuilder(256);
StringBuilder part = new StringBuilder(64);
hlp.append("NAME").append(ManifestUtils.EOL);
part.append(model.getCommandName());
String description = model.getLocalizedDescription();
if (ok(description)) {
part.append(" - ").append(model.getLocalizedDescription());
}
hlp.append(formatGeneratedManPagePart(part.toString(), 5, 65)).append(ManifestUtils.EOL);
//Usage
hlp.append(ManifestUtils.EOL).append("SYNOPSIS").append(ManifestUtils.EOL);
hlp.append(formatGeneratedManPagePart(getUsageText(model), 5, 65));
//Options
hlp.append(ManifestUtils.EOL).append(ManifestUtils.EOL);
hlp.append("OPTIONS").append(ManifestUtils.EOL);
CommandModel.ParamModel operand = null;
for (CommandModel.ParamModel paramModel : model.getParameters()) {
Param param = paramModel.getParam();
if (param == null || paramModel.getName().startsWith("_") ||
param.password() || param.obsolete()) {
continue;
}
if (param.primary()) {
operand = paramModel;
continue;
}
hlp.append(" --").append(paramModel.getName().toLowerCase(Locale.ENGLISH));
hlp.append(ManifestUtils.EOL);
if (ok(param.shortName())) {
hlp.append(" -").append(param.shortName().toLowerCase(Locale.ENGLISH));
hlp.append(ManifestUtils.EOL);
}
String descr = paramModel.getLocalizedDescription();
if (ok(descr)) {
hlp.append(formatGeneratedManPagePart(descr, 9, 65));
}
hlp.append(ManifestUtils.EOL);
}
//Operand
if (operand != null) {
hlp.append("OPERANDS").append(ManifestUtils.EOL);
hlp.append(" ").append(operand.getName().toLowerCase(Locale.ENGLISH));
hlp.append(ManifestUtils.EOL);
String descr = operand.getLocalizedDescription();
if (ok(descr)) {
hlp.append(formatGeneratedManPagePart(descr, 9, 65));
}
}
return new BufferedReader(new StringReader(hlp.toString()));
}
}
private String formatGeneratedManPagePart(String part, int prefix, int lineLength) {
if (part == null) {
return null;
}
if (prefix < 0) {
prefix = 0;
}
//Prepare prefix
StringBuilder sb = new StringBuilder(prefix);
for (int i = 0; i < prefix; i++) {
sb.append(' ');
}
String prfx = sb.toString();
StringBuilder result = new StringBuilder(part.length() + prefix + 16);
boolean newLine = true;
boolean lastWasCR = false;
int counter = 0;
for (int i = 0; i < part.length(); i++) {
boolean addPrefix = newLine;
char ch = part.charAt(i);
switch (ch) {
case '\n':
if (!lastWasCR) {
newLine = true;
} else {
lastWasCR = false;
}
counter = 0;
break;
case '\r':
newLine = true;
lastWasCR = true;
counter = 0;
break;
default:
newLine = false;
lastWasCR = false;
counter++;
}
if (addPrefix && !newLine) {
result.append(prfx);
counter += prefix;
}
result.append(ch);
if (lineLength > 0 && counter >= lineLength && !newLine) {
newLine = true;
result.append(ManifestUtils.EOL);
counter = 0;
}
}
return result.toString();
}
public void getHelp(AdminCommand command, ActionReport report) {
CommandModel model = getModel(command);
report.setActionDescription(model.getCommandName() + " help");
// XXX - this is a hack for now. if the request mapped to an
// XMLContentActionReporter, that means we want the command metadata.
if (report instanceof XMLContentActionReporter) {
getMetadata(command, model, report);
} else {
report.setMessage(model.getCommandName() + " - "
+ model.getLocalizedDescription());
report.getTopMessagePart().addProperty("SYNOPSIS",
encodeManPage(new BufferedReader(new StringReader(
getUsageText(model)))));
for (CommandModel.ParamModel param : model.getParameters()) {
addParamUsage(report, param);
}
report.setActionExitCode(ActionReport.ExitCode.SUCCESS);
}
}
/**
* Return the metadata for the command. We translate the parameter
* and operand information to parts and properties of the ActionReport,
* which will be translated to XML elements and attributes by the
* XMLContentActionReporter.
*
* @param command the command
* @param model the CommandModel describing the command
* @param report the (assumed to be) XMLContentActionReporter
*/
private void getMetadata(AdminCommand command, CommandModel model,
ActionReport report) {
ActionReport.MessagePart top = report.getTopMessagePart();
ActionReport.MessagePart cmd = top.addChild();
//
cmd.setChildrenType("command");
cmd.addProperty("name", model.getCommandName());
if (model.unknownOptionsAreOperands()) {
cmd.addProperty("unknown-options-are-operands", "true");
}
String usage = model.getUsageText();
if (ok(usage)) {
cmd.addProperty("usage", usage);
}
CommandModel.ParamModel primary = null;
// for each parameter add
//
for (CommandModel.ParamModel p : model.getParameters()) {
Param param = p.getParam();
if (param.primary()) {
primary = p;
continue;
}
ActionReport.MessagePart ppart = cmd.addChild();
ppart.setChildrenType("option");
ppart.addProperty("name", p.getName());
ppart.addProperty("type", typeOf(p));
ppart.addProperty("optional", Boolean.toString(param.optional()));
if (param.obsolete()) // don't include it if it's false
{
ppart.addProperty("obsolete", "true");
}
String paramDesc = p.getLocalizedDescription();
if (ok(paramDesc)) {
ppart.addProperty("description", paramDesc);
}
if (ok(param.shortName())) {
ppart.addProperty("short", param.shortName());
}
if (ok(param.defaultValue())) {
ppart.addProperty("default", param.defaultValue());
}
if (ok(param.acceptableValues())) {
ppart.addProperty("acceptable-values", param.acceptableValues());
}
if (ok(param.alias())) {
ppart.addProperty("alias", param.alias());
}
}
// are operands allowed?
if (primary != null) {
// for the operand(s), add
//
ActionReport.MessagePart primpart = cmd.addChild();
primpart.setChildrenType("operand");
primpart.addProperty("name", primary.getName());
primpart.addProperty("type", typeOf(primary));
primpart.addProperty("min",
primary.getParam().optional() ? "0" : "1");
primpart.addProperty("max", primary.getParam().multiple()
? "unlimited" : "1");
String desc = primary.getLocalizedDescription();
if (ok(desc)) {
primpart.addProperty("description", desc);
}
}
}
/**
* Map a Java type to one of the types supported by the asadmin client.
* Currently supported types are BOOLEAN, FILE, PROPERTIES, PASSWORD, and
* STRING. (All of which should be defined constants on some class.)
*
* @param p the Java type
* @return the string representation of the asadmin type
*/
private static String typeOf(CommandModel.ParamModel p) {
Class t = p.getType();
if (t == Boolean.class || t == boolean.class) {
return "BOOLEAN";
} else if (t == File.class || t == File[].class) {
return "FILE";
} else if (t == Properties.class) { // XXX - allow subclass?
return "PROPERTIES";
} else if (p.getParam().password()) {
return "PASSWORD";
} else {
return "STRING";
}
}
/**
* Return an InputStream for the man page for the named command.
*/
public static BufferedReader getManPage(String commandName,
CommandModel model) {
Class clazz = model.getCommandClass();
if (clazz == null) {
return null;
}
return ManPageFinder.getCommandManPage(commandName, clazz.getName(),
Locale.getDefault(), clazz.getClassLoader(), logger);
}
private void addParamUsage(
ActionReport report,
CommandModel.ParamModel model) {
Param param = model.getParam();
if (param != null) {
// this is a param.
String paramName = model.getName().toLowerCase(Locale.ENGLISH);
// skip "hidden" options
if (paramName.startsWith("_")) {
return;
}
// do not want to display password in the usage
if (param.password()) {
return;
}
// do not want to display obsolete options
if (param.obsolete()) {
return;
}
if (param.primary()) {
// if primary then it's an operand
report.getTopMessagePart().addProperty(paramName + "_operand",
model.getLocalizedDescription());
} else {
report.getTopMessagePart().addProperty(paramName,
model.getLocalizedDescription());
}
}
}
private static boolean ok(String s) {
return s != null && s.length() > 0;
}
/**
* Validate the parameters with the Param annotation. If parameter is
* not defined as a Param annotation then it's an invalid option.
* If parameter's key is "DEFAULT" then it's a operand.
*
* @param model command model
* @param parameters parameters from URL
*
*/
static void validateParameters(final CommandModel model,
final ParameterMap parameters) throws MultiException {
ParameterMap adds = null; // renamed password parameters
// loop through parameters and make sure they are
// part of the Param declared field
for (Map.Entry> entry : parameters.entrySet()) {
String key = entry.getKey();
// to do, we should validate meta-options differently.
if (key.equals("DEFAULT")) {
continue;
}
// help and Xhelp are meta-options that are handled specially
if (key.equals("help") || key.equals("Xhelp") || key.equals("notify")) {
continue;
}
if (key.startsWith(OLD_PASSWORD_PARAM_PREFIX)) {
// This is an old prefixed password parameter being passed in.
// Strip the prefix and lowercase the name
key = key.substring(OLD_PASSWORD_PARAM_PREFIX.length()).toLowerCase(Locale.ENGLISH);
if (adds == null) adds = new ParameterMap();
adds.add(key, entry.getValue().get(0));
}
// check if key is a valid Param Field
boolean validOption = false;
// loop through the Param field in the command class
// if either field name or the param name is equal to
// key then it's a valid option
for (CommandModel.ParamModel pModel : model.getParameters()) {
validOption = pModel.isParamId(key);
if (validOption) {
break;
}
}
if (!validOption) {
throw new MultiException(new IllegalArgumentException(" Invalid option: " + key));
}
}
parameters.mergeAll(adds);
}
/**
* Check if the variable, "skipParamValidation" is defined in the command
* class. If defined and set to true, then parameter validation will be
* skipped from that command.
* This is used mostly for command referencing. For example the
* list-applications command references list-components command and you
* don't want to define the same params from the class that implements
* list-components.
*
* @param command - AdminCommand class
* @return true if to skip param validation, else return false.
*/
static boolean skipValidation(AdminCommand command) {
try {
final Field f =
command.getClass().getDeclaredField("skipParamValidation");
AccessController.doPrivileged(new PrivilegedAction