/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2008-2017 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://oss.oracle.com/licenses/CDDL+GPL-1.1
* or 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 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.
*/
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<Class<? extends AdminCommand>, String> commandModelEtagMap = new WeakHashMap<Class<? extends AdminCommand>, String>();
private final Map<NameCommandClassPair, String> commandModelEtagMap = new IdentityHashMap<NameCommandClassPair, String>();
@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<? extends Annotation> 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<? extends Annotation> 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<Param> 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<ClassLoader>() {
@Override
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
try {
Thread.currentThread().setContextClassLoader(org.hibernate.validator.HibernateValidator.class.getClassLoader());
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
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<ConstraintViolation<Object>> 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<Void> () {
@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<Void> () {
@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 <command-name>.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();
// <command name="name">
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
// <option name="name" type="type" short="s" default="default"
// acceptable-values="list"/>
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
// <operand type="type" min="0/1" max="1"/>
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<String, List<String>> 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<Object>() {
@Override
public Object run() {
f.setAccessible(true);
return null;
}
});
if (f.getType().isAssignableFrom(boolean.class)) {
return f.getBoolean(command);
}
} catch (NoSuchFieldException e) {
return false;
} catch (IllegalAccessException e) {
return false;
}
//all else return false
return false;
}
private static String encodeManPage(BufferedReader br) {
if (br == null) {
return null;
}
try {
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append(ManifestUtils.EOL_TOKEN);
}
return sb.toString();
} catch (Exception ex) {
return null;
} finally {
try {
br.close();
} catch (IOException ioex) {
}
}
}
private static CommandModel getModel(AdminCommand command) {
if (command instanceof CommandModelProvider) {
return ((CommandModelProvider) command).getModel();
} else {
return new CommandModelImpl(command.getClass());
}
}
/**
* Called from ExecutionContext.execute.
*/
private void doCommand(ExecutionContext inv, AdminCommand command,
final Subject subject, final Job job) {
boolean fromCheckpoint = job != null &&
(job.getState() == AdminCommandState.State.REVERTING ||
job.getState() == AdminCommandState.State.FAILED_RETRYABLE);
CommandModel model;
try {
CommandModelProvider c = CommandModelProvider.class.cast(command);
model = c.getModel();
} catch (ClassCastException e) {
model = new CommandModelImpl(command.getClass());
}
UploadedFilesManager ufm = null;
ActionReport report = inv.report();
if (!fromCheckpoint) {
report.setActionDescription(model.getCommandName() + " command");
report.setActionExitCode(ActionReport.ExitCode.SUCCESS);
}
ParameterMap parameters;
final AdminCommandContext context = new AdminCommandContextImpl(
logger, report, inv.inboundPayload(), inv.outboundPayload(),
job.getEventBroker(),
job.getId());
context.setSubject(subject);
List<RuntimeType> runtimeTypes = new ArrayList<RuntimeType>();
FailurePolicy fp = null;
Set<CommandTarget> targetTypesAllowed = new HashSet<CommandTarget>();
ActionReport.ExitCode preSupplementalReturn = ActionReport.ExitCode.SUCCESS;
ActionReport.ExitCode postSupplementalReturn = ActionReport.ExitCode.SUCCESS;
CommandRunnerProgressHelper progressHelper =
new CommandRunnerProgressHelper(command, model.getCommandName(), job, inv.progressStatusChild);
// If this glassfish installation does not have stand alone instances / clusters at all, then
// lets not even look Supplemental command and such. A small optimization
boolean doReplication = false;
if ((domain.getServers().getServer().size() > 1) || (!domain.getClusters().getCluster().isEmpty())) {
doReplication = true;
} else {
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.devmode",
"The GlassFish environment does not have any clusters or instances present; Replication is turned off"));
}
try {
//Get list of suplemental commands
Collection<SupplementalCommand> suplementalCommands =
supplementalExecutor.listSuplementalCommands(model.getCommandName());
try {
/*
* Extract any uploaded files and build a map from parameter names
* to the corresponding extracted, uploaded file.
*/
ufm = new UploadedFilesManager(inv.report, logger,
inv.inboundPayload());
if (inv.typedParams() != null) {
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.delegatedcommand",
"This command is a delegated command. Dynamic reconfiguration will be bypassed"));
InjectionResolver<Param> injectionTarget =
new DelegatedInjectionResolver(model, inv.typedParams(),
ufm.optionNameToFileMap());
if (injectParameters(model, command, injectionTarget, report)) {
inv.setReport(doCommand(model, command, context, progressHelper));
}
return;
}
parameters = inv.parameters();
if (parameters == null) {
// no parameters, pass an empty collection
parameters = new ParameterMap();
}
if (isSet(parameters, "help") || isSet(parameters, "Xhelp")) {
BufferedReader in = getManPage(model.getCommandName(), model);
String manPage = encodeManPage(in);
if (manPage != null && isSet(parameters, "help")) {
inv.report().getTopMessagePart().addProperty("MANPAGE", manPage);
} else {
report.getTopMessagePart().addProperty(
AdminCommandResponse.GENERATED_HELP, "true");
getHelp(command, report);
}
return;
}
try {
if (!fromCheckpoint && !skipValidation(command)) {
validateParameters(model, parameters);
}
} catch (MultiException e) {
// If the cause is UnacceptableValueException -- we want the message
// from it. It is wrapped with a less useful Exception.
Exception exception = e;
for (Throwable cause : e.getErrors()) {
if (cause != null
&& (cause instanceof UnacceptableValueException)) {
// throw away the wrapper.
exception = (Exception) cause;
break;
}
}
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;
}
// initialize the injector and inject
MapInjectionResolver injectionMgr =
new MapInjectionResolver(model, parameters,
ufm.optionNameToFileMap());
injectionMgr.setContext(context);
if (!injectParameters(model, command, injectionMgr, report)) {
return;
}
CommandSupport.init(habitat, command, context, job);
/*
* Now that parameters have been injected into the command object,
* decide if the current Subject should be permitted to execute
* the command. We need to wait until after injection is done
* because the class might implement its own authorization check
* and that logic might need the injected values.
*/
final Map<String,Object> env = buildEnvMap(parameters);
try {
if ( ! commandSecurityChecker.authorize(context.getSubject(), env, command, context)) {
/*
* If the command class tried to prepare itself but
* could not then the return is false and the command has
* set the action report accordingly. Don't process
* the command further and leave the action report alone.
*/
return;
}
} catch (SecurityException ex) {
report.setFailureCause(ex);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.noauth",
"User is not authorized for this command"));
return;
} catch (Exception ex) {
report.setFailureCause(ex);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.errAuth",
"Error during authorization"));
return;
}
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.injectiondone",
"Parameter mapping, validation, injection completed successfully; Starting paramater injection"));
// Read cluster annotation attributes
org.glassfish.api.admin.ExecuteOn clAnnotation = model.getClusteringAttributes();
if (clAnnotation == null) {
runtimeTypes.add(RuntimeType.DAS);
runtimeTypes.add(RuntimeType.INSTANCE);
fp = FailurePolicy.Error;
} else {
if (clAnnotation.value().length == 0) {
runtimeTypes.add(RuntimeType.DAS);
runtimeTypes.add(RuntimeType.INSTANCE);
} else {
runtimeTypes.addAll(Arrays.asList(clAnnotation.value()));
}
if (clAnnotation.ifFailure() == null) {
fp = FailurePolicy.Error;
} else {
fp = clAnnotation.ifFailure();
}
}
TargetType tgtTypeAnnotation = command.getClass().getAnnotation(TargetType.class);
//@ExecuteOn(RuntimeType.SINGLE_INSTANCE) cannot be combined with
//@TargetType since we do not want to replicate the command
if (runtimeTypes.contains(RuntimeType.SINGLE_INSTANCE)) {
if (tgtTypeAnnotation != null) {
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.targettype.unallowed",
"Target type is not allowed on single instance command {0} ,"
, model.getCommandName()));
return;
}
//Do not replicate the command when there is
//@ExecuteOn(RuntimeType.SINGLE_INSTANCE)
doReplication = false;
}
String targetName = parameters.getOne("target");
if (targetName == null || model.getModelFor("target").getParam().obsolete()) {
if (command instanceof DeploymentTargetResolver) {
targetName = ((DeploymentTargetResolver) command).getTarget(parameters);
} else {
targetName = "server";
}
}
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.target",
"@ExecuteOn parsing and default settings done; Current target is {0}", targetName));
if (serverEnv.isDas()) {
//Do not replicate this command if it has @ExecuteOn(RuntimeType.SINGLE_INSTANCE)
//and the user is authorized to execute on DAS
// TODO add authorization check
/*if (runtimeTypes.contains(RuntimeType.SINGLE_INSTANCE)) {
//If authorization fails
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.das.unallowed",
"Not authorized to execute command {0} on DAS"
, model.getCommandName()));
progressHelper.complete(context);
return;
}*/
// Check if the command allows this target type; first read the annotation
//TODO : See is @TargetType can also be moved to the CommandModel
if (tgtTypeAnnotation != null) {
targetTypesAllowed.addAll(Arrays.asList(tgtTypeAnnotation.value()));
}
//If not @TargetType, default it
if (targetTypesAllowed.isEmpty()) {
targetTypesAllowed.add(CommandTarget.DAS);
targetTypesAllowed.add(CommandTarget.STANDALONE_INSTANCE);
targetTypesAllowed.add(CommandTarget.CLUSTER);
targetTypesAllowed.add(CommandTarget.CONFIG);
}
// If the target is "server" and the command is not marked for DAS,
// add DAS to RuntimeTypes; This is important because those class of CLIs that
// do not always have to be run on DAS followed by applicable instances
// will have @ExecuteOn(RuntimeType.INSTANCE) and they have to be run on DAS
// ONLY if the target is "server"
if (CommandTarget.DAS.isValid(habitat, targetName)
&& !runtimeTypes.contains(RuntimeType.DAS)) {
runtimeTypes.add(RuntimeType.DAS);
}
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.runtimeTypes",
"RuntimeTypes are: {0}", runtimeTypes.toString()));
logger.fine(adminStrings.getLocalString("dynamicreconfiguration,diagnostics.targetTypes",
"TargetTypes are: {0}", targetTypesAllowed.toString()));
// Check if the target is valid
//Is there a server or a cluster or a config with given name ?
if ((!CommandTarget.DOMAIN.isValid(habitat, targetName))
&& (domain.getServerNamed(targetName) == null)
&& (domain.getClusterNamed(targetName) == null)
&& (domain.getConfigNamed(targetName) == null)) {
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.invalidtarget",
"Unable to find a valid target with name {0}", targetName));
return;
}
//Does this command allow this target type
boolean isTargetValidType = false;
Iterator<CommandTarget> it = targetTypesAllowed.iterator();
while (it.hasNext()) {
if (it.next().isValid(habitat, targetName)) {
isTargetValidType = true;
break;
}
}
if (!isTargetValidType) {
StringBuilder validTypes = new StringBuilder();
it = targetTypesAllowed.iterator();
while (it.hasNext()) {
validTypes.append(it.next().getDescription()).append(", ");
}
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.invalidtargettype",
"Target {0} is not a supported type. Command {1} supports these types of targets only : {2}",
targetName, model.getCommandName(), validTypes.toString()));
return;
}
//If target is a clustered instance and the allowed types does not allow operations on clustered
//instance, return error
if ((CommandTarget.CLUSTERED_INSTANCE.isValid(habitat, targetName))
&& (!targetTypesAllowed.contains(CommandTarget.CLUSTERED_INSTANCE))) {
Cluster c = domain.getClusterForInstance(targetName);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.instanceopnotallowed",
"The {0} command is not allowed on instance {1} because it is part of cluster {2}",
model.getCommandName(), targetName, c.getName()));
return;
}
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.replicationvalidationdone",
"All @ExecuteOn attribute and type validation completed successfully. Starting replication stages"));
}
/**
* We're finally ready to actually execute the command instance.
* Acquire the appropriate lock.
*/
Lock lock = null;
boolean lockTimedOut = false;
try {
// XXX: The owner of the lock should not be hardcoded. The
// value is not used yet.
lock = adminLock.getLock(command, "asadmin");
//Set there progress statuses
if (!fromCheckpoint) {
for (SupplementalCommand supplementalCommand : suplementalCommands) {
progressHelper.addProgressStatusToSupplementalCommand(supplementalCommand);
}
}
// If command is undoable, then invoke prepare method
if (command instanceof UndoableCommand) {
UndoableCommand uCmd = (UndoableCommand) command;
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.prepareunodable",
"Command execution stage 1 : Calling prepare for undoable command {0}", inv.name()));
if (!uCmd.prepare(context, parameters).equals(ActionReport.ExitCode.SUCCESS)) {
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.errorinprepare",
"The command {0} cannot be completed because the preparation for the command failed "
+ "indicating potential issues : {1}", model.getCommandName(), report.getMessage()));
return;
}
}
ClusterOperationUtil.clearInstanceList();
// Run Supplemental commands that have to run before this command on this instance type
if (!fromCheckpoint) {
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.presupplemental",
"Command execution stage 2 : Call pre supplemental commands for {0}", inv.name()));
preSupplementalReturn = supplementalExecutor.execute(suplementalCommands,
Supplemental.Timing.Before, context, parameters, ufm.optionNameToFileMap());
if (preSupplementalReturn.equals(ActionReport.ExitCode.FAILURE)) {
report.setActionExitCode(preSupplementalReturn);
if (!StringUtils.ok(report.getTopMessagePart().getMessage())) {
report.setMessage(adminStrings.getLocalString("commandrunner.executor.supplementalcmdfailed",
"A supplemental command failed; cannot proceed further"));
}
return;
}
}
//Run main command if it is applicable for this instance type
if ((runtimeTypes.contains(RuntimeType.ALL))
|| (serverEnv.isDas() &&
(CommandTarget.DOMAIN.isValid(habitat, targetName) || runtimeTypes.contains(RuntimeType.DAS)))
|| runtimeTypes.contains(RuntimeType.SINGLE_INSTANCE)
|| (serverEnv.isInstance() && runtimeTypes.contains(RuntimeType.INSTANCE))) {
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.maincommand",
"Command execution stage 3 : Calling main command implementation for {0}", inv.name()));
report = doCommand(model, command, context, progressHelper);
inv.setReport(report);
}
if (!FailurePolicy.applyFailurePolicy(fp,
report.getActionExitCode()).equals(ActionReport.ExitCode.FAILURE)) {
//Run Supplemental commands that have to be run after this command on this instance type
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.postsupplemental",
"Command execution stage 4 : Call post supplemental commands for {0}", inv.name()));
postSupplementalReturn = supplementalExecutor.execute(suplementalCommands,
Supplemental.Timing.After, context, parameters, ufm.optionNameToFileMap());
if (postSupplementalReturn.equals(ActionReport.ExitCode.FAILURE)) {
report.setActionExitCode(postSupplementalReturn);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.supplementalcmdfailed",
"A supplemental command failed; cannot proceed further"));
return;
}
}
} catch (AdminCommandLockTimeoutException ex) {
lockTimedOut = true;
String lockTime = formatSuspendDate(ex.getTimeOfAcquisition());
String logMsg = "Command: " + model.getCommandName()
+ " failed to acquire a command lock. REASON: time out "
+ "(current lock acquired on " + lockTime + ")";
String msg = adminStrings.getLocalString("lock.timeout",
"Command timed out. Unable to acquire a lock to access "
+ "the domain. Another command acquired exclusive access "
+ "to the domain on {0}. Retry the command at a later "
+ "time.", lockTime);
report.setMessage(msg);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
} catch (AdminCommandLockException ex) {
lockTimedOut = true;
String lockTime = formatSuspendDate(ex.getTimeOfAcquisition());
String lockMsg = ex.getMessage();
String logMsg;
logMsg = "Command: " + model.getCommandName()
+ " was blocked. The domain was suspended by a "
+ "user on:" + lockTime;
if (lockMsg != null && !lockMsg.isEmpty()) {
logMsg += " Reason: " + lockMsg;
}
String msg = adminStrings.getLocalString("lock.notacquired",
"The command was blocked. The domain was suspended by "
+ "a user on {0}.", lockTime);
if (lockMsg != null && !lockMsg.isEmpty()) {
msg += " "
+ adminStrings.getLocalString("lock.reason", "Reason:")
+ " " + lockMsg;
}
report.setMessage(msg);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
} finally {
// command is done, release the lock
if (lock != null && lockTimedOut == false) {
lock.unlock();
}
}
} catch (Exception ex) {
logger.log(Level.SEVERE, KernelLoggerInfo.invocationException, ex);
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
report.setMessage(ex.getMessage());
report.setFailureCause(ex);
ActionReport.MessagePart childPart =
report.getTopMessagePart().addChild();
childPart.setMessage(getUsageText(model));
return;
}
/*
* Command execution completed; If this is DAS and the command succeeded,
* time to replicate; At this point we will get the appropriate ClusterExecutor
* and give it complete control; We will let the executor take care all considerations
* (like FailurePolicy settings etc)
* and just give the final execution results which we will set as is in the Final
* Action report returned to the caller.
*/
if (processEnv.getProcessType().isEmbedded()) {
return;
}
if (preSupplementalReturn == ActionReport.ExitCode.WARNING
|| postSupplementalReturn == ActionReport.ExitCode.WARNING) {
report.setActionExitCode(ActionReport.ExitCode.WARNING);
}
if (doReplication
&& (!FailurePolicy.applyFailurePolicy(fp, report.getActionExitCode()).equals(ActionReport.ExitCode.FAILURE))
&& (serverEnv.isDas())
&& (runtimeTypes.contains(RuntimeType.INSTANCE) || runtimeTypes.contains(RuntimeType.ALL))) {
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.startreplication",
"Command execution stages completed on DAS; Starting replication on remote instances"));
ClusterExecutor executor = null;
// This try-catch block is a fix for 13838
try {
if (model.getClusteringAttributes() != null && model.getClusteringAttributes().executor() != null) {
executor = habitat.getService(model.getClusteringAttributes().executor());
} else {
executor = habitat.getService(ClusterExecutor.class, "GlassFishClusterExecutor");
}
} catch (UnsatisfiedDependencyException usdepex) {
logger.log(Level.WARNING, KernelLoggerInfo.cantGetClusterExecutor, usdepex);
}
if (executor != null) {
report.setActionExitCode(executor.execute(model.getCommandName(), command, context, parameters));
if (report.getActionExitCode().equals(ActionReport.ExitCode.FAILURE)) {
report.setMessage(adminStrings.getLocalString("commandrunner.executor.errorwhilereplication",
"An error occurred during replication"));
} else {
if (!FailurePolicy.applyFailurePolicy(fp,
report.getActionExitCode()).equals(ActionReport.ExitCode.FAILURE)) {
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.afterreplsupplemental",
"Command execution stage 5 : Call post-replication supplemental commands for {0}", inv.name()));
ActionReport.ExitCode afterReplicationSupplementalReturn = supplementalExecutor.execute(suplementalCommands,
Supplemental.Timing.AfterReplication, context, parameters, ufm.optionNameToFileMap());
if (afterReplicationSupplementalReturn.equals(ActionReport.ExitCode.FAILURE)) {
report.setActionExitCode(afterReplicationSupplementalReturn);
report.setMessage(adminStrings.getLocalString("commandrunner.executor.supplementalcmdfailed",
"A supplemental command failed; cannot proceed further"));
return;
}
}
}
}
}
if (report.getActionExitCode().equals(ActionReport.ExitCode.FAILURE)) {
// If command is undoable, then invoke undo method method
if (command instanceof UndoableCommand) {
UndoableCommand uCmd = (UndoableCommand) command;
logger.fine(adminStrings.getLocalString("dynamicreconfiguration.diagnostics.undo",
"Command execution failed; calling undo() for command {0}", inv.name()));
uCmd.undo(context, parameters, ClusterOperationUtil.getCompletedInstances());
}
} else {
//TODO : Is there a better way of doing this ? Got to look into it
if ("_register-instance".equals(model.getCommandName())) {
state.addServerToStateService(parameters.getOne("DEFAULT"));
}
if ("_unregister-instance".equals(model.getCommandName())) {
state.removeInstanceFromStateService(parameters.getOne("DEFAULT"));
}
}
} finally {
if (ufm != null) {
ufm.close();
}
}
}
private Map<String,Object> buildEnvMap(final ParameterMap params) {
final Map<String,Object> result = new HashMap<String,Object>();
for (Map.Entry<String,List<String>> entry : params.entrySet()) {
final List<String> values = entry.getValue();
if (values != null && values.size() > 0) {
result.put(entry.getKey(), values.get(0));
}
}
return result;
}
public void executeFromCheckpoint(JobManager.Checkpoint checkpoint, boolean revert, AdminCommandEventBroker eventBroker) {
ExecutionContext ec = new ExecutionContext(null, null, null, null,false);
ec.executeFromCheckpoint(checkpoint, revert, eventBroker);
}
/*
* Some private classes used in the implementation of CommandRunner.
*/
/**
* ExecutionContext is a CommandInvocation, which
* defines a command excecution context like the requested
* name of the command to execute, the parameters of the command, etc.
*/
class ExecutionContext implements CommandInvocation {
private class NameListerPair {
private String nameRegexp;
private AdminCommandEventBroker.AdminCommandListener listener;
public NameListerPair(String nameRegexp, AdminCommandListener listener) {
this.nameRegexp = nameRegexp;
this.listener = listener;
}
}
protected String scope;
protected String name;
protected ActionReport report;
protected ParameterMap params;
protected CommandParameters paramObject;
protected Payload.Inbound inbound;
protected Payload.Outbound outbound;
protected Subject subject;
protected ProgressStatus progressStatusChild;
protected boolean isManagedJob;
protected boolean isNotify;
private List<NameListerPair> nameListerPairs = new ArrayList<NameListerPair>();
private ExecutionContext(String scope, String name, ActionReport report, Subject subject, boolean isNotify) {
this.scope = scope;
this.name = name;
this.report = report;
this.subject = subject;
this.isNotify = isNotify;
}
@Override
public CommandInvocation parameters(CommandParameters paramObject) {
this.paramObject = paramObject;
return this;
}
@Override
public CommandInvocation parameters(ParameterMap params) {
this.params = params;
return this;
}
@Override
public CommandInvocation inbound(Payload.Inbound inbound) {
this.inbound = inbound;
return this;
}
@Override
public CommandInvocation outbound(Payload.Outbound outbound) {
this.outbound = outbound;
return this;
}
@Override
public CommandInvocation listener(String nameRegexp, AdminCommandEventBroker.AdminCommandListener listener) {
nameListerPairs.add(new NameListerPair(nameRegexp, listener));
return this;
}
@Override
public CommandInvocation progressStatusChild(ProgressStatus ps) {
this.progressStatusChild = ps;
return this;
}
@Override
public CommandInvocation managedJob() {
this.isManagedJob = true;
return this;
}
@Override
public void execute() {
execute(null);
}
private ParameterMap parameters() {
return params;
}
private CommandParameters typedParams() {
return paramObject;
}
private String name() {
return name;
}
private String scope() {
return scope;
}
@Override
public ActionReport report() {
return report;
}
private void setReport(ActionReport ar) {
report = ar;
}
private Payload.Inbound inboundPayload() {
return inbound;
}
private Payload.Outbound outboundPayload() {
return outbound;
}
private void executeFromCheckpoint(JobManager.Checkpoint checkpoint, boolean revert, AdminCommandEventBroker eventBroker) {
Job job = checkpoint.getJob();
if (subject == null) {
subject = checkpoint.getContext().getSubject();
}
parameters(job.getParameters());
AdminCommandContext context = checkpoint.getContext();
this.report = context.getActionReport();
this.inbound = context.getInboundPayload();
this.outbound = context.getOutboundPayload();
this.scope = job.getScope();
this.name = job.getName();
if (eventBroker == null) {
eventBroker = job.getEventBroker() == null ? new AdminCommandEventBrokerImpl() : job.getEventBroker();
}
((AdminCommandInstanceImpl) job).setEventBroker(eventBroker);
((AdminCommandInstanceImpl) job).setState(revert ? AdminCommandState.State.REVERTING : AdminCommandState.State.RUNNING_RETRYABLE);
JobManager jobManager = habitat.getService(JobManagerService.class);
jobManager.registerJob(job);
//command
AdminCommand command = checkpoint.getCommand();
if (command == null) {
command = getCommand(job.getScope(), job.getName(), report(), logger);
if (command == null) {
return;
}
}
//execute
CommandRunnerImpl.this.doCommand(this, command, subject, job);
job.complete(report(), outboundPayload());
if (progressStatusChild != null) {
progressStatusChild.complete();
}
CommandSupport.done(habitat, command, job);
}
@Override
public void execute(AdminCommand command) {
if (command == null) {
command = getCommand(scope(), name(), report(), logger);
if (command == null) {
return;
}
}
/*
* The caller should have set the subject explicitly. In case
* it didn't, try setting it from the current access controller context
* since the command framework will have set that before invoking
* the original command's execute method.
*/
if (subject == null) {
subject = AccessController.doPrivileged(new PrivilegedAction<Subject>() {
@Override
public Subject run() {
return Subject.getSubject(AccessController.getContext());
}
});
}
if(!isManagedJob) {
isManagedJob = AnnotationUtil.presentTransitive(ManagedJob.class, command.getClass());
}
JobCreator jobCreator = null;
JobManager jobManager = null;
jobCreator = habitat.getService(JobCreator.class,scope+"job-creator");
jobManager = habitat.getService(JobManagerService.class);
if (jobCreator == null ) {
jobCreator = habitat.getService(JobCreatorService.class);
}
Job job = null;
if (isManagedJob) {
job = jobCreator.createJob(jobManager.getNewId(), scope(), name(), subject, isManagedJob, parameters());
} else {
job = jobCreator.createJob(null, scope(), name(), subject, isManagedJob, parameters());
}
//Register the brokers else the detach functionality will not work
for (NameListerPair nameListerPair : nameListerPairs) {
job.getEventBroker().registerListener(nameListerPair.nameRegexp, nameListerPair.listener);
}
if (isManagedJob) {
jobManager.registerJob(job);
}
CommandRunnerImpl.this.doCommand(this, command, subject, job);
job.complete(report(), outboundPayload());
if (progressStatusChild != null) {
progressStatusChild.complete();
}
CommandSupport.done(habitat, command, job, isNotify);
}
}
/**
* An InjectionResolver that uses an Object as the source of
* the data to inject.
*/
private static class DelegatedInjectionResolver
extends InjectionResolver<Param> {
private final CommandModel model;
private final CommandParameters parameters;
private final MultiMap<String, File> optionNameToUploadedFileMap;
public DelegatedInjectionResolver(CommandModel model,
CommandParameters parameters,
final MultiMap<String, File> optionNameToUploadedFileMap) {
super(Param.class);
this.model = model;
this.parameters = parameters;
this.optionNameToUploadedFileMap = optionNameToUploadedFileMap;
}
@Override
public boolean isOptional(AnnotatedElement element, Param annotation) {
String name = CommandModel.getParamName(annotation, element);
CommandModel.ParamModel param = model.getModelFor(name);
return param.getParam().optional();
}
@Override
public <V> V getValue(Object component, AnnotatedElement target, Type genericType, Class<V> type) {
// look for the name in the list of parameters passed.
if (target instanceof Field) {
final Field targetField = (Field) target;
try {
Field sourceField =
parameters.getClass().getField(targetField.getName());
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
targetField.setAccessible(true);
return null;
}
});
Object paramValue = sourceField.get(parameters);
/*
* If this field is a File, then replace the param value
* (which is whatever the client supplied on the command) with
* the actual absolute path(s) of the uploaded and extracted
* file(s) if, in fact, the file(s) was (were) uploaded.
*/
final List<String> paramFileValues =
MapInjectionResolver.getUploadedFileParamValues(
targetField.getName(),
targetField.getType(),
optionNameToUploadedFileMap);
if (!paramFileValues.isEmpty()) {
V fileValue = (V) MapInjectionResolver.convertListToObject(target, type, paramFileValues);
return fileValue;
}
/*
if (paramValue==null) {
return convertStringToObject(target, type,
param.defaultValue());
}
*/
// XXX temp fix, to revisit
if (paramValue != null) {
checkAgainstAcceptableValues(target,
paramValue.toString());
}
return type.cast(paramValue);
} catch (IllegalAccessException e) {
} catch (NoSuchFieldException e) {
}
}
return null;
}
private static void checkAgainstAcceptableValues(
AnnotatedElement target, String paramValueStr) {
Param param = target.getAnnotation(Param.class);
String acceptable = param.acceptableValues();
String paramName = CommandModel.getParamName(param, target);
if (ok(acceptable) && ok(paramValueStr)) {
String[] ss = acceptable.split(",");
for (String s : ss) {
if (paramValueStr.equals(s.trim())) {
return; // matched, value is good
}
}
// didn't match any, error
throw new UnacceptableValueException(
adminStrings.getLocalString(
"adapter.command.unacceptableValue",
"Invalid parameter: {0}. Its value is {1} "
+ "but it isn''t one of these acceptable values: {2}",
paramName,
paramValueStr,
acceptable));
}
}
}
/**
* Is the boolean valued parameter specified?
* If so, and it has a value, is the value "true"?
*/
private static boolean isSet(ParameterMap params, String name) {
String val = params.getOne(name);
if (val == null) {
return false;
}
return val.length() == 0 || Boolean.valueOf(val).booleanValue();
}
/** Works as a key in ETag cache map
*/
private static class NameCommandClassPair {
private String name;
private Class<? extends AdminCommand> clazz;
private int hash; //immutable, we can cache it
public NameCommandClassPair(String name, Class<? extends AdminCommand> clazz) {
this.name = name;
this.clazz = clazz;
hash = 3;
hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 67 * hash + (this.clazz != null ? this.clazz.hashCode() : 0);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final NameCommandClassPair other = (NameCommandClassPair) obj;
if (this.clazz != other.clazz) {
return false;
}
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return hash;
}
}
/**
* Encapsulates handling of files uploaded to the server in the payload
* of the incoming HTTP request.
* <p>
* Extracts any such files from the payload into a temporary directory
* under the domain's applications directory. (Putting them there allows
* the deployment processing to rename the uploaded archive to another location
* under the applications directory, rather than having to copy them.)
*/
private class UploadedFilesManager {
private final ActionReport report;
private final Logger logger;
/**
* maps option names as sent with each uploaded file to the corresponding
* extracted files
*/
private MultiMap<String, File> optionNameToFileMap;
/*
* PFM needs to be a field so it is not gc-ed before the
* UploadedFilesManager is closed.
*/
private PayloadFilesManager.Temp payloadFilesMgr = null;
private UploadedFilesManager(final ActionReport report,
final Logger logger,
final Payload.Inbound inboundPayload) throws IOException, Exception {
this.logger = logger;
this.report = report;
extractFiles(inboundPayload);
}
private MultiMap<String, File> optionNameToFileMap() {
return optionNameToFileMap;
}
private void close() {
if (payloadFilesMgr != null) {
payloadFilesMgr.cleanup();
}
}
private void extractFiles(final Payload.Inbound inboundPayload)
throws Exception {
if (inboundPayload == null) {
return;
}
final File uniqueSubdirUnderApplications = chooseTempDirParent();
payloadFilesMgr = new PayloadFilesManager.Temp(
uniqueSubdirUnderApplications,
report,
logger);
/*
* Extract the files into the temp directory.
*/
final Map<File, Properties> payloadFiles =
payloadFilesMgr.processPartsExtended(inboundPayload);
/*
* Prepare the map of command options names to corresponding
* uploaded files.
*/
optionNameToFileMap = new MultiMap<String, File>();
for (Map.Entry<File, Properties> e : payloadFiles.entrySet()) {
final String optionName = e.getValue().getProperty("data-request-name");
if (optionName != null) {
logger.finer("UploadedFilesManager: map " + optionName
+ " to " + e.getKey());
optionNameToFileMap.add(optionName, e.getKey());
}
}
}
private File chooseTempDirParent() throws IOException {
final File appRoot = new File(domain.getApplicationRoot());
/*
* Apparently during embedded runs the applications directory
* might not be present already. Create it if needed.
*/
if (!appRoot.isDirectory()) {
if (!appRoot.exists() && !appRoot.mkdirs()) {
throw new IOException(adminStrings.getLocalString("commandrunner.errCreDir",
"Could not create the directory {0}; no further information is available.",
appRoot.getAbsolutePath()));
}
}
return appRoot;
}
}
/**
* Format the lock acquisition time.
*/
private String formatSuspendDate(Date lockTime) {
if (lockTime != null) {
String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
return sdf.format(lockTime);
} else {
return adminStrings.getLocalString("lock.timeoutunavailable",
"<<Date is unavailable>>");
}
}
}