/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.management.internal.cli.multistep;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.geode.LogWriter;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.CliUtil;
import org.apache.geode.management.internal.cli.CommandRequest;
import org.apache.geode.management.internal.cli.GfshParseResult;
import org.apache.geode.management.internal.cli.LogWrapper;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.json.GfJsonException;
import org.apache.geode.management.internal.cli.json.GfJsonObject;
import org.apache.geode.management.internal.cli.remote.CommandExecutionContext;
import org.apache.geode.management.internal.cli.result.CommandResult;
import org.apache.geode.management.internal.cli.result.CompositeResultData;
import org.apache.geode.management.internal.cli.result.CompositeResultData.SectionResultData;
import org.apache.geode.management.internal.cli.result.ResultBuilder;
import org.apache.geode.management.internal.cli.result.ResultData;
import org.apache.geode.management.internal.cli.result.TabularResultData;
import org.apache.geode.management.internal.cli.shell.Gfsh;
import org.springframework.shell.event.ParseResult;
import org.springframework.util.ReflectionUtils;
/**
* Utility class to abstract CompositeResultData for Multi-step commands Also contain execution
* strategy for multi-step commands
*
*
*/
public class CLIMultiStepHelper {
public static final String STEP_SECTION = "STEP_SECTION";
public static final String PAGE_SECTION = "PAGE_SECTION";
public static final String ARG_SECTION = "ARG_SECTION";
public static final String NEXT_STEP_NAME = "NEXT_STEP_NAME";
public static final String NEXT_STEP_ARGS = "NEXT_STEP_ARGS";
public static final String NEXT_STEP_NAMES = "NEXT_STEP_NAMES";
public static final String STEP_ARGS = "stepArgs";
public static final int DEFAULT_PAGE_SIZE = 20;
public static Object execCLISteps(LogWrapper logWrapper, Gfsh shell, ParseResult parseResult) {
CLIStep[] steps = (CLIStep[]) ReflectionUtils.invokeMethod(parseResult.getMethod(),
parseResult.getInstance(), parseResult.getArguments());
if (steps != null) {
boolean endStepReached = false;
int stepNumber = 0;
CLIStep nextStep = steps[stepNumber];
Result lastResult = null;
SectionResultData nextStepArgs = null;
while (!endStepReached) {
try {
Result result = executeStep(logWrapper, shell, nextStep, parseResult, nextStepArgs);
String nextStepString = null;
nextStepString = getNextStep(result);
nextStepArgs = extractArgumentsForNextStep(result);
if (!"END".equals(nextStepString)) {
String step = nextStepString;
boolean stepFound = false;
for (CLIStep s : steps)
if (step.equals(s.getName())) {
nextStep = s;
stepFound = true;
}
if (!stepFound) {
return ResultBuilder.buildResult(ResultBuilder.createErrorResultData()
.addLine("Wrong step name returned by previous step : " + step));
}
} else {
lastResult = result;
endStepReached = true;
}
} catch (CLIStepExecption e) {
endStepReached = true;
lastResult = e.getResult();
}
}
return lastResult;
} else {
Gfsh.println("Command returned null steps");
return ResultBuilder.buildResult(ResultBuilder.createErrorResultData()
.addLine("Multi-step command Return NULL STEP Array"));
}
}
private static Result executeStep(final LogWrapper logWrapper, final Gfsh shell,
final CLIStep nextStep, final ParseResult parseResult, final SectionResultData nextStepArgs) {
try {
if (nextStep instanceof CLIRemoteStep) {
if (shell.isConnectedAndReady()) {
if (GfshParseResult.class.isInstance(parseResult)) {
GfshParseResult gfshParseResult = (GfshParseResult) parseResult;
// this makes sure that "quit" step will correctly update the environment with empty
// stepArgs
if (nextStepArgs != null) {
GfJsonObject argsJSon = nextStepArgs.getSectionGfJsonObject();
shell.setEnvProperty(CLIMultiStepHelper.STEP_ARGS, argsJSon.toString());
}
CommandRequest commandRequest = new CommandRequest(gfshParseResult, shell.getEnv());
commandRequest
.setCustomInput(changeStepName(gfshParseResult.getUserInput(), nextStep.getName()));
commandRequest.getCustomParameters().put(CliStrings.QUERY__STEPNAME,
nextStep.getName());
String json = (String) shell.getOperationInvoker().processCommand(commandRequest);
return ResultBuilder.fromJson(json);
} else {
throw new IllegalArgumentException("Command Configuration/Definition error.");
}
} else {
try {
throw new Exception();
} catch (Exception ex) {
ex.printStackTrace();
}
throw new IllegalStateException(
"Can't execute a remote command without connection. Use 'connect' first to connect.");
}
} else {
Map<String, String> args = CommandExecutionContext.getShellEnv();
if (args == null) {
args = new HashMap<String, String>();
CommandExecutionContext.setShellEnv(args);
}
if (nextStepArgs != null) {
GfJsonObject argsJSon = nextStepArgs.getSectionGfJsonObject();
Gfsh.getCurrentInstance().setEnvProperty(CLIMultiStepHelper.STEP_ARGS,
argsJSon.toString());
}
return nextStep.exec();
}
} catch (CLIStepExecption e) {
logWrapper.severe("CLIStep " + nextStep.getName() + " failed aborting command");
throw e;
}
}
private static String changeStepName(String userInput, String stepName) {
int i = userInput.indexOf("--step-name=");
if (i == -1) {
return userInput + " --step-name=" + stepName;
} else {
// TODO this is a dangerous assumption... to assume the "--step-name" query command option is
// the last parameter
// specified!
return userInput.substring(0, i) + "--step-name=" + stepName;
}
}
public static SectionResultData extractArgumentsForNextStep(Result result) {
CommandResult cResult = (CommandResult) result;
if (ResultData.TYPE_COMPOSITE.equals(cResult.getType())) {
CompositeResultData rd = (CompositeResultData) cResult.getResultData();
SectionResultData data = rd.retrieveSection(CLIMultiStepHelper.ARG_SECTION);
return data;
} else {
if (ResultData.TYPE_ERROR.equals(cResult.getType())) {
throw new CLIStepExecption(cResult);
} else {
throw new StepExecutionException("Step returned result of type other than "
+ ResultData.TYPE_COMPOSITE + " Type " + cResult.getType());
}
}
}
public static String getNextStep(Result cdata) {
CommandResult cResult = (CommandResult) cdata;
if (ResultData.TYPE_COMPOSITE.equals(cResult.getType())) {
CompositeResultData rd = (CompositeResultData) cResult.getResultData();
SectionResultData section = rd.retrieveSection(CLIMultiStepHelper.STEP_SECTION);
String nextStep = (String) section.retrieveObject(CLIMultiStepHelper.NEXT_STEP_NAME);
return nextStep;
} else {
if (ResultData.TYPE_ERROR.equals(cResult.getType())) {
throw new CLIStepExecption(cResult);
} else {
throw new RuntimeException("Step returned result of type other than "
+ ResultData.TYPE_COMPOSITE + " Type " + cResult.getType());
}
}
}
public static CommandResult getDisplayResultFromArgs(GfJsonObject args) {
SectionResultData sectionData = new SectionResultData(args);
CompositeResultData data = ResultBuilder.createCompositeResultData();
data.addSection(sectionData);
return (CommandResult) ResultBuilder.buildResult(data);
}
public static GfJsonObject getStepArgs() {
Map<String, String> args = null;
if (CliUtil.isGfshVM) {
args = Gfsh.getCurrentInstance().getEnv();
} else {
args = CommandExecutionContext.getShellEnv();
}
if (args == null)
return null;
String stepArg = args.get(CLIMultiStepHelper.STEP_ARGS);
if (stepArg == null)
return null;
GfJsonObject object;
try {
object = new GfJsonObject(stepArg);
} catch (GfJsonException e) {
throw new RuntimeException("Error converting arguments section into json object");
}
return object;
}
public static Result createSimpleStepResult(String nextStep) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, nextStep);
return ResultBuilder.buildResult(result);
}
public static Object chooseStep(CLIStep[] steps, String stepName) {
if ("ALL".equals(stepName)) {
return steps;
} else {
for (CLIStep s : steps)
if (stepName.equals(s.getName())) {
return s.exec();
}
return null;
}
}
public static Result createStepSeqResult(CLIStep[] steps) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, steps[0].getName());
String[] array = new String[steps.length];
for (int i = 0; i < steps.length; i++) {
array[i] = steps[i++].getName();
}
section.addData(NEXT_STEP_NAMES, array);
return ResultBuilder.buildResult(result);
}
public static Result createEmptyResult(String step) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, step);
return ResultBuilder.buildResult(result);
}
public static Result createPageResult(String fields[], Object values[], String step,
String[] header, Object[][] table) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, step);
SectionResultData page = result.addSection(ARG_SECTION);
if (fields.length != values.length)
throw new RuntimeException("Fields array and its value arraylength dont match");
for (int i = 0; i < fields.length; i++) {
page.addData(fields[i], values[i]);
}
createPageTableAndBanner(page, header, table);
return ResultBuilder.buildResult(result);
}
public static Result createPageResult(List<String> fields,
@SuppressWarnings("rawtypes") List values, String step, String[] header, Object[][] table) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, step);
SectionResultData page = result.addSection(ARG_SECTION);
if (fields.size() != values.size())
throw new RuntimeException("Fields array and its value arraylength dont match");
for (int i = 0; i < fields.size(); i++) {
page.addData(fields.get(i), values.get(i));
}
createPageTableAndBanner(page, header, table);
return ResultBuilder.buildResult(result);
}
private static void createPageTableAndBanner(SectionResultData page, String[] header,
Object[][] table) {
TabularResultData resultData = page.addTable();
int columns = header.length;
for (int i = 0; i < table.length; i++) {
int rowLength = table[i].length;
if (rowLength != columns)
throw new RuntimeException("Row contains more than " + columns + " : " + rowLength);
}
for (int i = 0; i < table.length; i++) {
for (int j = 0; j < columns; j++) {
resultData.accumulate(header[j], table[i][j]);
}
}
}
public static Result createBannerResult(String fields[], Object values[], String step) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, step);
SectionResultData page = result.addSection(ARG_SECTION);
if (fields.length != values.length) {
throw new RuntimeException("Fields array and its value arraylength dont match");
}
for (int i = 0; i < fields.length; i++) {
page.addData(fields[i], values[i]);
}
return ResultBuilder.buildResult(result);
}
public static Result createBannerResult(List<String> fields,
@SuppressWarnings("rawtypes") List values, String step) {
CompositeResultData result = ResultBuilder.createCompositeResultData();
SectionResultData section = result.addSection(STEP_SECTION);
section.addData(NEXT_STEP_NAME, step);
SectionResultData page = result.addSection(ARG_SECTION);
if (fields.size() != values.size()) {
throw new RuntimeException("Fields array and its value arraylength dont match");
}
for (int i = 0; i < fields.size(); i++) {
page.addData(fields.get(i), values.get(i));
}
return ResultBuilder.buildResult(result);
}
public static void logFine(String msg) {
Gfsh.println(msg);
// TODO Use gemfire Logging for code path running on manager
}
public static abstract class LocalStep implements CLIStep {
private String name = null;
protected Object[] commandArguments = null;
public LocalStep(String name, Object[] arguments) {
this.name = name;
this.commandArguments = arguments;
}
public String getName() {
return name;
}
}
@SuppressWarnings("serial")
public static abstract class RemoteStep implements CLIRemoteStep {
private String name = null;
protected Object[] commandArguments = null;
public RemoteStep(String name, Object[] arguments) {
this.name = name;
this.commandArguments = arguments;
}
public String getName() {
return name;
}
}
public static class StepExecutionException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String message;
public StepExecutionException(String message) {
LogWriter logger = CacheFactory.getAnyInstance().getLogger();
logger.severe(message);
this.message = message;
}
@Override
public String getMessage() {
return StepExecutionException.class.getName();
}
public String getStepExecutionExceptionMessage() {
return message;
}
}
}