/*
* 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.process;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.logging.Level;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
import com.rapid_i.repository.wsimport.ProcessResponse;
import com.rapid_i.repository.wsimport.Response;
import com.rapidminer.RepositoryProcessLocation;
import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.actions.OpenAction;
import com.rapidminer.gui.actions.RunRemoteAction;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.ResourceDockKey;
import com.rapidminer.gui.tools.ResourceLabel;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.ViewToolBar;
import com.rapidminer.gui.tools.components.ToolTipWindow;
import com.rapidminer.gui.tools.components.ToolTipWindow.TipProvider;
import com.rapidminer.repository.IOObjectEntry;
import com.rapidminer.repository.MalformedRepositoryLocationException;
import com.rapidminer.repository.RemoteProcessState;
import com.rapidminer.repository.Repository;
import com.rapidminer.repository.RepositoryConstants;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.repository.gui.ToolTipProviderHelper;
import com.rapidminer.repository.remote.RemoteRepository;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;
/**
* Displays a tree of processes running on a remote server.
*
* @author Simon Fischer, Tobias Malbrecht
*/
public class RemoteProcessViewer extends JPanel implements Dockable {
private final class OpenProcessAction extends ResourceAction {
private final Repository repository;
private final ProcessResponse processResponse;
private static final long serialVersionUID = 1L;
private OpenProcessAction(Repository repository, ProcessResponse processResponse) {
super("remoteprocessviewer.open");
this.repository = repository;
this.processResponse = processResponse;
}
public OpenProcessAction(Repository repository, OutputLocation location, TreePath tp) {
super("remoteprocessviewer.open");
this.repository = repository;
this.processResponse = (ProcessResponse) tp.getPath()[2];
}
@Override
public void actionPerformed(ActionEvent e) {
TreePath selectionPath = tree.getSelectionPath();
if (selectionPath != null) {
Object selection = selectionPath.getLastPathComponent();
if (selection instanceof ProcessResponse) {
String locStr = RepositoryLocation.REPOSITORY_PREFIX + repository.getName() +
((ProcessResponse) selection).getProcessLocation();
RepositoryLocation loc;
try {
loc = new RepositoryLocation(locStr);
} catch (MalformedRepositoryLocationException e1) {
SwingTools.showSimpleErrorMessage("while_loading", e1, locStr, e1.getMessage());
return;
}
OpenAction.open(new RepositoryProcessLocation(loc), true);
} else if (selection instanceof OutputLocation) {
try {
RepositoryLocation procLoc = new RepositoryLocation(RepositoryLocation.REPOSITORY_PREFIX + repository.getName() +
processResponse.getProcessLocation());
RepositoryLocation ioLoc = new RepositoryLocation(procLoc.parent(), ((OutputLocation) selection).getLocation());
IOObjectEntry locatedEntry = (IOObjectEntry) ioLoc.locateEntry();
if (locatedEntry == null) { // may happen if entry has been deleted in the meantime
SwingTools.showVerySimpleErrorMessage("cannot_find_repository_location", ioLoc.toString());
return;
}
OpenAction.showAsResult(locatedEntry);
} catch (Exception e1) {
SwingTools.showSimpleErrorMessage("cannot_fetch_data_from_repository", e1);
}
}
}
}
}
private final class StopAction extends ResourceAction {
private static final long serialVersionUID = 1L;
private TreePath treePath;
private StopAction(TreePath tp) {
super("remoteprocessviewer.stop");
this.treePath = tp;
}
@Override
public void actionPerformed(ActionEvent e) {
if (treePath != null) {
Object selection = treePath.getLastPathComponent();
if (selection instanceof ProcessResponse) {
ProcessResponse processResponse = (ProcessResponse) selection;
if (!RemoteProcessState.valueOf(processResponse.getState()).isTerminated()) {
if (treePath.getLastPathComponent() instanceof ProcessResponse) {
RemoteRepository repository = (RemoteRepository) treePath.getPath()[1];
try {
Response stopResponse = repository.getProcessService().stopProcess(processResponse.getId());
if (stopResponse.getStatus() != RepositoryConstants.OK) {
SwingTools.showVerySimpleErrorMessage("remoteprocessviewer.stop_failed", stopResponse.getErrorMessage());
}
} catch (RepositoryException e1) {
SwingTools.showSimpleErrorMessage("remoteprocessviewer.stop_failed", e1);
}
}
}
}
}
}
}
private final class ShowLogAction extends ResourceAction {
private RemoteRepository repository;
private TreePath treePath;
private static final long serialVersionUID = 1L;
private ShowLogAction(RemoteRepository repository, TreePath tp) {
super("remoteprocessviewer.show_log");
this.treePath = tp;
this.repository = repository;
}
@Override
public void actionPerformed(ActionEvent e) {
if (treePath != null) {
Object selection = treePath.getLastPathComponent();
if (selection instanceof ProcessResponse) {
ProcessResponse processResponse = (ProcessResponse) selection;
repository.showLog(processResponse.getId());
}
}
}
}
private final class BrowseProcessAction extends ResourceAction {
private RemoteRepository repository;
private TreePath treePath;
private static final long serialVersionUID = 1L;
private BrowseProcessAction(RemoteRepository repositoy, TreePath tp) {
super("remoteprocessviewer.browse");
this.treePath = tp;
this.repository = repositoy;
}
@Override
public void actionPerformed(ActionEvent e) {
if (treePath != null) {
Object selection = treePath.getLastPathComponent();
if (selection instanceof ProcessResponse) {
repository.browse(((ProcessResponse) selection).getProcessLocation());
} else if (selection instanceof OutputLocation) {
try {
ProcessResponse proResponse = (ProcessResponse) treePath.getPath()[2];
RepositoryLocation procLoc = new RepositoryLocation(RepositoryLocation.REPOSITORY_PREFIX + repository.getName() +
proResponse.getProcessLocation());
RepositoryLocation ioLoc = new RepositoryLocation(procLoc.parent(), ((OutputLocation) selection).getLocation());
repository.browse(ioLoc.getPath());
} catch (Exception e1) {
SwingTools.showSimpleErrorMessage("cannot_fetch_data_from_repository", e1);
}
}
}
}
}
private static final long serialVersionUID = 1L;
private JTree tree;
private RemoteProcessesTreeModel treeModel;
private Date sessionStartDate = new Date();
private JComboBox sinceWhenCombo = new JComboBox(new Object[] {
I18N.getMessage(I18N.getGUIBundle(), "gui.combo.remoteprocessviewer.since_session_start"),
I18N.getMessage(I18N.getGUIBundle(), "gui.combo.remoteprocessviewer.for_today"),
I18N.getMessage(I18N.getGUIBundle(), "gui.combo.remoteprocessviewer.all")
});
public RemoteProcessViewer() {
setLayout(new BorderLayout());
treeModel = new RemoteProcessesTreeModel();
treeModel.setSince(sessionStartDate);
tree = new JTree(treeModel);
tree.setCellRenderer(new RemoteProcessTreeCellRenderer());
tree.setShowsRootHandles(true);
tree.setRootVisible(false);
JScrollPane scrollPane = new ExtendedJScrollPane(tree);
scrollPane.setBorder(null);
add(scrollPane, BorderLayout.CENTER);
JToolBar toolBar = new ViewToolBar();
add(toolBar, BorderLayout.NORTH);
toolBar.add(new RunRemoteAction());
toolBar.addSeparator();
ResourceLabel label = new ResourceLabel("remoteprocessviewer.filter");
label.setLabelFor(sinceWhenCombo);
toolBar.add(label);
toolBar.add(sinceWhenCombo);
ToolTipWindow window = new ToolTipWindow(new TipProvider() {
@Override
public Component getCustomComponent(Object id) {
if (id instanceof TreePath) {
RepositoryLocation loc = getSelectedRepositoryLocation((TreePath) id);
if (loc != null) {
try {
// TODO: Need to run locateEntry() in background. How?
return ToolTipProviderHelper.getCustomComponent(loc.locateEntry());
} catch (RepositoryException e) {
//LogService.getRoot().log(Level.WARNING, "Error locating entry for "+loc+": "+e, e);
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.repository.gui.process.RemoteProcessViewer.locating_entry_error",
loc, e),
e);
return null;
}
} else {
return null;
}
} else {
return null;
}
}
@Override
public Object getIdUnder(Point point) {
TreePath path = tree.getPathForLocation((int) point.getX(), (int) point.getY());
if (path != null) {
return path;
} else {
return null;
}
}
@Override
public String getTip(Object o) {
if (o instanceof TreePath) {
Object last = ((TreePath) o).getLastPathComponent();
if (last instanceof ProcessResponse) {
ProcessResponse pr = (ProcessResponse) last;
StringBuilder b = new StringBuilder();
b.append("<html><body>");
b.append("<strong>").append(pr.getProcessLocation()).append("</strong> ");
if (RemoteProcessState.valueOf(pr.getState()) == RemoteProcessState.FAILED) {
b.append("<span style=\"color:red\">(").append(pr.getState().toLowerCase()).append(")</span><br/>");
} else {
b.append("(").append(pr.getState().toLowerCase()).append(")<br/>");
}
if (pr.getStartTime() != null) {
b.append("<em>Started: </em>").append(DateFormat.getDateTimeInstance().format(pr.getStartTime().toGregorianCalendar().getTime())).append("<br/>");
}
if (pr.getCompletionTime() != null) {
b.append("<em>Completed: </em>").append(DateFormat.getDateTimeInstance().format(pr.getCompletionTime().toGregorianCalendar().getTime())).append("<br/>");
}
if (pr.getException() != null) {
b.append("<span style=\"color:red\">").append(pr.getException()).append("</span><br/>");
}
RemoteRepository repos = (RemoteRepository) ((TreePath) o).getPath()[1];
b.append("<a href=\"" + repos.getProcessLogURI(pr.getId()).toString() + "\">View Log</a>");
b.append("</body></html>");
return b.toString();
} else if (last instanceof RemoteRepository) {
return ((RemoteRepository) last).getDescription();
} else if (last == RemoteProcessesTreeModel.EMPTY_PROCESS_LIST) {
return I18N.getMessage(I18N.getGUIBundle(), "gui.label.remoteprocessviewer.empty");
} else if (last == RemoteProcessesTreeModel.PENDING_PROCESS_LIST) {
return I18N.getMessage(I18N.getGUIBundle(), "gui.label.remoteprocessviewer.pending");
} else {
RepositoryLocation loc = getSelectedRepositoryLocation((TreePath)o);
if (loc != null) {
try {
return ToolTipProviderHelper.getTip(loc.locateEntry());
} catch (RepositoryException e) {
//LogService.getRoot().log(Level.WARNING, "Error locating entry for "+loc+": "+e, e);
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.repository.gui.process.RemoteProcessViewer.locating_entry_error",
loc, e),
e);
return null;
}
} else {
return null;
}
}
} else {
return null;
}
}
}, tree);
sinceWhenCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
switch (sinceWhenCombo.getSelectedIndex()) {
case 0:
treeModel.setSince(sessionStartDate);
break;
case 1:
Calendar today = new GregorianCalendar();
today.set(Calendar.HOUR_OF_DAY, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MILLISECOND, 0);
treeModel.setSince(today.getTime());
break;
case 2:
treeModel.setSince(null);
}
}
});
tree.addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent event) {
Object leaf = event.getPath().getLastPathComponent();
if (leaf instanceof RemoteRepository) {
treeModel.observe((RemoteRepository) leaf);
}
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
Object leaf = event.getPath().getLastPathComponent();
if (leaf instanceof RemoteRepository) {
treeModel.ignore((RemoteRepository) leaf);
}
}
});
tree.setToggleClickCount(3);
tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
showPopupMenu(e);
}
@Override
public void mouseReleased(MouseEvent e) {
showPopupMenu(e);
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
doubleCLickEvent(e);
e.consume();
} else {
showPopupMenu(e);
}
}
private void showPopupMenu(MouseEvent e) {
TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
if (tp != null) {
final RemoteRepository repository = (RemoteRepository) tp.getPath()[1];
Object last = tp.getLastPathComponent();
if (e.isPopupTrigger()) {
JPopupMenu menu = new JPopupMenu();
if (last instanceof ProcessResponse) {
final ProcessResponse processResponse = (ProcessResponse) last;
// select the process on which the mouse is pointing:
tree.setSelectionPath(tp);
menu.add(new OpenProcessAction(repository, processResponse));
menu.add(new BrowseProcessAction(repository, tp));
if (((ProcessResponse) last).getState().equals("RUNNING")) {
menu.add(new StopAction(tp));
}
menu.add(new ShowLogAction(repository, tp));
}
if (last instanceof OutputLocation) {
menu.add(new OpenProcessAction(repository, (OutputLocation) last, tp));
}
menu.show(tree, e.getX(), e.getY());
}
}
}
private void doubleCLickEvent(MouseEvent e) {
TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
if (tp != null) {
final RemoteRepository repository = (RemoteRepository) tp.getPath()[1];
Object last = tp.getLastPathComponent();
if (last instanceof ProcessResponse) {
// prevent the tree node from collapsing again after a double-click:
//tree.expandPath(tp);
final ProcessResponse processResponse = (ProcessResponse) last;
OpenProcessAction openAction = new OpenProcessAction(repository, processResponse);
openAction.actionPerformed(new ActionEvent(e.getSource(), e.getID(), ""));
} else if (last instanceof OutputLocation) {
OpenProcessAction openAction = new OpenProcessAction(repository, (OutputLocation) last, tp);
openAction.actionPerformed(new ActionEvent(e.getSource(), e.getID(), ""));
}
}
}
});
}
private RepositoryLocation getSelectedRepositoryLocation(TreePath selectionPath) {
try {
if (selectionPath != null) {
Object selection = selectionPath.getLastPathComponent();
if (selection instanceof ProcessResponse) {
Repository repository = (Repository) selectionPath.getPath()[1];
return new RepositoryLocation(RepositoryLocation.REPOSITORY_PREFIX + repository.getName() +
((ProcessResponse) selection).getProcessLocation());
} else if (selection instanceof OutputLocation) {
Repository repository = (Repository) selectionPath.getPath()[1];
ProcessResponse proResponse = (ProcessResponse) selectionPath.getPath()[2];
RepositoryLocation procLoc = new RepositoryLocation(RepositoryLocation.REPOSITORY_PREFIX + repository.getName() +
proResponse.getProcessLocation());
return new RepositoryLocation(procLoc.parent(), ((OutputLocation) selection).getLocation());
}
}
} catch (MalformedRepositoryLocationException e) {
return null;
}
return null;
}
public static final String PROCESS_PANEL_DOCK_KEY = "remote_process_viewer";
private final DockKey DOCK_KEY = new ResourceDockKey(PROCESS_PANEL_DOCK_KEY);
{
DOCK_KEY.setDockGroup(MainFrame.DOCK_GROUP_ROOT);
}
@Override
public Component getComponent() {
return this;
}
@Override
public DockKey getDockKey() {
return DOCK_KEY;
}
}