/* * * * Copyright (C) 2015 CS SI * * * * 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/ * */ package org.esa.snap.ui.tooladapter.dialogs; import com.bc.ceres.binding.Property; import com.bc.ceres.core.ProgressMonitor; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.gpf.GPF; import org.esa.snap.core.gpf.Operator; import org.esa.snap.core.gpf.descriptor.*; import org.esa.snap.core.gpf.descriptor.template.TemplateFile; import org.esa.snap.core.gpf.operators.tooladapter.DefaultOutputConsumer; import org.esa.snap.core.gpf.operators.tooladapter.ToolAdapterConstants; import org.esa.snap.core.gpf.operators.tooladapter.ToolAdapterOp; import org.esa.snap.core.gpf.ui.OperatorMenu; import org.esa.snap.core.gpf.ui.OperatorParameterSupport; import org.esa.snap.core.gpf.ui.SingleTargetProductDialog; import org.esa.snap.rcp.actions.file.SaveProductAsAction; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.AbstractDialog; import org.esa.snap.ui.AppContext; import org.esa.snap.ui.tooladapter.actions.EscapeAction; import org.esa.snap.ui.tooladapter.model.OperationType; import org.esa.snap.ui.tooladapter.preferences.ToolAdapterOptionsController; import org.esa.snap.utils.PrivilegedAccessor; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressUtils; import org.openide.util.Cancellable; import org.openide.util.NbBundle; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.List; import java.util.function.Consumer; import java.util.logging.Logger; import java.util.stream.Collectors; /** * Form dialog for running a tool adapter operator. * * @author Lucian Barbulescu. * @author Cosmin Cara */ @NbBundle.Messages({ "NoSourceProductWarning_Text=No input product was selected.\nAre you sure you want to continue?", "RequiredTargetProductMissingWarning_Text=A target product is required in adapter's template, but none was provided", "NoOutput_Text=The operator did not produce any output", "BeginOfErrorMessages_Text=The operator completed with the following errors:\n", "OutputTitle_Text=Process output", "ExecutionFailed_Text=Execution Failed", "ExecutionFailed_Message=The execution completed with errors: \n%s\n\nDo you want to try to open the resulting product?" }) public class ToolAdapterExecutionDialog extends SingleTargetProductDialog { private static final String SOURCE_PRODUCT_FIELD = "sourceProduct"; /** * Operator identifier. */ private ToolAdapterOperatorDescriptor operatorDescriptor; /** * Parameters related info. */ private OperatorParameterSupport parameterSupport; /** * The form used to get the user's input */ private ToolExecutionForm form; private Product result; private OperatorTask operatorTask; private Logger logger; private List<String> warnings; private static final String helpID = "sta_execution"; private List<ToolParameterDescriptor> artificiallyAddedParams; /** * Constructor. * * @param descriptor The operator descriptor * @param appContext The application context * @param title The dialog title */ public ToolAdapterExecutionDialog(ToolAdapterOperatorDescriptor descriptor, AppContext appContext, String title) { super(appContext, title, helpID); logger = Logger.getLogger(ToolAdapterExecutionDialog.class.getName()); initialize(descriptor); warnings = new ArrayList<>(); } /* Add workaround for SNAP-402 (JIRA) issue */ private void updatePrimitiveZeroValuesHashMap(){ try { HashMap<Class<?>, Object> primitiveZeroValuesMap = (HashMap<Class<?>, Object>) PrivilegedAccessor.getStaticValue(Property.class, "PRIMITIVE_ZERO_VALUES"); /* Update dictionary with primitive default values */ primitiveZeroValuesMap.put(Boolean.class,false); primitiveZeroValuesMap.put(Character.class, (char) 0); primitiveZeroValuesMap.put(Byte.class, (byte) 0); primitiveZeroValuesMap.put(Short.class, (short) 0); primitiveZeroValuesMap.put(Integer.class, 0); primitiveZeroValuesMap.put(Long.class, (long) 0); primitiveZeroValuesMap.put(Float.class, (float) 0); primitiveZeroValuesMap.put(Double.class, (double) 0); } catch (IllegalAccessException | NoSuchFieldException iaExc){ logger.severe(iaExc.getMessage()); } } private void initialize(ToolAdapterOperatorDescriptor descriptor) { //this.operatorDescriptor = new ToolAdapterOperatorDescriptor(descriptor); this.operatorDescriptor = descriptor; //add paraeters of template parameters artificiallyAddedParams = new ArrayList<>(); Arrays.stream(this.operatorDescriptor.getToolParameterDescriptors().toArray()).filter(p -> ((ToolParameterDescriptor)p).isTemplateParameter()). forEach(p -> artificiallyAddedParams.addAll(((TemplateParameterDescriptor)p).getParameterDescriptors())); this.operatorDescriptor.getToolParameterDescriptors().addAll(artificiallyAddedParams); this.parameterSupport = new OperatorParameterSupport(this.operatorDescriptor); Arrays.stream(this.operatorDescriptor.getToolParameterDescriptors().toArray()). filter(p -> ToolAdapterConstants.FOLDER_PARAM_MASK.equals(((ToolParameterDescriptor)p).getParameterType())). forEach(p -> parameterSupport.getPropertySet().getProperty(((ToolParameterDescriptor)p).getName()).getDescriptor().setAttribute("directory", true)); form = new ToolExecutionForm(appContext, this.operatorDescriptor, parameterSupport.getPropertySet(), getTargetProductSelector()); OperatorMenu operatorMenu = new OperatorMenu(this.getJDialog(), this.operatorDescriptor, parameterSupport, appContext, helpID); getJDialog().setJMenuBar(operatorMenu.createDefaultMenu()); EscapeAction.register(getJDialog()); /* Add workaround for SNAP-402 (JIRA) issue */ updatePrimitiveZeroValuesHashMap(); this.getJDialog().addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) {form.refreshDimension();} }); this.getJDialog().addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) {form.refreshDimension();} }); this.getJDialog().setMinimumSize(new Dimension(250, 250)); } /* Begin @Override methods section */ @Override protected void onApply() { final Product[] sourceProducts = form.getSourceProducts(); List<ParameterDescriptor> descriptors = Arrays.stream(operatorDescriptor.getParameterDescriptors()) .filter(p -> ToolAdapterConstants.TOOL_TARGET_PRODUCT_FILE.equals(p.getName())) .collect(Collectors.toList()); String templateContents; try { //templateContents = ToolAdapterIO.readOperatorTemplate(operatorDescriptor.getName()); TemplateFile template = operatorDescriptor.getTemplate(); templateContents = template.getContents(); } catch (Exception ex) { showErrorDialog(String.format("Cannot read operator template [%s]", ex.getMessage())); return; } if (Arrays.stream(sourceProducts).anyMatch(p -> p == null)) { Dialogs.Answer decision = Dialogs.requestDecision("No Product Selected", Bundle.NoSourceProductWarning_Text(), false, ToolAdapterOptionsController.PREFERENCE_KEY_SHOW_EMPTY_PRODUCT_WARNING); if (decision.equals(Dialogs.Answer.NO)) { return; } } if (descriptors.size() == 1 && form.getPropertyValue(ToolAdapterConstants.TOOL_TARGET_PRODUCT_FILE) == null && templateContents.contains("$" + ToolAdapterConstants.TOOL_TARGET_PRODUCT_FILE)) { Dialogs.showWarning(Bundle.RequiredTargetProductMissingWarning_Text()); } else { if (!canApply()) { displayWarnings(); AbstractAdapterEditor dialog = AbstractAdapterEditor.createEditorDialog(appContext, getJDialog(), operatorDescriptor, OperationType.EDIT); final int code = dialog.show(); if (code == AbstractDialog.ID_OK) { onOperatorDescriptorChanged(dialog.getUpdatedOperatorDescriptor()); } dialog.close(); } else { if (validateUserInput()) { Map<String, Product> sourceProductMap = new HashMap<>(); if (sourceProducts.length > 0) { sourceProductMap.put(SOURCE_PRODUCT_FIELD, sourceProducts[0]); } Operator op = GPF.getDefaultInstance().createOperator(operatorDescriptor.getAlias(), parameterSupport.getParameterMap(), sourceProductMap, null); for (Property property : parameterSupport.getPropertySet().getProperties()) { op.setParameter(property.getName(), property.getValue()); } op.setSourceProducts(sourceProducts); operatorTask = new OperatorTask(op, ToolAdapterExecutionDialog.this::operatorCompleted); ProgressHandle progressHandle = ProgressHandleFactory.createHandle(this.getTitle()); String progressPattern = operatorDescriptor.getProgressPattern(); ConsoleConsumer consumer; ProgressWrapper progressWrapper = new ProgressWrapper(progressHandle, progressPattern == null || progressPattern.isEmpty()); consumer = new ConsoleConsumer(operatorDescriptor.getProgressPattern(), operatorDescriptor.getErrorPattern(), operatorDescriptor.getStepPattern(), progressWrapper, form.console); form.console.clear(); progressWrapper.setConsumer(consumer); ((ToolAdapterOp) op).setProgressMonitor(progressWrapper); ((ToolAdapterOp) op).setConsumer(consumer); ProgressUtils.runOffEventThreadWithProgressDialog(operatorTask, this.getTitle(), progressHandle, true, 1, 1); } else { if (warnings.size() > 0) { displayWarnings(); } } } } } @Override public int show() { form.prepareShow(); setContent(form); return super.show(); } @Override public void hide() { form.prepareHide(); super.hide(); this.operatorDescriptor.getToolParameterDescriptors().removeAll(artificiallyAddedParams); } @Override protected Product createTargetProduct() throws Exception { return result; } @Override protected boolean canApply() { warnings.clear(); try { Path toolLocation = operatorDescriptor.resolveVariables(operatorDescriptor.getMainToolFileLocation()).toPath(); if (!(Files.exists(toolLocation) && Files.isExecutable(toolLocation))) { warnings.add(logAndReturn(String.format("Path does not exist: '%s'", toolLocation))); } Path workLocation = operatorDescriptor.resolveVariables(operatorDescriptor.getWorkingDir()).toPath(); if (!(Files.exists(workLocation))) { warnings.add(logAndReturn("Working path does not exist: '%s'", workLocation)); } ParameterDescriptor[] parameterDescriptors = operatorDescriptor.getParameterDescriptors(); if (parameterDescriptors != null && parameterDescriptors.length > 0) { for (ParameterDescriptor parameterDescriptor : parameterDescriptors) { Class<?> dataType = parameterDescriptor.getDataType(); String paramName = parameterDescriptor.getName(); if (parameterSupport.getParameterMap().containsKey(paramName)) { Object value = parameterSupport.getParameterMap().get(paramName); String currentValue = value != null ? value.toString() : null; try { if (File.class.isAssignableFrom(dataType) && (parameterDescriptor.isNotNull() || parameterDescriptor.isNotEmpty()) && (currentValue == null || currentValue.isEmpty() || !Files.exists(Paths.get(currentValue)))) { warnings.add(logAndReturn("Path does not exist: '%s'", currentValue == null ? "null" : currentValue)); } } catch (Exception ex) { warnings.add(logAndReturn("Cannot access path %s [%s]", currentValue, ex.getMessage())); } } } } for (SystemVariable variable : operatorDescriptor.getVariables()) { String value = variable.getValue(); if (value == null || value.isEmpty()) { warnings.add(logAndReturn("Variable %s is not set", variable.getKey())); } } } catch (Exception e) { warnings.add(logAndReturn(e.getMessage())); } return warnings.size() == 0; } @Override protected void onCancel() { if (operatorTask != null) { operatorTask.cancel(); } super.onCancel(); } @Override protected void onClose() { super.onClose(); } /* End @Override methods section */ private void onOperatorDescriptorChanged(ToolAdapterOperatorDescriptor newOperatorDescriptor) { initialize(newOperatorDescriptor); show(); } /** * Performs any validation on the user input. * * @return <code>true</code> if the input is valid, <code>false</code> otherwise */ private boolean validateUserInput() { boolean isValid = true; if(!operatorDescriptor.isHandlingOutputName()) { File productDir = null;//targetProductSelector.getModel().getProductDir(); Object value = form.getPropertyValue(ToolAdapterConstants.TOOL_TARGET_PRODUCT_FILE); if (value != null && value instanceof File) { productDir = ((File) value).getParentFile(); appContext.getPreferences().setPropertyString(SaveProductAsAction.PREFERENCES_KEY_LAST_PRODUCT_DIR, ((File) value).getAbsolutePath()); } isValid = (productDir != null) && productDir.exists(); if (!isValid) { warnings.add("Target product folder is not accessible or does not exist"); } } List<ToolParameterDescriptor> mandatoryParams = operatorDescriptor.getToolParameterDescriptors() .stream() .filter(d -> d.isNotEmpty() || d.isNotNull()) .collect(Collectors.toList()); Map<String, Object> parameterMap = parameterSupport.getParameterMap(); for (ToolParameterDescriptor mandatoryParam : mandatoryParams) { String name = mandatoryParam.getName(); if (!parameterMap.containsKey(name) || parameterMap.get(name) == null || parameterMap.get(name).toString().isEmpty()) { isValid = false; warnings.add(String.format("No value was assigned for the mandatory parameter [%s]", name)); } } if (operatorDescriptor.getSourceProductCount() > 0) { Product[] sourceProducts = form.getSourceProducts(); isValid &= (sourceProducts != null) && sourceProducts.length > 0 && Arrays.stream(sourceProducts).filter(sp -> sp == null).count() == 0; if (!isValid) { warnings.add("No source product was selected"); } } return isValid; } private String logAndReturn(String templateMessage, Object...params) { String message = String.format(templateMessage, params); logger.warning(message); return message; } private void displayWarnings() { StringBuilder warnMessage = new StringBuilder(); warnMessage.append("Before executing the tool, please correct the errors below:") .append("\n").append("\n"); for (String msg : warnings) { warnMessage.append("\t").append(msg).append("\n"); } Dialogs.showWarning(warnMessage.toString()); } /** * This is actually the callback method to be passed to the runnable * wrapping the operator execution. * * @param result The output product */ private void operatorCompleted(Product result) { this.result = result; super.onApply(); //displayErrors(); } private void tearDown(Throwable throwable, Product result) { //boolean hasBeenCancelled = operatorTask != null && !operatorTask.hasCompleted; if (operatorTask != null) { operatorTask.cancel(); } if (throwable != null) { if (result != null) { final Dialogs.Answer answer = Dialogs.requestDecision(Bundle.ExecutionFailed_Text(), String.format(Bundle.ExecutionFailed_Message(), throwable.getMessage()), false, null); if (answer == Dialogs.Answer.YES) { operatorCompleted(result); } } /*else displayErrors();*/ //SnapDialogs.showError(Bundle.ExecutionFailed_Text(), throwable.getMessage()); } //displayErrors(); displayErrorMessage(); } private void displayErrorMessage() { if (operatorTask != null) { List<String> errors = operatorTask.getErrors(); if (errors != null && errors.size() > 0){ Dialogs.showWarning("\nIt seems there was en error on execution or the defined tool output error pattern was found.\nPlease consult the SNAP log file"); } } } /** * Runnable for executing the operator. It requires a callback * method that is to be called when the operator has finished its * execution. */ private class OperatorTask implements Runnable, Cancellable { private Operator operator; private Consumer<Product> callbackMethod; private boolean hasCompleted; /** * Constructs a runnable for the given operator that will * call back the given method when the execution has finished. * * @param op The operator to be executed * @param callback The callback method to be invoked at completion */ OperatorTask(Operator op, Consumer<Product> callback) { operator = op; callbackMethod = callback; } @Override public boolean cancel() { if (!hasCompleted) { if (operator instanceof ToolAdapterOp) { ((ToolAdapterOp) operator).stop(); //onCancel(); } hasCompleted = true; } return true; } @Override public void run() { try { callbackMethod.accept(operator.getTargetProduct()); } catch (Throwable t) { if (operator instanceof ToolAdapterOp) { tearDown(t, ((ToolAdapterOp) operator).getResult()); } else { tearDown(t, null); } } finally { hasCompleted = true; } } List<String> getErrors() { List<String> errors = null; if (operator != null && operator instanceof ToolAdapterOp) { errors = ((ToolAdapterOp) operator).getErrors(); } return errors; } public List<String> getOutput() { List<String> allMessages = null; if (operator != null && operator instanceof ToolAdapterOp) { allMessages = ((ToolAdapterOp) operator).getExecutionOutput(); } return allMessages; } } private class ConsoleConsumer extends DefaultOutputConsumer { private ConsolePane consolePane; ConsoleConsumer(String progressPattern, String errorPattern, String stepPattern, ProgressMonitor pm, ConsolePane consolePane) { super(progressPattern, errorPattern, stepPattern, pm); this.consolePane = consolePane; } @Override public void consumeOutput(String line) { super.consumeOutput(line); if (consolePane != null) { if (SwingUtilities.isEventDispatchThread()) { consume(line); } else { SwingUtilities.invokeLater(() -> consume(line)); } } } void setVisible(boolean value) { if (this.consolePane != null) { this.consolePane.setVisible(value); } } private void consume(String line) { if (this.error == null || !this.error.matcher(line).matches()) { consolePane.appendInfo(line); } else { consolePane.appendError(line); } } } private class ProgressWrapper implements ProgressMonitor { private ProgressHandle progressHandle; private boolean isIndeterminate; private ConsoleConsumer console; ProgressWrapper(ProgressHandle handle, boolean indeterminate) { this.progressHandle = handle; this.isIndeterminate = indeterminate; } void setConsumer(ConsoleConsumer consumer) { console = consumer; } @Override public void beginTask(String taskName, int totalWork) { this.progressHandle.setDisplayName(taskName); this.progressHandle.start(totalWork, -1); if (this.isIndeterminate) { this.progressHandle.switchToIndeterminate(); } if (this.console != null) { this.console.setVisible(true); } } @Override public void done() { this.progressHandle.finish(); } @Override public void internalWorked(double work) { this.progressHandle.progress((int)work); } @Override public boolean isCanceled() { return false; } @Override public void setCanceled(boolean canceled) { this.progressHandle.suspend("Cancelled"); } @Override public void setTaskName(String taskName) { this.progressHandle.setDisplayName(taskName); } @Override public void setSubTaskName(String subTaskName) { this.progressHandle.progress(subTaskName); } @Override public void worked(int work) { internalWorked(work); } } }