/*
* RapidMiner
*
* Copyright (C) 2001-2014 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.repository.gui;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyVetoException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import com.michaelbaranov.microba.calendar.DatePicker;
import com.rapidminer.Process;
import com.rapidminer.ProcessContext;
import com.rapidminer.ProcessLocation;
import com.rapidminer.RepositoryProcessLocation;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.actions.SaveAction;
import com.rapidminer.gui.dialog.CronEditorDialog;
import com.rapidminer.gui.processeditor.ProcessContextEditor;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.ResourceLabel;
import com.rapidminer.gui.tools.ResourceTabbedPane;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.dialogs.ButtonDialog;
import com.rapidminer.gui.tools.dialogs.ConfirmDialog;
import com.rapidminer.repository.MalformedRepositoryLocationException;
import com.rapidminer.repository.Repository;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.repository.RepositoryManager;
import com.rapidminer.repository.remote.SchedulerResponse;
import com.rapidminer.repository.remote.ProcessSchedulerConfig;
import com.rapidminer.repository.remote.ProcessSchedulerFactory;
import com.rapidminer.repository.remote.RemoteRepository;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.Observable;
import com.rapidminer.tools.Observer;
/**
* A dialog that lets the user run a process on a remote server, either now, at
* a fixed later point of time or scheduled by a cron expression.
*
* @author Simon Fischer, Nils Woehler
*
*/
public class RunRemoteDialog extends ButtonDialog {
private static final long serialVersionUID = 1L;
public static final String DEFAULT = "DEFAULT";
private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(); //new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
private final DatePicker dateField = new DatePicker(new Date(), DATE_FORMAT); // new
// JTextField(DATE_FORMAT.format(new
// Date()), 30);
private final JTextField cronField = new JTextField(30);
private final JComboBox repositoryBox = new JComboBox();
private static int lastRepositoryIndexSelected = 0;
private final JLabel dateLabel = new ResourceLabel("runremotedialog.date");
private final JLabel cronLabel = new ResourceLabel("runremotedialog.cronexpression");
private JLabel cronHelpIconLabel;
private JButton cronEditorButton;
private JComboBox queueComboBox;
private final JCheckBox startBox = new JCheckBox(new ResourceAction("runremotedialog.cronstart") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
enableComponents();
}
});
private final DatePicker startField = new DatePicker(new Date(), DATE_FORMAT);
private final DatePicker endField = new DatePicker(new Date(), DATE_FORMAT);
private final JCheckBox endBox = new JCheckBox(new ResourceAction("runremotedialog.cronend") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
enableComponents();
}
});
private final JTextField processField = new JTextField(30);
private JRadioButton nowButton;
private JRadioButton onceButton;
private JRadioButton cronButton;;
private final ResourceTabbedPane tabs = new ResourceTabbedPane("runremotedialog");
private ProcessContext context = new ProcessContext();
private CronEditorDialog cronEditor = new CronEditorDialog();
private ResourceLabel queueLabel;
private DefaultComboBoxModel queueModel;
private boolean scheduleOnOk;
private ProcessContextEditor contextPanel;
public RunRemoteDialog(Process process, boolean scheduleOnOk) {
super("runremotedialog", true, new Object[] {});
this.scheduleOnOk = scheduleOnOk;
setModal(true);
dateField.setStripTime(false);
dateField.setKeepTime(true);
startField.setStripTime(false);
startField.setKeepTime(true);
endField.setStripTime(false);
endField.setKeepTime(true);
startBox.setSelected(false);
endBox.setSelected(false);
ProcessLocation processLocation = process.getProcessLocation();
if ((processLocation != null) && (processLocation instanceof RepositoryProcessLocation)) {
processField.setText(((RepositoryProcessLocation) processLocation).getRepositoryLocation().getPath());
} else {
processField.setText("");
}
processField.selectAll();
final JButton okButton = makeOkButton("run_remote_dialog_schedule");
final JButton cancelButton = makeCancelButton();
List<RemoteRepository> remoteRepositories = RepositoryManager.getInstance(null).getRemoteRepositories();
DefaultComboBoxModel aModel = new DefaultComboBoxModel(remoteRepositories.toArray());
repositoryBox.setModel(aModel);
repositoryBox.setRenderer(new DefaultListCellRenderer() {
private static final long serialVersionUID = 1L;
@Override
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected,
cellHasFocus);
if (value instanceof RemoteRepository) {
RemoteRepository repo = (RemoteRepository) value;
label.setText("<html>" + repo.getAlias() + "<br/><small style=\"color:gray\">(" + repo.getBaseUrl() + ")</small></html>");
}
return label;
}
});
if (repositoryBox.getItemCount() < lastRepositoryIndexSelected) {
lastRepositoryIndexSelected = 0;
}
try {
RepositoryLocation repositoryLocation = process.getRepositoryLocation();
if (repositoryLocation != null) {
Repository repository = repositoryLocation.getRepository();
if (repository instanceof RemoteRepository) {
repositoryBox.setSelectedItem(repository);
} else {
repositoryBox.setSelectedIndex(lastRepositoryIndexSelected);
}
} else {
repositoryBox.setSelectedIndex(lastRepositoryIndexSelected);
}
} catch (RepositoryException e1) {
e1.printStackTrace();
repositoryBox.setSelectedIndex(lastRepositoryIndexSelected);
}
repositoryBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
okButton.setEnabled(repositoryBox.getSelectedItem() != null);
lastRepositoryIndexSelected = repositoryBox.getSelectedIndex();
updateExecutionQueueComboBox();
}
});
repositoryBox.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
updateExecutionQueueComboBox();
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {}
});
RepositoryManager.getInstance(null).addObserver(new Observer<Repository>() {
@Override
public void update(Observable<Repository> observable, Repository arg) {
repositoryBox.setModel(new DefaultComboBoxModel(RepositoryManager.getInstance(null).getRemoteRepositories().toArray()));
if ((arg != null) && (arg instanceof RemoteRepository)) {
repositoryBox.setSelectedItem(arg);
}
pack();
}
}, true);
JPanel schedulePanel = makeSchedulePanel();
// copy context
// List<String> inputRepositoryLocations = new LinkedList<String>();
// inputRepositoryLocations.addAll(process.getContext().getInputRepositoryLocations());
// context.setInputRepositoryLocations(inputRepositoryLocations);
//
// List<String> outputRepositoryLocations = new LinkedList<String>();
// outputRepositoryLocations.addAll(process.getContext().getOutputRepositoryLocations());
// context.setOutputRepositoryLocations(outputRepositoryLocations);
//
// List<Pair<String, String>> macros = new LinkedList<Pair<String,String>>();
// for (Pair<String,String> macro : process.getContext().getMacros()) {
// macros.add(new Pair<String,String>(macro.getFirst(),macro.getSecond()));
// }
// context.setMacros(macros);
contextPanel = new ProcessContextEditor(process, context);
tabs.addTabI18N("schedule", schedulePanel);
tabs.addTabI18N("context", contextPanel);
// Buttons
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(nowButton);
buttonGroup.add(onceButton);
buttonGroup.add(cronButton);
nowButton.setSelected(true);
layoutDefault(tabs, NORMAL, okButton, cancelButton);
enableComponents();
okButton.setEnabled(repositoryBox.getSelectedItem() != null);
updateExecutionQueueComboBox();
}
private JPanel makeSchedulePanel() {
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.FIRST_LINE_START;
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
c.weightx = 1;
c.weighty = 0;
// Repository
JPanel repositoryPanel = new JPanel(new GridBagLayout());
c.insets = new Insets(0, GAP, 0, GAP);
JLabel label = new ResourceLabel("runremotedialog.repository");
label.setLabelFor(repositoryBox);
c.insets = new Insets(GAP, GAP, GAP, GAP);
repositoryPanel.add(label, c);
c.gridwidth = GridBagConstraints.RELATIVE;
c.insets = new Insets(0, GAP, GAP, 0);
repositoryPanel.add(repositoryBox, c);
JButton addRepositoryButton = new JButton(RepositoryBrowser.ADD_REPOSITORY_ACTION);
addRepositoryButton.setText("");
c.weightx = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
c.insets = new Insets(0, 0, GAP, GAP);
repositoryPanel.add(addRepositoryButton, c);
// Process
label = new ResourceLabel("runremotedialog.process_location");
label.setLabelFor(processField);
c.weightx = 1;
c.insets = new Insets(GAP, GAP, 0, GAP);
repositoryPanel.add(label, c);
c.gridwidth = GridBagConstraints.RELATIVE;
c.insets = new Insets(0, GAP, GAP, 0);
repositoryPanel.add(processField, c);
JButton selectButton = new JButton(new ResourceAction(true, "repository_select_location") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
String selected = RepositoryLocationChooser.selectLocation(null, RunRemoteDialog.this);
if (selected != null) {
try {
RepositoryLocation location = new RepositoryLocation(selected);
Repository repository = location.getRepository();
String relative = location.getPath();
repositoryBox.setSelectedItem(repository);
processField.setText(relative);
} catch (Exception ex) {
processField.setText(selected);
}
processField.selectAll();
}
}
});
selectButton.setText("");
c.weightx = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
c.insets = new Insets(0, 0, GAP, GAP);
repositoryPanel.add(selectButton, c);
queueModel = new DefaultComboBoxModel();
queueComboBox = new JComboBox(queueModel);
queueLabel = new ResourceLabel("runremotedialog.execution_queue");
queueLabel.setLabelFor(queueComboBox);
c.gridwidth = GridBagConstraints.REMAINDER;
c.weightx = 1;
c.insets = new Insets(GAP, GAP, 0, GAP);
repositoryPanel.add(queueLabel, c);
repositoryPanel.add(queueComboBox, c);
c.gridwidth = GridBagConstraints.REMAINDER;
c.fill = GridBagConstraints.BOTH;
c.weighty = 1;
JPanel dummy = new JPanel();
repositoryPanel.add(dummy, c);
// RIGHT SIDE
// Now
JPanel schedPanel = new JPanel(new GridBagLayout());
nowButton = new JRadioButton(new ResourceAction("runremotedialog.now") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
enableComponents();
}
});
c.weightx = 1;
c.insets = new Insets(GAP, GAP, GAP, GAP);
c.gridwidth = GridBagConstraints.REMAINDER;
schedPanel.add(nowButton, c);
// Once
onceButton = new JRadioButton(new ResourceAction("runremotedialog.once") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
enableComponents();
}
});
c.insets = new Insets(3 * GAP, GAP, GAP, GAP);
c.gridwidth = GridBagConstraints.REMAINDER;
schedPanel.add(onceButton, c);
dateLabel.setLabelFor(dateField);
c.insets = new Insets(0, 8 * GAP, 0, GAP);
c.gridwidth = GridBagConstraints.REMAINDER;
schedPanel.add(dateLabel, c);
c.insets = new Insets(0, 8 * GAP, GAP, GAP);
schedPanel.add(dateField, c);
// Cron
cronButton = new JRadioButton(new ResourceAction("runremotedialog.cron") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
enableComponents();
}
});
c.insets = new Insets(3 * GAP, GAP, GAP, GAP);
c.gridwidth = GridBagConstraints.REMAINDER;
schedPanel.add(cronButton, c);
c.insets = new Insets(0, 8 * GAP, 0, GAP);
cronLabel.setLabelFor(cronField);
JPanel cronLabelPanel = new JPanel();
cronLabelPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.weightx = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 0);
cronLabelPanel.add(cronLabel, gbc);
cronHelpIconLabel = new JLabel();
cronHelpIconLabel.setIcon(SwingTools.createIcon("16/" + I18N.getMessage(I18N.getGUIBundle(), "gui.action.cron_help.icon")));
cronHelpIconLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (cronButton.isSelected()) {
SwingTools.showMessageDialog("cron_long_help");
}
}
});
gbc.gridx = 1;
cronLabelPanel.add(cronHelpIconLabel, gbc);
gbc.gridx = 2;
gbc.weightx = 1;
cronLabelPanel.add(Box.createHorizontalGlue(), gbc);
schedPanel.add(cronLabelPanel, c);
c.insets = new Insets(0, 8 * GAP, GAP, 0);
c.gridwidth = GridBagConstraints.RELATIVE;
schedPanel.add(cronField, c);
c.insets = new Insets(0, 0, GAP, GAP);
// ResourceLabel cronHelp = new ResourceLabel("cron_help");
cronEditorButton = new JButton(new ResourceAction(true, "cron_editor") {
private static final long serialVersionUID = 1L;
{
putValue(Action.NAME, "");
}
@Override
public void actionPerformed(ActionEvent e) {
cronEditor.prompt();
if (cronEditor.wasConfirmed()) {
cronField.setText(cronEditor.getCronExpression());
}
}
});
c.weightx = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
schedPanel.add(cronEditorButton, c);
c.weightx = 1;
c.insets = new Insets(GAP, 8 * GAP, 0, GAP);
schedPanel.add(startBox, c);
c.insets = new Insets(0, 8 * GAP, GAP, GAP);
schedPanel.add(startField, c);
c.insets = new Insets(GAP, 8 * GAP, 0, GAP);
schedPanel.add(endBox, c);
c.insets = new Insets(0, 8 * GAP, GAP, GAP);
schedPanel.add(endField, c);
JPanel panel = new JPanel(createGridLayout(1, 2));
panel.add(repositoryPanel);
panel.add(schedPanel);
return panel;
}
private void updateExecutionQueueComboBox() {
Repository selected = (Repository) repositoryBox.getSelectedItem();
if (selected != null) {
if (selected instanceof RemoteRepository) {
RemoteRepository remoteRepo = (RemoteRepository) selected;
List<String> processQueueNames = remoteRepo.getProcessQueueNames();
updateQueueComoboxModel(processQueueNames);
if (processQueueNames == null || processQueueNames.isEmpty()) {
enableQueueSelection(false);
} else {
enableQueueSelection(true);
}
} else {
enableQueueSelection(false);
updateQueueComoboxModel(null);
}
}
}
private void updateQueueComoboxModel(List<String> queueNames) {
queueModel.removeAllElements();
if (queueNames == null || queueNames.isEmpty()) {
queueModel.addElement(DEFAULT);
} else {
for (String queueName : queueNames) {
queueModel.addElement(queueName);
}
}
queueComboBox.setSelectedIndex(0);
}
private void enableQueueSelection(boolean enable) {
queueComboBox.setEnabled(enable);
queueLabel.setEnabled(enable);
}
private void enableComponents() {
dateLabel.setEnabled(onceButton.isSelected());
dateField.setEnabled(onceButton.isSelected());
cronLabel.setEnabled(cronButton.isSelected());
cronField.setEnabled(cronButton.isSelected());
cronHelpIconLabel.setEnabled(cronButton.isSelected());
cronEditorButton.setEnabled(cronButton.isSelected());
startBox.setEnabled(cronButton.isSelected());
endBox.setEnabled(cronButton.isSelected());
startField.setEnabled(cronButton.isSelected() && startBox.isSelected());
endField.setEnabled(cronButton.isSelected() && endBox.isSelected());
}
/**
* @param process may not be <code>null</code>!
* @param scheduleOnOkay if <code>true</code> the process will be scheduled when the user clicks ok
*/
public static void showDialog(Process process, boolean scheduleOnOkay) {
showDialog(process, scheduleOnOkay, null);
}
public static void showDialog(Process process, boolean scheduleOnOkay, ProcessSchedulerConfig config) {
// no RA repositories found, show message instead of dialog
if (RepositoryManager.getInstance(null).getRemoteRepositories().size() <= 0) {
SwingTools.showVerySimpleErrorMessage("schedule_on_ra_no_ra_repo_found");
return;
}
RunRemoteDialog d = new RunRemoteDialog(process, scheduleOnOkay);
if (config != null) {
d.adaptToProcessScheduleConfig(process, config);
}
d.setVisible(true);
}
public ProcessSchedulerConfig createProcessScheduleConfig() throws MalformedRepositoryLocationException {
String queueName = queueComboBox.isEnabled() ? (String) queueComboBox.getSelectedItem() : null;
if (nowButton.isSelected()) {
return new ProcessSchedulerConfig(getRepositoryLocation(), context, queueName);
} else if (onceButton.isSelected()) {
Date onceDate = dateField.getDate();
return new ProcessSchedulerConfig(getRepositoryLocation(), onceDate, context, queueName);
} else {
Date start = startBox.isSelected() ? startField.getDate() : null;
Date end = endBox.isSelected() ? endField.getDate() : null;
return new ProcessSchedulerConfig(getRepositoryLocation(), cronField.getText(), start, end, context, queueName);
}
}
private RepositoryLocation getRepositoryLocation() throws MalformedRepositoryLocationException {
Repository repo = (Repository) repositoryBox.getSelectedItem();
return new RepositoryLocation(RepositoryLocation.REPOSITORY_PREFIX + repo.getName() + processField.getText());
}
public void adaptToProcessScheduleConfig(Process process, ProcessSchedulerConfig config) {
switch (config.getMode()) {
case CRON:
cronButton.setSelected(true);
cronField.setText(config.getCronExpression());
Date start = config.getStart();
Date end = config.getEnd();
startBox.setSelected(start != null);
endBox.setSelected(end != null);
try {
startField.setDate(start);
endField.setDate(end);
} catch (PropertyVetoException e1) {}
break;
case ONCE:
onceButton.setSelected(true);
try {
dateField.setDate(config.getOnceDate());
} catch (PropertyVetoException e) {}
default:
nowButton.setSelected(true);
break;
}
contextPanel.setProcess(process, config.getContext());
enableComponents();
RepositoryLocation location = config.getLocation();
try {
repositoryBox.setSelectedItem(location.getRepository());
} catch (RepositoryException e) {
repositoryBox.setSelectedIndex(lastRepositoryIndexSelected);
}
processField.setText(location.getPath());
queueComboBox.setSelectedItem(config.getQueueName());
}
@Override
public void ok() {
if (scheduleOnOk) {
String location = processField.getText();
// check if process selected is the same as the current process in the GUI, if so check if it has been edited and ask for save
// before continuing. Otherwise the last version would be executed which can result in confusion (and therefore support tickets..)
if (RapidMinerGUI.getMainFrame().getProcess().getProcessLocation() != null) {
String mainFrameProcessLocString = ((RepositoryProcessLocation) RapidMinerGUI.getMainFrame().getProcess().getProcessLocation()).getRepositoryLocation().getPath();
if (location.equals(mainFrameProcessLocString) && RapidMinerGUI.getMainFrame().isChanged()) {
if (SwingTools.showConfirmDialog("save_before_remote_run", ConfirmDialog.OK_CANCEL_OPTION) == ConfirmDialog.CANCEL_OPTION) {
// user does not want to save "dirty" process, abort
return;
}
SaveAction.save(RapidMinerGUI.getMainFrame().getProcess());
}
}
try {
SchedulerResponse response = ProcessSchedulerFactory.getInstance().getProcessScheduler().scheduleProcess(createProcessScheduleConfig(), null);
Date firstExec = response.getFirstExecution();
SwingTools.showMessageDialog("process_will_first_run", firstExec);
} catch (Exception e) {
SwingTools.showSimpleErrorMessage("run_proc_remote", e.getLocalizedMessage());
return;
}
}
dispose();
}
}