/*
* Copyright (C) 2011 Ives van der Flaas
*
* 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 be.ac.ua.comp.scarletnebula.core;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import be.ac.ua.comp.scarletnebula.core.SSHCommandConnection.ChannelInputStreamTuple;
import be.ac.ua.comp.scarletnebula.gui.NewDatapointListener;
import be.ac.ua.comp.scarletnebula.gui.NotPromptingJschUserInfo;
import com.jcraft.jsch.Channel;
public class ServerStatisticsManager {
public interface NoStatisticsListener {
public void connectionFailed(ServerStatisticsManager manager);
}
private final class PollingRunnable implements Runnable {
private boolean stop = false;
@Override
public void run() {
try {
final SSHCommandConnection connection = (SSHCommandConnection) server
.newCommandConnection(new NotPromptingJschUserInfo());
final ChannelInputStreamTuple tup = connection
.executeContinuousCommand(server.getStatisticsCommand());
final InputStream pollingInputStream = tup.inputStream;
final Channel sshChannel = tup.channel;
StringBuilder result = new StringBuilder();
final int buffersize = 1024;
final byte[] tmp = new byte[buffersize];
while (!stop) {
while (pollingInputStream.available() > 0) {
final int i = pollingInputStream.read(tmp, 0,
buffersize);
if (i < 0) {
break;
}
result.append(new String(tmp, 0, i));
// Split on newlines
while (result.indexOf("\n") >= 0) {
final int nlPos = result.indexOf("\n");
final String before = result.substring(0, nlPos);
newDatapoint(before);
final String after = result.substring(nlPos + 1);
result = new StringBuilder(after);
}
}
if (sshChannel.isClosed()) {
log.warn("SSH Channel was closed.");
break;
}
try {
Thread.sleep(1000);
} catch (final Exception ee) {
}
}
sshChannel.disconnect();
} catch (final Exception e) {
for (final NoStatisticsListener listener : noStatisticsListeners) {
log.info("Notifying listener of server statistics failure.");
listener.connectionFailed(ServerStatisticsManager.this);
}
if (e.getCause() == null) {
log.error("Problem executing continuous command."
+ e.getLocalizedMessage());
} else {
log.error("Problem executing continuous command."
+ e.getCause().getLocalizedMessage());
}
}
}
public void pleaseStop() {
stop = true;
}
}
final private static Log log = LogFactory
.getLog(ServerStatisticsManager.class);
private final Collection<NewDatastreamListener> newDatastreamListeners = new ArrayList<NewDatastreamListener>();
private final Collection<DeleteDatastreamListener> deleteDatastreamListeners = new ArrayList<DeleteDatastreamListener>();
private final Server server;
private final Map<String, Datastream> availableStreams = new HashMap<String, Datastream>();
private final Map<String, Collection<NewDatapointListener>> futureHookups = new HashMap<String, Collection<NewDatapointListener>>();
private final Collection<NoStatisticsListener> noStatisticsListeners = new LinkedList<NoStatisticsListener>();
private PollingRunnable pollingRunnable;
ServerStatisticsManager(final Server server) {
this.server = server;
startPolling();
}
private void startPolling() {
pollingRunnable = new PollingRunnable();
final Thread readerThread = new Thread(pollingRunnable);
readerThread.start();
log.info("Starting polling thread");
}
private void newDatapoint(final String stringRepresentation) {
final Datapoint datapoint = Datapoint.fromJson(stringRepresentation);
final String datastreamName = datapoint.getDatastream();
if (!availableStreams.containsKey(datastreamName)) {
log.info("Registering new stream " + datastreamName);
updateNewDatastreamObservers(datapoint);
final Datastream newDatastream = new Datastream(datapoint);
availableStreams.put(datastreamName, newDatastream);
// If the streamname appears in the futureHookups, add the listeners
// for that streamname
if (futureHookups.containsKey(datastreamName)) {
for (final NewDatapointListener listener : futureHookups
.get(datastreamName)) {
log.info("Adding future hookup listener to newly created stream.");
newDatastream.addNewDatapointListener(listener);
}
futureHookups.remove(datastreamName);
}
}
availableStreams.get(datastreamName).newDatapoint(datapoint);
}
public void addNewDatastreamListener(final NewDatastreamListener listener) {
newDatastreamListeners.add(listener);
}
public void addDeleteDatastreamListener(
final DeleteDatastreamListener listener) {
deleteDatastreamListeners.add(listener);
}
public void addNoStatisticsListener(final NoStatisticsListener listener) {
noStatisticsListeners.add(listener);
}
private void updateNewDatastreamObservers(final Datapoint datapoint) {
for (final NewDatastreamListener listener : newDatastreamListeners) {
listener.newDataStream(datapoint);
}
}
/**
* Stops the polling thead and closes the SSH connection.
*/
public void stop() {
log.info("Stopping polling thread");
pollingRunnable.pleaseStop();
}
public Collection<String> getAvailableDatastreams() {
return availableStreams.keySet();
}
/**
* Interface to implement if you'd like to be notified of new data streams
* that present themselves relating to this server.
*
* @author ives
*
*/
public interface NewDatastreamListener {
/**
* Called when a new datastream is registered, together with the first
* point in that datastream. Do *not* use the value in this point as an
* actual datapoint, instead use a NewDataPointListener for this. The
* Datapoint should only be used for its fields such as lowWarnLevel,
* max, etc. This method is guaranteed to be called before the
* NewDatapointObservers.
*
* @param datapoint
*/
public void newDataStream(Datapoint datapoint);
}
public interface DeleteDatastreamListener {
/**
* Called when a datastream is removed (when the corresponding graphs
* should no longer be displayed)
*
* @author ives
*
*/
public void deleteDataStream(String streamname);
}
/**
* Adds a new listener to the Datastream datastream. If no such stream is
* found, this request is stored and applied as soon as this stream is
* created. So when the first datapoint for this stream arrives, the
* listener will automatically be added.
*
* @param listener
* @param datastream
*/
public void addNewDatapointListener(final NewDatapointListener listener,
final String datastream) {
if (availableStreams.containsKey(datastream)) {
availableStreams.get(datastream).addNewDatapointListener(listener);
} else {
log.info("Adding stream " + datastream
+ " to be hooked when it arrives.");
if (!futureHookups.containsKey(datastream)) {
futureHookups.put(datastream,
new ArrayList<NewDatapointListener>());
}
futureHookups.get(datastream).add(listener);
}
}
public Datastream getDatastream(final String streamname) {
return availableStreams.get(streamname);
}
public List<TimedDatapoint> getHistoricalDatapoints(final String streamname) {
List<TimedDatapoint> datapoints;
if (availableStreams.containsKey(streamname)) {
datapoints = availableStreams.get(streamname)
.getRecentlyProcessedDatapoints();
} else {
datapoints = new ArrayList<TimedDatapoint>();
}
return datapoints;
}
public void reset() {
for (final String streamname : availableStreams.keySet()) {
for (final DeleteDatastreamListener listener : deleteDatastreamListeners) {
listener.deleteDataStream(streamname);
}
}
stop();
startPolling();
}
public Datastream.WarnLevel getHighestWarnLevel() {
Datastream.WarnLevel highestWarnLevel = Datastream.WarnLevel.NONE;
for (final Datastream datastream : availableStreams.values()) {
if (datastream.getCurrentWarnLevel().compareTo(highestWarnLevel) > 0) {
highestWarnLevel = datastream.getCurrentWarnLevel();
}
}
return highestWarnLevel;
}
}