/*******************************************************************************
* Copyright 2012 University of Southern California
*
* Licensed 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.
*
* This code was developed by the Information Integration Group as part
* of the Karma project at the Information Sciences Institute of the
* University of Southern California. For more information, publications,
* and related projects, please see: http://www.isi.edu/integration
******************************************************************************/
package edu.isi.karma.controller.history;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.isi.karma.controller.command.Command;
import edu.isi.karma.controller.command.CommandException;
import edu.isi.karma.controller.command.CommandFactory;
import edu.isi.karma.controller.command.ICommand.CommandTag;
import edu.isi.karma.controller.history.CommandHistory.HistoryArguments;
import edu.isi.karma.controller.history.HistoryJsonUtil.ClientJsonKeys;
import edu.isi.karma.controller.history.HistoryJsonUtil.ParameterType;
import edu.isi.karma.controller.update.AbstractUpdate;
import edu.isi.karma.controller.update.TrivialErrorUpdate;
import edu.isi.karma.controller.update.UpdateContainer;
import edu.isi.karma.rep.HNode;
import edu.isi.karma.rep.HNode.HNodeType;
import edu.isi.karma.rep.HTable;
import edu.isi.karma.rep.Workspace;
import edu.isi.karma.util.Util;
import edu.isi.karma.webserver.ExecutionController;
import edu.isi.karma.webserver.KarmaException;
import edu.isi.karma.webserver.WorkspaceRegistry;
public class WorksheetCommandHistoryExecutor {
private final String worksheetId;
private final Workspace workspace;
private static Logger logger = LoggerFactory.getLogger(WorksheetCommandHistoryExecutor.class);
private static String[] commandsIgnoreNodeBefore = { "AddColumnCommand",
"SubmitPythonTransformationCommand"
};
public WorksheetCommandHistoryExecutor(String worksheetId, Workspace workspace) {
super();
this.worksheetId = worksheetId;
this.workspace = workspace;
}
public UpdateContainer executeCommandsByTags(
List<CommandTag> tagsToAdd, List<CommandTag> tagsToRemove, JSONArray historyJson) throws JSONException,
KarmaException, CommandException {
JSONArray filteredCommands = HistoryJsonUtil.filterCommandsByTag(tagsToAdd, historyJson);
filteredCommands = HistoryJsonUtil.removeCommandsByTag(tagsToRemove, filteredCommands);
return executeAllCommands(filteredCommands);
}
public UpdateContainer executeAllCommands(JSONArray historyJson)
throws JSONException, KarmaException, CommandException {
UpdateContainer uc =new UpdateContainer();
boolean saveToHistory = false;
for (int i = 0; i< historyJson.length(); i++) {
JSONObject commObject = (JSONObject) historyJson.get(i);
if(i == historyJson.length() - 1) saveToHistory = true;
UpdateContainer update = executeCommand(commObject, saveToHistory);
if(update != null)
uc.append(update);
}
return uc;
}
private UpdateContainer executeCommand(JSONObject commObject, boolean saveToHistory)
throws JSONException, KarmaException, CommandException {
ExecutionController ctrl = WorkspaceRegistry.getInstance().getExecutionController(workspace.getId());
HashMap<String, CommandFactory> commandFactoryMap = ctrl.getCommandFactoryMap();
JSONArray inputParamArr = (JSONArray) commObject.get(HistoryArguments.inputParameters.name());
String commandName = (String)commObject.get(HistoryArguments.commandName.name());
logger.debug("Command in history: " + commandName);
// Change the hNode ids, vworksheet id to point to the current worksheet ids
try {
UpdateContainer uc = normalizeCommandHistoryJsonInput(workspace, worksheetId, inputParamArr, commandName, true);
// Invoke the command
if (uc == null) {
uc = new UpdateContainer();
}
CommandFactory cf = commandFactoryMap.get(commObject.get(HistoryArguments.commandName.name()));
if(cf != null) {
try { // This is sort of a hack the way I did this, but could not think of a better way to get rid of the dependency
String model = Command.NEW_MODEL;
if(commObject.has(HistoryArguments.model.name()))
model = commObject.getString(HistoryArguments.model.name());
Command comm = cf.createCommand(inputParamArr, model, workspace);
if(comm != null){
try {
comm.setExecutedInBatch(true);
logger.debug("Executing command: " + commandName);
uc.append(workspace.getCommandHistory().doCommand(comm, workspace, saveToHistory));
comm.setExecutedInBatch(false);
} catch(Exception e) {
logger.error("Error executing command: "+ commandName + ". Please notify this error. \nInputs:" + inputParamArr, e);
//make these InfoUpdates so that the UI can still process the rest of the model
return new UpdateContainer(new TrivialErrorUpdate("Error executing command " + commandName + " from history"));
}
}
else {
logger.error("Error occured while creating command (Could not create Command object): "
+ commObject.get(HistoryArguments.commandName.name()));
return new UpdateContainer(new TrivialErrorUpdate("Error executing command " + commandName + " from history"));
}
} catch (UnsupportedOperationException ignored) {
}
}
return uc;
} catch(Exception e) {
logger.error("Error executing command: "+ commandName + ".", e);
//make these InfoUpdates so that the UI can still process the rest of the model
return new UpdateContainer(new TrivialErrorUpdate("Error executing command " + commandName + " from history"));
}
}
private boolean ignoreIfBeforeColumnDoesntExist(String commandName) {
boolean ignore = false;
for(String ignoreCom : commandsIgnoreNodeBefore) {
if(commandName.equals(ignoreCom)) {
ignore = true;
break;
}
}
return ignore;
}
public UpdateContainer normalizeCommandHistoryJsonInput(Workspace workspace, String worksheetId,
JSONArray inputArr, String commandName, boolean addIfNonExist) throws JSONException {
UpdateContainer uc = null;
HTable hTable = workspace.getWorksheet(worksheetId).getHeaders();
for (int i = 0; i < inputArr.length(); i++) {
JSONObject inpP = inputArr.getJSONObject(i);
if (inpP.getString(ClientJsonKeys.name.name()).equals("outputColumns") || inpP.getString(ClientJsonKeys.name.name()).equals("inputColumns"))
continue;
/*** Check the input parameter type and accordingly make changes ***/
if(HistoryJsonUtil.getParameterType(inpP) == ParameterType.hNodeId) {
JSONArray hNodeJSONRep = new JSONArray(inpP.get(ClientJsonKeys.value.name()).toString());
for (int j=0; j<hNodeJSONRep.length(); j++) {
JSONObject cNameObj = (JSONObject) hNodeJSONRep.get(j);
if(hTable == null) {
AbstractUpdate update = new TrivialErrorUpdate("null HTable while normalizing JSON input for the command " + commandName);
if (uc == null)
uc = new UpdateContainer(update);
else
uc.add(update);
continue;
}
String nameObjColumnName = cNameObj.getString("columnName");
logger.debug("Column being normalized: "+ nameObjColumnName);
HNode node = hTable.getHNodeFromColumnName(nameObjColumnName);
if(node == null) { //Because add column can happen even if the column after which it is to be added is not present
AbstractUpdate update = new TrivialErrorUpdate(nameObjColumnName + " does not exist, using empty values");
if (uc == null)
uc = new UpdateContainer(update);
else
uc.add(update);
if (addIfNonExist) {
node = hTable.addHNode(nameObjColumnName, HNodeType.Regular, workspace.getWorksheet(worksheetId), workspace.getFactory());
}
else {
continue;
}
}
if (j == hNodeJSONRep.length()-1) { // Found!
if(node != null)
inpP.put(ClientJsonKeys.value.name(), node.getId());
else {
//Get the id of the last node in the table
ArrayList<String> allNodeIds = hTable.getOrderedNodeIds();
//TODO check for allNodeIds.size == 0
String lastNodeId = allNodeIds.get(allNodeIds.size()-1);
inpP.put(ClientJsonKeys.value.name(), lastNodeId);
}
hTable = workspace.
getWorksheet(worksheetId).getHeaders();
} else if(node != null) {
hTable = node.getNestedTable();
if (hTable == null && addIfNonExist) {
hTable = node.addNestedTable("NestedTable", workspace.getWorksheet(worksheetId), workspace.getFactory());
}
}
}
} else if(HistoryJsonUtil.getParameterType(inpP) == ParameterType.linkWithHNodeId) {
JSONObject link = new JSONObject(inpP.get(ClientJsonKeys.value.name()).toString());
Object subject = link.get("subject");
String predicate = link.getString("predicate");
Object object = link.get("object");
String objectHNodeId, subjectHNodeId;
if(subject instanceof JSONArray) {
subjectHNodeId = generateHNodeId((JSONArray)subject, hTable, addIfNonExist, uc);
} else {
subjectHNodeId = subject.toString();
}
if(object instanceof JSONArray) {
objectHNodeId = generateHNodeId((JSONArray)object, hTable, addIfNonExist, uc);
} else {
objectHNodeId = object.toString();
}
inpP.put(ClientJsonKeys.value.name(), subjectHNodeId + "---" + predicate + "---" + objectHNodeId);
} else if(HistoryJsonUtil.getParameterType(inpP) == ParameterType.worksheetId) {
inpP.put(ClientJsonKeys.value.name(), worksheetId);
} else if (HistoryJsonUtil.getParameterType(inpP) == ParameterType.hNodeIdList) {
JSONArray hNodes = new JSONArray(inpP.get(ClientJsonKeys.value.name()).toString());
for (int k = 0; k < hNodes.length(); k++) {
JSONObject hnodeJSON = hNodes.getJSONObject(k);
JSONArray hNodeJSONRep = new JSONArray(hnodeJSON.get(ClientJsonKeys.value.name()).toString());
for (int j=0; j<hNodeJSONRep.length(); j++) {
JSONObject cNameObj = (JSONObject) hNodeJSONRep.get(j);
if(hTable == null) {
AbstractUpdate update = new TrivialErrorUpdate("null HTable while normalizing JSON input for the command " + commandName);
if (uc == null)
uc = new UpdateContainer(update);
else
uc.add(update);
continue;
}
String nameObjColumnName = cNameObj.getString("columnName");
logger.debug("Column being normalized: "+ nameObjColumnName);
HNode node = hTable.getHNodeFromColumnName(nameObjColumnName);
if(node == null) { //Because add column can happen even if the column after which it is to be added is not present
AbstractUpdate update = new TrivialErrorUpdate(nameObjColumnName + " does not exist, using empty values");
if (uc == null)
uc = new UpdateContainer(update);
else
uc.add(update);
if (addIfNonExist) {
hTable.addHNode(nameObjColumnName, HNodeType.Regular, workspace.getWorksheet(worksheetId), workspace.getFactory());
}
else {
continue;
}
}
if (j == hNodeJSONRep.length()-1) { // Found!
if(node != null)
hnodeJSON.put(ClientJsonKeys.value.name(), node.getId());
else {
//Get the id of the last node in the table
ArrayList<String> allNodeIds = hTable.getOrderedNodeIds();
String lastNodeId = allNodeIds.get(allNodeIds.size()-1);
hnodeJSON.put(ClientJsonKeys.value.name(), lastNodeId);
}
hTable = workspace.
getWorksheet(worksheetId).getHeaders();
} else if(node != null) {
hTable = node.getNestedTable();
if (hTable == null && addIfNonExist) {
hTable = node.addNestedTable("NestedTable", workspace.getWorksheet(worksheetId), workspace.getFactory());
}
}
}
}
inpP.put(ClientJsonKeys.value.name(), hNodes.toString());
}
else if (HistoryJsonUtil.getParameterType(inpP) == ParameterType.orderedColumns) {
JSONArray hNodes = new JSONArray(inpP.get(ClientJsonKeys.value.name()).toString());
for (int k = 0; k < hNodes.length(); k++) {
JSONObject hnodeJSON = hNodes.getJSONObject(k);
JSONArray hNodeJSONRep = new JSONArray(hnodeJSON.get(ClientJsonKeys.id.name()).toString());
processHNodeId(hNodeJSONRep, hTable, commandName, hnodeJSON);
if (hnodeJSON.has(ClientJsonKeys.children.name())) {
JSONArray children = new JSONArray(hnodeJSON.get(ClientJsonKeys.children.name()).toString());
hnodeJSON.put(ClientJsonKeys.children.name(), processChildren(children, hTable, commandName));
}
}
inpP.put(ClientJsonKeys.value.name(), hNodes.toString());
}
}
return uc;
}
private String generateHNodeId(JSONArray hNodeJSONRep, HTable hTable, boolean addIfNonExist, UpdateContainer uc) {
String hNodeId = null;
for (int j=0; j<hNodeJSONRep.length(); j++) {
JSONObject cNameObj = (JSONObject) hNodeJSONRep.get(j);
if(hTable == null) {
AbstractUpdate update = new TrivialErrorUpdate("null HTable while normalizing JSON input for the command");
if (uc == null)
uc = new UpdateContainer(update);
else
uc.add(update);
continue;
}
String nameObjColumnName = cNameObj.getString("columnName");
logger.debug("Column being normalized: "+ nameObjColumnName);
HNode node = hTable.getHNodeFromColumnName(nameObjColumnName);
if(node == null) { //Because add column can happen even if the column after which it is to be added is not present
AbstractUpdate update = new TrivialErrorUpdate(nameObjColumnName + " does not exist, using empty values");
if (uc == null)
uc = new UpdateContainer(update);
else
uc.add(update);
if (addIfNonExist) {
node = hTable.addHNode(nameObjColumnName, HNodeType.Regular, workspace.getWorksheet(worksheetId), workspace.getFactory());
}
else {
continue;
}
}
if (j == hNodeJSONRep.length()-1) { // Found!
if(node != null)
hNodeId = node.getId();
else {
//Get the id of the last node in the table
ArrayList<String> allNodeIds = hTable.getOrderedNodeIds();
//TODO check for allNodeIds.size == 0
String lastNodeId = allNodeIds.get(allNodeIds.size()-1);
hNodeId = lastNodeId;
}
hTable = workspace.
getWorksheet(worksheetId).getHeaders();
} else if(node != null) {
hTable = node.getNestedTable();
if (hTable == null && addIfNonExist) {
hTable = node.addNestedTable("NestedTable", workspace.getWorksheet(worksheetId), workspace.getFactory());
}
}
}
return hNodeId;
}
private boolean processHNodeId(JSONArray hNodeJSONRep, HTable hTable, String commandName, JSONObject hnodeJSON) {
for (int j=0; j<hNodeJSONRep.length(); j++) {
JSONObject cNameObj = (JSONObject) hNodeJSONRep.get(j);
if(hTable == null) {
return false;
}
String nameObjColumnName = cNameObj.getString("columnName");
logger.debug("Column being normalized: "+ nameObjColumnName);
HNode node = hTable.getHNodeFromColumnName(nameObjColumnName);
if(node == null && !ignoreIfBeforeColumnDoesntExist(commandName)) { //Because add column can happen even if the column after which it is to be added is not present
logger.info("null HNode " + nameObjColumnName + " while normalizing JSON input for the command " + commandName);
return false;
}
if (j == hNodeJSONRep.length()-1) { // Found!
if(node != null)
hnodeJSON.put(ClientJsonKeys.id.name(), node.getId());
else {
//Get the id of the last node in the table
ArrayList<String> allNodeIds = hTable.getOrderedNodeIds();
String lastNodeId = allNodeIds.get(allNodeIds.size()-1);
hnodeJSON.put(ClientJsonKeys.id.name(), lastNodeId);
}
hTable = workspace.
getWorksheet(worksheetId).getHeaders();
} else if(node != null) {
hTable = node.getNestedTable();
}
}
return true;
}
private JSONArray processChildren(JSONArray children, HTable hTable, String commandName) {
for (int i = 0; i < children.length(); i++) {
JSONObject obj = children.getJSONObject(i);
JSONArray array = new JSONArray(obj.get(ClientJsonKeys.id.name()).toString());
processHNodeId(array, hTable, commandName, obj);
if (obj.has(ClientJsonKeys.children.name())) {
obj.put(ClientJsonKeys.children.name(), processChildren(new JSONArray(obj.get(ClientJsonKeys.children.name()).toString()), hTable, commandName));
}
}
return children;
}
}