/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import javax.swing.SwingUtilities; import javax.swing.event.EventListenerList; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import javax.xml.datatype.XMLGregorianCalendar; import com.rapid_i.repository.wsimport.ProcessResponse; import com.rapid_i.repository.wsimport.ProcessStackTrace; import com.rapid_i.repository.wsimport.ProcessStackTraceElement; import com.rapidminer.io.process.XMLTools; import com.rapidminer.repository.RemoteProcessState; import com.rapidminer.repository.remote.RemoteRepository; import com.rapidminer.tools.LogService; /** * The TreeModel for the log of remotely executed processes. * * @author Simon Fischer */ public class RemoteProcessesTreeModel implements TreeModel { private static final long serialVersionUID = 1L; private static final long UPDATE_PERIOD = 2500; private class ProcessList { private List<Integer> knownIds = new LinkedList<Integer>(); private Map<Integer,ProcessResponse> processResponses = new HashMap<Integer,ProcessResponse>(); public int add(ProcessResponse pr) { int newIndex = -1; if (!processResponses.containsKey(pr.getId())) { newIndex = knownIds.size(); knownIds.add(pr.getId()); } processResponses.put(pr.getId(), pr); return newIndex; } public ProcessResponse getByIndex(int index) { return processResponses.get(knownIds.get(index)); } public ProcessResponse getById(int id) { return processResponses.get(id); } public int size() { return knownIds.size(); } public int indexOf(ProcessResponse child) { int index = 0; for (Integer id : knownIds) { ProcessResponse pr = processResponses.get(id); if ((pr != null) && (pr.getId() == child.getId())) { return index; } index++; } return -1; } private TreeModelEvent trim(Set<Integer> processIds, RemoteRepository repos) { List<Integer> removedIndices = new LinkedList<Integer>(); List<Object> removedObjects = new LinkedList<Object>(); Iterator<Integer> i = knownIds.iterator(); int index = 0; while (i.hasNext()) { Integer id = i.next(); if (!processIds.contains(id)) { i.remove(); ProcessResponse process = processResponses.remove(id); removedIndices.add(index); removedObjects.add(process); } index++; } if (!removedIndices.isEmpty()) { int[] indices = new int[removedIndices.size()]; for (int j = 0; j < removedIndices.size(); j++) { indices[j] = removedIndices.get(j); } return new TreeModelEvent(this, new Object[] { root, repos }, indices, removedObjects.toArray()); } else { return null; } } } private final class UpdateTask extends TimerTask { @Override public void run() { final List<RemoteRepository> newRepositories = RemoteRepository.getAll(); if (!newRepositories.equals(repositories)) { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { repositories = newRepositories; processes.clear(); for (RemoteRepository repos : repositories) { processes.put(repos, new ProcessList()); } fireStructureChanged(new TreeModelEvent(this, new Object[] { root })); }; }); } catch (InterruptedException e) { LogService.getRoot().log(Level.WARNING, e.toString(), e); } catch (InvocationTargetException e) { LogService.getRoot().log(Level.WARNING, e.toString(), e); } } for (final RemoteRepository repos : repositories) { if (observedRepositories.contains(repos)) { final ProcessList processList = processes.get(repos); try { final Collection<Integer> processIds = repos.getProcessService().getRunningProcesses(since); // First, delete removed ids SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { TreeModelEvent deleteEvent = processList.trim(new HashSet<Integer>(processIds), repos); //processes.put(repos, processList); if (deleteEvent != null) { fireDelete(deleteEvent); } } }); for (Integer processId : processIds) { ProcessResponse oldProcess = processList.getById(processId); // we update if we don't know the id yet or if the process is not complete if (oldProcess == null) { final ProcessResponse newResponse = repos.getProcessService().getRunningProcessesInfo(processId); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { int newIndex = processList.add(newResponse); fireAdd(new TreeModelEvent(this, new Object[] {root, repos}, new int[] {newIndex}, new Object[] {newResponse})); } }); } else if (!RemoteProcessState.valueOf(oldProcess.getState()).isTerminated()) { final ProcessResponse updatedResponse = repos.getProcessService().getRunningProcessesInfo(processId); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { processList.add(updatedResponse); fireStructureChanged(new TreeModelEvent(this, new Object[] {root, repos, updatedResponse})); } }); } else { // If process is terminated, there is not need to update. // The process is already in the list since it is copied } } } catch (Exception ex) { LogService.getRoot().log(Level.WARNING, "Error fetching remote process list: "+ex, ex); fireStructureChanged(new TreeModelEvent(this, new TreePath(new Object[] { root, repos }))); } } } } } private Map<RemoteRepository, ProcessList> processes = new HashMap<RemoteRepository, ProcessList>(); private List<RemoteRepository> repositories = new LinkedList<RemoteRepository>(); private Set<RemoteRepository> observedRepositories = new HashSet<RemoteRepository>(); private Object root = new Object(); private Timer updateTimer = new Timer("RemoteProcess-Updater", true); private XMLGregorianCalendar since; public RemoteProcessesTreeModel() { updateTimer.schedule(new UpdateTask(), UPDATE_PERIOD, UPDATE_PERIOD); } private EventListenerList listeners = new EventListenerList(); @Override public void addTreeModelListener(TreeModelListener l) { listeners.add(TreeModelListener.class, l); } @Override public void removeTreeModelListener(TreeModelListener l) { listeners.remove(TreeModelListener.class, l); } @Override public Object getChild(Object parent, int index) { if (parent == root) { return repositories.get(index); } else if (parent instanceof RemoteRepository) { return processes.get(parent).getByIndex(index); } else if (parent instanceof ProcessResponse) { ProcessResponse proResponse = (ProcessResponse)parent; if (proResponse.getException() != null) { if (index == 0) { return new ExceptionWrapper(proResponse.getException()); } else { return null; } } else { ProcessStackTrace trace = proResponse.getTrace(); int elementsSize = 0; if ((trace != null) && (trace.getElements() != null)) { elementsSize = trace.getElements().size(); } if (index < elementsSize) { return trace.getElements().get(index); } else { return new OutputLocation(proResponse.getOutputLocations().get(index - elementsSize)); } } } else { return null; } } @Override public int getChildCount(Object parent) { if (parent == root) { return repositories.size(); } else if (parent instanceof RemoteRepository) { ProcessList list = processes.get(parent); if (list == null) { return 0; } else { return list.size(); } } else if (parent instanceof ProcessResponse) { ProcessResponse proResponse = (ProcessResponse)parent; if (proResponse.getException() != null) { return 1; } else { int size = 0; ProcessStackTrace trace = proResponse.getTrace(); if ((trace != null) && (trace.getElements() != null)) { size += trace.getElements().size(); } if (proResponse.getOutputLocations() != null) { size += proResponse.getOutputLocations().size(); } return size; } } else { return 0; } } @Override public int getIndexOfChild(Object parent, Object child) { if (parent == root) { return repositories.indexOf(child); } else if (parent instanceof RemoteRepository) { return processes.get(parent).indexOf((ProcessResponse) child); } else if (parent instanceof ProcessResponse) { ProcessResponse proResponse = (ProcessResponse)parent; if (child instanceof ProcessStackTraceElement) { ProcessStackTrace trace = proResponse.getTrace(); if ((trace != null) && (trace.getElements() != null)) { return trace.getElements().indexOf(child); } else { return -1; } } else if (child instanceof OutputLocation) { if (proResponse.getOutputLocations() != null) { return proResponse.getOutputLocations().indexOf(((OutputLocation)child).getLocation()); } else { return -1; } } else if (child instanceof ExceptionWrapper) { return 0; } else { return -1; } } else { return -1; } } @Override public Object getRoot() { return root; } @Override public boolean isLeaf(Object node) { return (node != root) && !(node instanceof ProcessResponse) && !(node instanceof RemoteRepository); } @Override public void valueForPathChanged(TreePath path, Object newValue) { // not editable } private void fireAdd(TreeModelEvent e) { for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { l.treeNodesInserted(e); } } // private void fireUpdate(TreeModelEvent e) { // for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { // l.treeNodesChanged(e); // } // } private void fireDelete(TreeModelEvent event) { for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { l.treeNodesRemoved(event); } } private void fireStructureChanged(TreeModelEvent e) { for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { l.treeStructureChanged(e); } } public void setSince(Date since) { if (since == null) { this.since = null; } else { this.since = XMLTools.getXMLGregorianCalendar(since); } } public void observe(RemoteRepository rep) { observedRepositories.add(rep); } public void ignore(RemoteRepository rep) { observedRepositories.remove(rep); } }