blob: 36e6f81e6efe080b159e5b00f4e17d551ee10726 [file] [log] [blame]
/*
* 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.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.geode.cache.query.Struct;
import org.apache.geode.cache.query.internal.StructImpl;
import org.apache.geode.cache.query.internal.Undefined;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.GfshParser;
import org.apache.geode.management.internal.cli.result.model.DataResultModel;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.cli.result.model.TabularResultModel;
import org.apache.geode.management.internal.i18n.CliStrings;
import org.apache.geode.pdx.JSONFormatter;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.util.internal.GeodeJsonMapper;
/**
* Domain object used for Data Commands Functions
*/
public class DataCommandResult implements Serializable {
private static final long serialVersionUID = 2601227194108110936L;
public static final Logger logger = LogManager.getLogger();
public static final String DATA_INFO_SECTION = "data-info";
public static final String QUERY_SECTION = "query";
public static final String LOCATION_SECTION = "location";
private String command;
private Object putResult;
private Object getResult;
private List<SelectResultRow> selectResult;
private String queryTraceString;
public static final String RESULT_FLAG = "Result";
public static final String NUM_ROWS = "Rows";
public static final String MISSING_VALUE = "<NULL>";
// Aggregated Data.
private List<KeyInfo> locateEntryLocations;
private KeyInfo locateEntryResult;
private boolean hasResultForAggregation;
private Object removeResult;
private Object inputKey;
private Object inputValue;
private Object inputQuery;
private Throwable error;
private String errorString;
private String infoString;
private String keyClass;
private String valueClass;
private int limit;
private boolean operationCompletedSuccessfully; // used for validation purposes.
public static final String NEW_LINE = GfshParser.LINE_SEPARATOR;
public String toString() {
StringBuilder sb = new StringBuilder();
if (isGet()) {
sb.append(" Type : Get").append(NEW_LINE);
sb.append(" Key : ").append(inputKey).append(NEW_LINE);
if (getResult != null) {
sb.append(" ReturnValue Class : ").append(getResult.getClass()).append(NEW_LINE);
}
sb.append(" ReturnValue : ").append(getResult).append(NEW_LINE);
} else if (isPut()) {
sb.append(" Type : Put");
sb.append(" Key : ").append(inputKey).append(NEW_LINE);
if (putResult != null) {
sb.append(" ReturnValue Class : ").append(putResult.getClass()).append(NEW_LINE);
}
sb.append(" ReturnValue : ").append(putResult).append(NEW_LINE);
sb.append(" Value : ").append(inputValue).append(NEW_LINE);
} else if (isRemove()) {
sb.append(" Type : Remove");
sb.append(" Key : ").append(inputKey).append(NEW_LINE);
if (removeResult != null) {
sb.append(" ReturnValue Class : ").append(removeResult.getClass()).append(NEW_LINE);
}
sb.append(" ReturnValue : ").append(removeResult).append(NEW_LINE);
} else if (isLocateEntry()) {
sb.append(" Type : Locate Entry");
sb.append(" Key : ").append(inputKey).append(NEW_LINE);
// Assume here that this is aggregated result
sb.append(" Results : ").append(locateEntryResult).append(NEW_LINE);
sb.append(" Locations : ").append(locateEntryLocations).append(NEW_LINE);
}
if (errorString != null) {
sb.append(" ERROR ").append(errorString);
}
return sb.toString();
}
public boolean isGet() {
return CliStrings.GET.equals(command);
}
public boolean isPut() {
return CliStrings.PUT.equals(command);
}
public boolean isRemove() {
return CliStrings.REMOVE.equals(command);
}
public boolean isLocateEntry() {
return CliStrings.LOCATE_ENTRY.equals(command);
}
public boolean isSelect() {
return CliStrings.QUERY.equals(command);
}
public List<SelectResultRow> getSelectResult() {
return selectResult;
}
public static DataCommandResult createGetResult(Object inputKey, Object value, Throwable error,
String errorString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.GET;
result.inputKey = inputKey;
result.getResult = value;
result.error = error;
result.errorString = errorString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createGetInfoResult(Object inputKey, Object value,
Throwable error, String infoString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.GET;
result.inputKey = inputKey;
result.getResult = value;
result.error = error;
result.infoString = infoString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createLocateEntryResult(Object inputKey, KeyInfo locationResult,
Throwable error, String errorString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.LOCATE_ENTRY;
result.inputKey = inputKey;
if (flag) {
result.hasResultForAggregation = true;
}
result.locateEntryResult = locationResult;
result.error = error;
result.errorString = errorString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createLocateEntryInfoResult(Object inputKey,
KeyInfo locationResult, Throwable error, String infoString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.LOCATE_ENTRY;
result.inputKey = inputKey;
if (flag) {
result.hasResultForAggregation = true;
}
result.locateEntryResult = locationResult;
result.error = error;
result.infoString = infoString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createPutResult(Object inputKey, Object value, Throwable error,
String errorString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.PUT;
result.inputKey = inputKey;
result.putResult = value;
result.error = error;
result.errorString = errorString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createPutInfoResult(Object inputKey, Object value,
Throwable error, String infoString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.PUT;
result.inputKey = inputKey;
result.putResult = value;
result.error = error;
result.infoString = infoString;
return result;
}
public static DataCommandResult createRemoveResult(Object inputKey, Object value, Throwable error,
String errorString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.REMOVE;
result.inputKey = inputKey;
result.removeResult = value;
result.error = error;
result.errorString = errorString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createRemoveInfoResult(Object inputKey, Object value,
Throwable error, String infoString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.REMOVE;
result.inputKey = inputKey;
result.removeResult = value;
result.error = error;
result.infoString = infoString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createSelectResult(Object inputQuery, List<SelectResultRow> value,
String queryTraceString, Throwable error, String errorString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.QUERY;
result.inputQuery = inputQuery;
result.queryTraceString = queryTraceString;
result.selectResult = value;
result.error = error;
result.errorString = errorString;
result.operationCompletedSuccessfully = flag;
return result;
}
public static DataCommandResult createSelectInfoResult(Object inputQuery,
List<SelectResultRow> value, int limit, Throwable error, String infoString, boolean flag) {
DataCommandResult result = new DataCommandResult();
result.command = CliStrings.QUERY;
result.inputQuery = inputQuery;
result.limit = limit;
result.selectResult = value;
result.error = error;
result.infoString = infoString;
result.operationCompletedSuccessfully = flag;
return result;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public Object getPutResult() {
return putResult;
}
public void setPutResult(Object putResult) {
this.putResult = putResult;
}
public Object getGetResult() {
return getResult;
}
public void setGetResult(Object getResult) {
this.getResult = getResult;
}
public Object getRemoveResult() {
return removeResult;
}
public void setRemoveResult(Object removeResult) {
this.removeResult = removeResult;
}
public Object getInputKey() {
return inputKey;
}
public void setInputKey(Object inputKey) {
this.inputKey = inputKey;
}
public Object getInputValue() {
return inputValue;
}
public void setInputValue(Object inputValue) {
this.inputValue = inputValue;
}
public Throwable getErorr() {
return error;
}
public void setErorr(Throwable erorr) {
error = erorr;
}
public String getErrorString() {
return errorString;
}
public void setErrorString(String errorString) {
this.errorString = errorString;
}
public String getInfoString() {
return infoString;
}
public void setInfoString(String infoString) {
this.infoString = infoString;
}
public String getKeyClass() {
return keyClass;
}
public void setKeyClass(String keyClass) {
this.keyClass = keyClass;
}
public String getValueClass() {
return valueClass;
}
public void setValueClass(String valueClass) {
this.valueClass = valueClass;
}
public List<KeyInfo> getLocateEntryLocations() {
return locateEntryLocations;
}
public ResultModel toResultModel() {
if (StringUtils.isEmpty(keyClass)) {
keyClass = "java.lang.String";
}
if (StringUtils.isEmpty(valueClass)) {
valueClass = "java.lang.String";
}
ResultModel result = new ResultModel();
DataResultModel data = result.addData(DATA_INFO_SECTION);
if (errorString != null) {
data.addData("Message", errorString);
data.addData(RESULT_FLAG, operationCompletedSuccessfully);
return result;
}
data.addData(RESULT_FLAG, operationCompletedSuccessfully);
if (infoString != null) {
data.addData("Message", infoString);
}
if (isGet()) {
toResultModel_isGet(data);
} else if (isLocateEntry()) {
toResultModel_isLocate(result, data);
} else if (isPut()) {
toResultModel_isPut(data);
} else if (isRemove()) {
toResultModel_isRemove(data);
}
return result;
}
private void toResultModel_isGet(DataResultModel data) {
data.addData("Key Class", getKeyClass());
data.addData("Key", inputKey);
data.addData("Value Class", getValueClass());
data.addData("Value", getResult != null ? getResult.toString() : "null");
}
private void toResultModel_isLocate(ResultModel result, DataResultModel data) {
data.addData("Key Class", getKeyClass());
data.addData("Key", inputKey);
if (locateEntryLocations != null) {
TabularResultModel locationTable = result.addTable(LOCATION_SECTION);
int totalLocations = 0;
for (KeyInfo info : locateEntryLocations) {
List<Object[]> locations = info.getLocations();
if (locations != null) {
if (locations.size() == 1) {
Object[] array = locations.get(0);
boolean found = (Boolean) array[1];
if (found) {
totalLocations++;
boolean primary = (Boolean) array[3];
String bucketId = (String) array[4];
locationTable.accumulate("MemberName", info.getMemberName());
locationTable.accumulate("MemberId", info.getMemberId());
if (bucketId != null) {// PR
if (primary) {
locationTable.accumulate("Primary", "*Primary PR*");
} else {
locationTable.accumulate("Primary", "No");
}
locationTable.accumulate("BucketId", bucketId);
}
}
} else {
for (Object[] array : locations) {
String regionPath = (String) array[0];
boolean found = (Boolean) array[1];
if (found) {
totalLocations++;
boolean primary = (Boolean) array[3];
String bucketId = (String) array[4];
locationTable.accumulate("MemberName", info.getMemberName());
locationTable.accumulate("MemberId", info.getMemberId());
locationTable.accumulate("RegionPath", regionPath);
if (bucketId != null) {// PR
if (primary) {
locationTable.accumulate("Primary", "*Primary PR*");
} else {
locationTable.accumulate("Primary", "No");
}
locationTable.accumulate("BucketId", bucketId);
}
}
}
}
}
}
data.addData("Locations Found", totalLocations);
} else {
data.addData("Location Info ", "Could not find location information");
}
}
private void toResultModel_isPut(DataResultModel data) {
data.addData("Key Class", getKeyClass());
data.addData("Key", inputKey);
data.addData("Value Class", getValueClass());
data.addData("Old Value", putResult != null ? putResult.toString() : "null");
}
private void toResultModel_isRemove(DataResultModel data) {
if (inputKey != null) {// avoids printing key when remove ALL is called
data.addData("Key Class", getKeyClass());
data.addData("Key", inputKey);
}
}
/**
* This method returns result when flag interactive=false i.e. Command returns result in one go
* and does not goes through steps waiting for user input. Method returns CompositeResultData
* instead of Result as Command Step is required to add NEXT_STEP information to guide
* executionStrategy to route it through final step.
*/
public ResultModel toSelectCommandResult() {
ResultModel result = new ResultModel();
DataResultModel data = result.addData(DATA_INFO_SECTION);
if (!operationCompletedSuccessfully) {
result.setStatus(Result.Status.ERROR);
data.addData(RESULT_FLAG, operationCompletedSuccessfully);
if (errorString != null) {
data.addData("Message", errorString);
} else if (infoString != null) {
data.addData("Message", infoString);
}
return result;
} else {
TabularResultModel table = result.addTable(DataCommandResult.QUERY_SECTION);
data.addData(RESULT_FLAG, operationCompletedSuccessfully);
if (infoString != null) {
data.addData("Message", infoString);
}
if (inputQuery != null) {
if (limit > 0) {
data.addData("Limit", limit);
}
if (selectResult != null) {
data.addData(NUM_ROWS, selectResult.size());
if (queryTraceString != null) {
data.addData("Query Trace", queryTraceString);
}
buildTable(table, 0, selectResult.size());
}
}
return result;
}
}
private void buildTable(TabularResultModel table, int startCount, int endCount) {
int lastRowExclusive = Math.min(selectResult.size(), endCount + 1);
List<SelectResultRow> paginatedRows = selectResult.subList(startCount, lastRowExclusive);
// First find all the possible columns - not a Set because we want them ordered consistently
List<String> possibleColumns = new ArrayList<>();
for (SelectResultRow row : paginatedRows) {
for (String column : row.getColumnValues().keySet()) {
if (!possibleColumns.contains(column)) {
possibleColumns.add(column);
}
}
}
for (SelectResultRow row : paginatedRows) {
Map<String, String> columnValues = row.getColumnValues();
for (String column : possibleColumns) {
table.accumulate(column,
columnValues.getOrDefault(column, DataCommandResult.MISSING_VALUE));
}
}
}
public Object getInputQuery() {
return inputQuery;
}
public void setInputQuery(Object inputQuery) {
this.inputQuery = inputQuery;
}
public void setLimit(int limit) {
this.limit = limit;
}
public static class KeyInfo implements Serializable {
private String memberId;
private String memberName;
private String host;
private int pid;
// Indexes : regionName = 0, found=1, value=2 primary=3 bucketId=4
private ArrayList<Object[]> locations = null;
public void addLocation(Object[] locationArray) {
if (locations == null) {
locations = new ArrayList<>();
}
locations.add(locationArray);
}
public List<Object[]> getLocations() {
return locations;
}
public String getMemberId() {
return memberId;
}
public void setMemberId(String memberId) {
this.memberId = memberId;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{ Member : ").append(host).append("(").append(memberId).append(") , ");
for (Object[] array : locations) {
boolean primary = (Boolean) array[3];
String bucketId = (String) array[4];
sb.append(" [ Primary : ").append(primary).append(" , BucketId : ").append(bucketId)
.append(" ]");
}
sb.append(" }");
return sb.toString();
}
public boolean hasLocation() {
if (locations == null) {
return false;
} else {
for (Object[] array : locations) {
boolean found = (Boolean) array[1];
if (found) {
return true;
}
}
}
return false;
}
}
public static final int ROW_TYPE_STRUCT_RESULT = 100;
public static final int ROW_TYPE_BEAN = 200;
public static final int ROW_TYPE_PRIMITIVE = 300;
public static class SelectResultRow implements Serializable {
private static final long serialVersionUID = 1L;
private final int type;
private final Map<String, String> columnValues;
public SelectResultRow(int type, Object value) {
this.type = type;
columnValues = createColumnValues(value);
}
public Map<String, String> getColumnValues() {
return columnValues;
}
private Map<String, String> createColumnValues(Object value) {
Map<String, String> result = new LinkedHashMap<>();
if (value == null || MISSING_VALUE.equals(value)) {
result.put("Value", MISSING_VALUE);
} else if (type == ROW_TYPE_PRIMITIVE) {
result.put(RESULT_FLAG, value.toString());
} else if (value instanceof Undefined) {
result.put("Value", "UNDEFINED");
} else {
resolveObjectToColumns(result, value);
}
return result;
}
private void resolveObjectToColumns(Map<String, String> columnData, Object value) {
if (value instanceof PdxInstance) {
resolvePdxToColumns(columnData, (PdxInstance) value);
} else if (value instanceof Struct) {
resolveStructToColumns(columnData, (StructImpl) value);
} else if (value instanceof UUID) {
columnData.put("Result", valueToJson(value));
} else {
ObjectMapper mapper = GeodeJsonMapper.getMapperWithAlwaysInclusion();
JsonNode node = mapper.valueToTree(value);
node.fieldNames().forEachRemaining(field -> {
try {
columnData.put(field, mapper.writeValueAsString(node.get(field)));
} catch (JsonProcessingException e) {
columnData.put(field, e.getMessage());
}
});
}
}
private void resolvePdxToColumns(Map<String, String> columnData, PdxInstance pdx) {
for (String field : pdx.getFieldNames()) {
columnData.put(field, valueToJson(pdx.getField(field)));
}
}
private void resolveStructToColumns(Map<String, String> columnData, StructImpl struct) {
for (String field : struct.getFieldNames()) {
columnData.put(field, valueToJson(struct.get(field)));
}
}
private String valueToJson(Object value) {
if (value == null) {
return "null";
}
if (value instanceof String) {
return (String) value;
}
if (value instanceof PdxInstance) {
return JSONFormatter.toJSON((PdxInstance) value);
}
ObjectMapper mapper = GeodeJsonMapper.getMapperWithAlwaysInclusion();
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException jex) {
return jex.getMessage();
}
}
}
public void aggregate(DataCommandResult result) {
/* Right now only called for LocateEntry */
if (!isLocateEntry()) {
return;
}
if (locateEntryLocations == null) {
locateEntryLocations = new ArrayList<>();
}
if (result == null) {// self-transform result from single to aggregate when numMember==1
if (locateEntryResult != null) {
locateEntryLocations.add(locateEntryResult);
}
return;
}
if (result.errorString != null && !result.errorString.equals(errorString)) {
// append errorString only if differs
errorString = result.errorString + " " + errorString;
}
// append message only when it differs for negative results
if (!operationCompletedSuccessfully && result.infoString != null
&& !result.infoString.equals(infoString)) {
infoString = result.infoString;
}
if (result.hasResultForAggregation) {
operationCompletedSuccessfully = true;
infoString = result.infoString;
if (result.locateEntryResult != null) {
locateEntryLocations.add(result.locateEntryResult);
}
}
}
}