de.bund.bfr.knime.fsklab.deprecatednodes/src-1_7_2/de/bund/bfr/knime/fsklab/nodes/v1_7_2/editor/FSKEditorJSNodeModel.java
/*
***************************************************************************************************
* Copyright (c) 2017 Federal Institute for Risk Assessment (BfR), Germany
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* Contributors: Department Biological Safety - BfR
*************************************************************************************************
*/
package de.bund.bfr.knime.fsklab.nodes.v1_7_2.editor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.util.SystemOutLogger;
import org.emfjson.jackson.module.EMFModule;
import org.knime.core.node.CanceledExecutionException;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeLogger;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.port.PortObject;
import org.knime.core.node.port.PortObjectHolder;
import org.knime.core.node.port.PortObjectSpec;
import org.knime.core.node.port.PortType;
import org.knime.core.node.web.ValidationError;
import org.knime.core.node.workflow.NativeNodeContainer;
import org.knime.core.node.workflow.NodeContext;
import org.knime.core.node.workflow.WorkflowContext;
import org.knime.core.node.workflow.WorkflowEvent;
import org.knime.core.node.workflow.WorkflowListener;
import org.knime.core.node.workflow.WorkflowManager;
import org.knime.core.util.FileUtil;
import org.knime.core.util.IRemoteFileUtilsService;
import org.knime.js.core.node.AbstractWizardNodeModel;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
import de.bund.bfr.fskml.RScript;
import de.bund.bfr.knime.fsklab.FskPortObject;
import de.bund.bfr.knime.fsklab.FskPortObjectSpec;
import de.bund.bfr.knime.fsklab.FskSimulation;
import de.bund.bfr.knime.fsklab.nodes.FSKEditorJSNodeFactory;
import de.bund.bfr.knime.fsklab.nodes.FSKEditorJSViewRepresentation;
import de.bund.bfr.knime.fsklab.nodes.FSKEditorJSViewValue;
import de.bund.bfr.knime.fsklab.nodes.NodeUtils;
import de.bund.bfr.metadata.swagger.GenericModel;
import de.bund.bfr.metadata.swagger.Model;
import de.bund.bfr.metadata.swagger.Parameter;
import metadata.EmfMetadataModule;
import metadata.SwaggerUtil;
/**
* Fsk Editor JS node model.
*/
public final class FSKEditorJSNodeModel
extends AbstractWizardNodeModel<FSKEditorJSViewRepresentation, FSKEditorJSViewValue>
implements PortObjectHolder {
private static final NodeLogger LOGGER = NodeLogger.getLogger("Fskx JS Editor Model");
private static final ObjectMapper MAPPER;
static {
JsonFactory jsonFactory = new JsonFactory();
jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
jsonFactory.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
MAPPER = new ObjectMapper(jsonFactory).registerModule(new ThreeTenModule())
.registerModule(new EMFModule()).registerModule(new EmfMetadataModule());
MAPPER.setSerializationInclusion(Include.NON_NULL);
}
private final FSKEditorJSNodeSettings nodeSettings = new FSKEditorJSNodeSettings();
private FskPortObject m_port;
// Input and output port types
private static final PortType[] IN_TYPES = {FskPortObject.TYPE_OPTIONAL};
private static final PortType[] OUT_TYPES = {FskPortObject.TYPE};
private static final String VIEW_NAME = new FSKEditorJSNodeFactory().getInteractiveViewName();
static final AtomicLong TEMP_DIR_UNIFIER = new AtomicLong((int) (100000 * Math.random()));
String nodeWithId;
String nodeName;
String nodeId;
public FSKEditorJSNodeModel() {
super(IN_TYPES, OUT_TYPES, VIEW_NAME);
}
@Override
public FSKEditorJSViewRepresentation createEmptyViewRepresentation() {
return new FSKEditorJSViewRepresentation();
}
@Override
public FSKEditorJSViewValue createEmptyViewValue() {
return new FSKEditorJSViewValue();
}
@Override
public String getJavascriptObjectID() {
return "de.bund.bfr.knime.fsklab.v1.7.2.editor.component";
}
@Override
public boolean isHideInWizard() {
return false;
}
// This method is being called just when user click apply or close with saving options
@Override
public ValidationError validateViewValue(FSKEditorJSViewValue viewContent) {
// TODO
/*
* Use SchemaFactory to create new Schema and Validator from it and then apply
* validator.validate() to validate the EMF Object. Any error after the validate can be return
* back to the javascript view as ValidationError Object which can contains the error message
* EObject generalInformation; try { generalInformation =
* getEObjectFromJson(viewContent.getGeneralInformation(), GeneralInformation.class); EObject
* feed =
* (EObject)generalInformation.eGet(generalInformation.eClass().getEStructuralFeature("feed"));
*
* Diagnostician validator = Diagnostician.INSTANCE;
*
* // Validate the feed and inspect the resulting diagnostic.
* org.eclipse.emf.common.util.Diagnostic diagnostic = validator.validate(feed);
*
* return new ValidationError(diagnostic.toString()); } catch (InvalidSettingsException e) {
* e.printStackTrace(); }
*
*/
return null;
}
@Override
public void saveCurrentValue(NodeSettingsWO content) {}
@Override
public FSKEditorJSViewValue getViewValue() {
FSKEditorJSViewValue val;
synchronized (getLock()) {
val = super.getViewValue();
if (val == null) {
val = createEmptyViewValue();
}
}
return val;
}
@Override
protected PortObjectSpec[] configure(PortObjectSpec[] inSpecs) throws InvalidSettingsException {
return new PortObjectSpec[] {FskPortObjectSpec.INSTANCE};
}
/**
* Downloads a file from a URL.The code here is considering that the fileURL is using KNIME
* Protocol
*
* @param fileURL HTTP URL of the file to be downloaded
* @param workingDir path of the directory to save the file
* @throws IOException
* @throws URISyntaxException
* @throws InvalidSettingsException
*/
public void downloadFileToWorkingDir(String fileURL, String workingDir)
throws IOException, URISyntaxException, InvalidSettingsException {
String fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1, fileURL.length());
String destinationPath = workingDir + File.separator + fileName;
File fileTodownload = new File(destinationPath);
LOGGER.info("JS EDITOR path to write to: " + destinationPath);
try (InputStream inStream = FileUtil.openInputStream(fileURL);
OutputStream outStream = new FileOutputStream(fileTodownload)) {
IOUtils.copy(inStream, outStream);
}
}
@Override
protected PortObject[] performExecute(PortObject[] inObjects, ExecutionContext exec)
throws Exception {
nodeWithId = NodeContext.getContext().getNodeContainer().getNameWithID();
nodeName = NodeContext.getContext().getNodeContainer().getName();
nodeId = NodeContext.getContext().getNodeContainer().getID().toString().split(":")[1];
FskPortObject inObj1;
FskPortObject outObj;
if (inObjects.length > 0 && inObjects[0] != null) {
inObj1 = (FskPortObject) inObjects[0];
} else {
String workingDirectory = "";
String readme = "";
// Import readme
if (!nodeSettings.getReadme().isEmpty()) {
File readmeFile = FileUtil.getFileFromURL(FileUtil.toURL(nodeSettings.getReadme()));
readme = FileUtils.readFileToString(readmeFile, "UTF-8");
}
if (!nodeSettings.getWorkingDirectory().isEmpty()) {
workingDirectory = nodeSettings.getWorkingDirectory();
} else {
// each sub Model has it's own working directory to avoid resource conflict.
// get current node's and workflow's context
NodeContext nodeContext = NodeContext.getContext();
WorkflowManager wfm = nodeContext.getWorkflowManager();
WorkflowContext workflowContext = wfm.getContext();
File currentWorkingDirectory =
new File(workflowContext.getCurrentLocation(),
nodeContext.getNodeContainer().getNameWithID().toString().replaceAll("\\W", "")
.replace(" ", "") + "_" + "workingDirectory"
+ TEMP_DIR_UNIFIER.getAndIncrement());
currentWorkingDirectory.mkdir();
workingDirectory = currentWorkingDirectory.getPath();
}
inObj1 = new FskPortObject(workingDirectory, readme, new ArrayList<>());
inObj1.model = "";
inObj1.viz = "";
inObj1.modelMetadata = new GenericModel();
inObj1.modelMetadata.modelType("genericModel");
}
// Clone input object
synchronized (getLock()) {
FSKEditorJSViewValue fskEditorProxyValue = getViewValue();
if (!StringUtils.isBlank(nodeSettings.modelType)) {
fskEditorProxyValue.modelType = nodeSettings.modelType;
} else if(inObj1 != null && inObj1.modelMetadata != null) {
fskEditorProxyValue.modelType = inObj1.modelMetadata.getModelType();
} else {
fskEditorProxyValue.modelType = "GenericModel";
}
// If not executed
if (fskEditorProxyValue.getModelMetaData() == null) {
if (inObjects[0] == null) {
loadJsonSetting();
}
if (fskEditorProxyValue.getModelMetaData() == null) {
fskEditorProxyValue.setModelMetaData( FromOjectToJSON(inObj1.modelMetadata));
fskEditorProxyValue.firstModelScript = inObj1.model;
fskEditorProxyValue.firstModelViz = inObj1.viz;
fskEditorProxyValue.modelType = inObj1.modelMetadata.getModelType();
fskEditorProxyValue.readme = inObj1.getReadme();
}
} else {
if (fskEditorProxyValue.notCompleted) {
setWarningMessage("Output Parameters are not configured correctly");
}
if (StringUtils.isNotEmpty(fskEditorProxyValue.validationErrors)) {
setWarningMessage("\n" + (fskEditorProxyValue.validationErrors).replaceAll("\"", "")
.replaceAll(",,,", "\n"));
}
}
outObj = inObj1;
Class <? extends Model> modelClass = SwaggerUtil.modelClasses.get(fskEditorProxyValue.modelType);
outObj.modelMetadata = getObjectFromJson(fskEditorProxyValue.getModelMetaData(), modelClass);
if (outObj.modelMetadata != null && SwaggerUtil.getModelMath(outObj.modelMetadata) != null) {
List<Parameter> parametersList = SwaggerUtil.getParameter(outObj.modelMetadata);
// Create simulation
if (parametersList != null && parametersList.size() > 0) {
FskSimulation defaultSimulation = NodeUtils.createDefaultSimulation(parametersList);
if (outObj.simulations.size() > 0) {
List<FskSimulation> defaultSim =
outObj.simulations.stream().filter(sim -> "defaultSimulation".equals(sim.getName()))
.collect(Collectors.toList());
defaultSim.stream().forEach(sim -> {
outObj.simulations.remove(sim);
});
}
outObj.simulations.add(0, defaultSimulation);
} else {
parametersList = new ArrayList<>();
outObj.simulations.add(0, NodeUtils.createDefaultSimulation(parametersList));
}
}
outObj.model = fskEditorProxyValue.firstModelScript;
outObj.viz = fskEditorProxyValue.firstModelViz;
outObj.setReadme(fskEditorProxyValue.readme);
// resources files via fskEditorProxyValue will be available only in online mode of the JS
// editor
if (fskEditorProxyValue.resourcesFiles != null
&& fskEditorProxyValue.resourcesFiles.length != 0) {
for (String fileRequestString : fskEditorProxyValue.resourcesFiles) {
downloadFileToWorkingDir(fileRequestString, outObj.getWorkingDirectory());
}
// delete the parent folder of the uploaded files after moving them to the working
// directory.
// parentFolderPath is always uses KNIME protocol
String firstFile = fskEditorProxyValue.resourcesFiles[0];
String parentFolderPath = firstFile.substring(0, firstFile.lastIndexOf("/"));
BundleContext ctx =
FrameworkUtil.getBundle(IRemoteFileUtilsService.class).getBundleContext();
ServiceReference<IRemoteFileUtilsService> ref =
ctx.getServiceReference(IRemoteFileUtilsService.class);
if (ref != null) {
try {
ctx.getService(ref).delete(new URL(parentFolderPath));
} finally {
ctx.ungetService(ref);
}
}
}
// Collect R packages
final Set<String> librariesSet = new HashSet<>();
librariesSet.addAll(new RScript(outObj.model).getLibraries());
librariesSet.addAll(new RScript(outObj.viz).getLibraries());
outObj.packages.clear();
outObj.packages.addAll(new ArrayList<>(librariesSet));
}
NodeContext.getContext().getWorkflowManager().addListener(new WorkflowListener() {
@Override
public void workflowChanged(WorkflowEvent event) {
if (event.getType().equals(WorkflowEvent.Type.NODE_REMOVED)
&& event.getOldValue() instanceof NativeNodeContainer) {
NativeNodeContainer nnc = (NativeNodeContainer) event.getOldValue();
File directory =
nnc.getDirectNCParent().getProjectWFM().getContext().getCurrentLocation();
String nncnamewithId = nnc.getNameWithID();
if (nncnamewithId.equals(nodeWithId)) {
String containerName = nodeName + " (#" + nodeId + ") setting";
String settingFolderPath = directory.getPath().concat("/" + containerName);
File settingFolder = new File(settingFolderPath);
try {
if (settingFolder.exists()) {
Files.walk(settingFolder.toPath()).sorted(Comparator.reverseOrder())
.map(Path::toFile).forEach(File::delete);
}
} catch (IOException e) {
// nothing to do
}
}
}
}
});
return new PortObject[] {outObj};
}
private static String FromOjectToJSON(final Object object) throws JsonProcessingException {
String jsonStr = MAPPER.writeValueAsString(object);
return jsonStr;
}
private static <T> T getObjectFromJson(String jsonStr, Class<T> valueType)
throws InvalidSettingsException, JsonParseException, JsonMappingException, IOException {
Object object = MAPPER.readValue(jsonStr, valueType);
return valueType.cast(object);
}
@Override
protected void performReset() {
m_port = null;
nodeSettings.modelMetaData = "";
nodeSettings.setReadme("");
}
@Override
protected void useCurrentValueAsDefault() {}
@Override
protected void saveSettingsTo(NodeSettingsWO settings) {
/*
* nodeSettings.generalInformation = getViewValue().getGeneralInformation(); nodeSettings.scope
* = getViewValue().getScope(); nodeSettings.dataBackground =
* getViewValue().getDataBackground(); nodeSettings.modelMath = getViewValue().getModelMath();
* nodeSettings.model = getViewValue().getFirstModelScript(); nodeSettings.viz =
* getViewValue().getFirstModelViz(); nodeSettings.save(settings);
*/
try {
FSKEditorJSViewValue vv = getViewValue();
saveJsonSetting(vv.getModelMetaData(), vv.firstModelScript, vv.firstModelViz, vv.readme);
} catch (IOException | CanceledExecutionException e) {
e.printStackTrace();
}
}
@Override
protected void loadValidatedSettingsFrom(NodeSettingsRO settings)
throws InvalidSettingsException {
try {
nodeSettings.load(settings);
loadJsonSetting();
} catch (IOException | CanceledExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected void loadJsonSetting() throws IOException, CanceledExecutionException {
File directory =
NodeContext.getContext().getWorkflowManager().getContext().getCurrentLocation();
String name = NodeContext.getContext().getNodeContainer().getName();
// Dirty workaround. KNIME adds (deprecated) for these nodes. The old folder does not have it and are ignored.
// For now, (deprecated) is removed from the name so the old folders can be loaded.
name = StringUtils.remove(name, " (deprecated)");
String id = NodeContext.getContext().getNodeContainer().getID().toString().split(":")[1];
String containerName = name + " (#" + id + ") setting";
String settingFolderPath = directory.getPath().concat("/" + containerName);
File settingFolder = new File(settingFolderPath);
// Read configuration strings
nodeSettings.modelMetaData = NodeUtils.readConfigString(settingFolder, "modelMetaData.json");
String modelScript = NodeUtils.readConfigString(settingFolder, "modelScript.txt");
String visualizationScript = NodeUtils.readConfigString(settingFolder, "visualization.txt");
String readme = NodeUtils.readConfigString(settingFolder, "readme.txt");
// Update view value
FSKEditorJSViewValue viewValue = getViewValue();
viewValue.setModelMetaData( nodeSettings.modelMetaData);
viewValue.firstModelScript = modelScript;
viewValue.firstModelViz = visualizationScript;
viewValue.readme = readme;
}
protected void saveJsonSetting(String modelMetaData, String modelScript,
String visualizationScript, String readme) throws IOException, CanceledExecutionException {
File directory =
NodeContext.getContext().getWorkflowManager().getContext().getCurrentLocation();
String name = NodeContext.getContext().getNodeContainer().getName();
// Dirty workaround. KNIME adds (deprecated) for these nodes. The old folder does not have it and are ignored.
// For now, (deprecated) is removed from the name so the old folders can be loaded.
name = StringUtils.remove(name, " (deprecated)");
String id = NodeContext.getContext().getNodeContainer().getID().toString().split(":")[1];
String containerName = name + " (#" + id + ") setting";
String settingFolderPath = directory.getPath().concat("/" + containerName);
File settingFolder = new File(settingFolderPath);
if (!settingFolder.exists()) {
settingFolder.mkdir();
}
NodeUtils.writeConfigString(modelMetaData, settingFolder, "modelMetaData.json");
NodeUtils.writeConfigString(modelScript, settingFolder, "modelScript.txt");
NodeUtils.writeConfigString(visualizationScript, settingFolder, "visualization.txt");
NodeUtils.writeConfigString(readme, settingFolder, "readme.txt");
}
@Override
protected void validateSettings(NodeSettingsRO settings) throws InvalidSettingsException {}
@Override
public PortObject[] getInternalPortObjects() {
return new PortObject[] {m_port};
}
@Override
public void setInternalPortObjects(PortObject[] portObjects) {
m_port = (FskPortObject) portObjects[0];
}
public void setHideInWizard(boolean hide) {}
}