/*******************************************************************************
* Copyright (c) 2006-2009, G. Weirich and Elexis
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* G. Weirich - initial implementation
*
*******************************************************************************/
package ch.elexis.core.data.events;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.elexis.core.constants.Preferences;
import ch.elexis.core.data.activator.CoreHub;
/**
* Heartbeat is an event source, that fires events at user-definable intervals to all
* HeartListeners. All actions that must be repeated regularly should be registered as
* HeartListener. They will all be called at about the specified rate, but not in a guaranteed
* particular order and not necessarily at exactly identical intervals.
*
* Heartbeat löst das Pinger-Konzept ab. Der Heartbeat ist ein Singleton, das alle
* CoreHub.localCfg.get(heartbeatrate,30) Sekunden einen Event feuert. Wer reglmässige Aktionen
* durchführen will, kann sich als HeartbeatListener registrieren. Dieses Konzept hat gegenüber
* individuellen update-Threads den Vorteil, dass die Netzwerk- und Datenbankbelastung, sowie die
* Zahl der gleichzeitig laufenden Threads limitiert wird. Der Heartbeat sorgt dafür, dass die
* listener der Reihe nach (aber ncht in einer definierten Reihenfolge) aufgerufen werden.
*
* The client registering a listener can define the frequency, whether the listener should be called
* at every single heart beat or at a lower frequency.
*
* @author gerry
*
*/
public class Heartbeat {
/**
* Registering a listener using FREQUENCY_HIGH, it is called at every single heartbeat.
*/
public static final int FREQUENCY_HIGH = 1;
/**
* Registering a listener using FREQUENCY_MEDIUM, it is called at every 4th heartbeat.
*/
public static final int FREQUENCY_MEDIUM = 2;
/**
* Registering a listener using FREQUENCY_LOW, it is called at every 16th heartbeat.
*/
public static final int FREQUENCY_LOW = 3;
private beat theBeat;
private Timer pacer;
private boolean isSuspended;
private static Heartbeat theHeartbeat;
private CopyOnWriteArrayList<HeartListener> highFrequencyListeners;
private CopyOnWriteArrayList<HeartListener> mediumFrequencyListeners;
private CopyOnWriteArrayList<HeartListener> lowFrequencyListeners;
private static Logger log = LoggerFactory.getLogger(Heartbeat.class);
private Heartbeat(){
theBeat = new beat();
highFrequencyListeners = new CopyOnWriteArrayList<HeartListener>();
mediumFrequencyListeners = new CopyOnWriteArrayList<HeartListener>();
lowFrequencyListeners = new CopyOnWriteArrayList<HeartListener>();
pacer = new Timer(true);
int interval = CoreHub.localCfg.get(Preferences.ABL_HEARTRATE, 30); //$NON-NLS-1$
isSuspended = true;
pacer.schedule(theBeat, 0, interval * 1000L);
}
/**
* Das Singleton holen
*
* @return den Heartbeat der Anwendung
*/
public static Heartbeat getInstance(){
if (theHeartbeat == null) {
theHeartbeat = new Heartbeat();
}
return theHeartbeat;
}
/**
* Heartbeat (wieder) laufen lassen.
*
* @param immediately
* true: Sofort einen ersten beat losschicken, false: im normalen Rhythmus bleiben.
*/
public void resume(boolean immediately){
isSuspended = false;
log.debug("resume"); //$NON-NLS-1$
if (immediately) {
theBeat.run();
}
}
/**
* Heartbeat aussetzen (geht im Hintergrund weiter, wird aber nicht mehr weitergeleitet)
*/
public void suspend(){
log.debug("suspending"); //$NON-NLS-1$
isSuspended = true;
}
/**
* Heartbeat stoppen (kann dann nicht mehr gestartet werden)
*/
public void stop(){
log.debug("stopping"); //$NON-NLS-1$
pacer.cancel();
}
/**
* Einen Listener registrieren. Achtung: Muss unbedingt mit removeListener deregistriert werden
* Calls addListener(listen, FREQUENCY_HIGH)
*
* @param listen
* der Listener
*/
public void addListener(HeartListener listen){
addListener(listen, FREQUENCY_HIGH);
}
/**
* Add listener using the specified frequency. Must be de-regsitered again using removeListener
*
* @param listener
* @param frequency
* the frequency to call this listener. One of FREQUENCY_HIGH, FREQUENCY_MEDIUM,
* FREQUENCY_LOW
*/
public void addListener(HeartListener listen, int frequency){
if (!highFrequencyListeners.contains(listen) && !mediumFrequencyListeners.contains(listen)
&& !lowFrequencyListeners.contains(listen)) {
switch (frequency) {
case FREQUENCY_HIGH:
highFrequencyListeners.add(listen);
break;
case FREQUENCY_MEDIUM:
mediumFrequencyListeners.add(listen);
break;
case FREQUENCY_LOW:
lowFrequencyListeners.add(listen);
break;
}
}
}
/**
* Einen Listener wieder austragen
*
* @param listen
*/
public void removeListener(HeartListener listen){
// remove the listener from the three lists
// actually, it's contained in only one of them, but we don't know which
// one
highFrequencyListeners.remove(listen);
mediumFrequencyListeners.remove(listen);
lowFrequencyListeners.remove(listen);
}
/**
* we beat asynchronously, because most listeners will update their views
*
* @author Gerry
*
*/
private class beat extends TimerTask {
private static final int FREQUENCY_HIGH_MULTIPLIER = 1;
private static final int FREQUENCY_MEDIUM_MULTIPLIER = 4;
private static final int FREQUENCY_LOW_MULTIPLIER = 16;
// multiplier for resetting counter after each round
private static final int RESET_MULTIPLIER = FREQUENCY_LOW_MULTIPLIER;
private int counter = 0;
@Override
public void run(){
if (!isSuspended) {
// low frequency
if (counter % FREQUENCY_LOW_MULTIPLIER == 0) {
log.debug("Heartbeat low"); //$NON-NLS-1$
for (HeartListener l : lowFrequencyListeners) {
l.heartbeat();
}
}
// medium frequency
if (counter % FREQUENCY_MEDIUM_MULTIPLIER == 0) {
log.debug("Heartbeat medium"); //$NON-NLS-1$
for (HeartListener l : mediumFrequencyListeners) {
l.heartbeat();
}
}
// high frequency
if (counter % FREQUENCY_HIGH_MULTIPLIER == 0) {
log.debug("Heartbeat high"); //$NON-NLS-1$
for (HeartListener l : highFrequencyListeners) {
l.heartbeat();
}
}
}
counter++;
counter %= RESET_MULTIPLIER;
}
}
public interface HeartListener {
/**
* Die Methode heartbeat wird in "einigermassen" regelmässigen (aber nicht garantiert immer
* genau identischen) Abständen aufgerufen
*
*/
public void heartbeat();
}
}