/**
* This software is GPLv2.
* Take a look at the LICENSE file for more info.
*/
package de.tu.dresden.dud.dc.WorkCycle;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.TreeSet;
import org.apache.log4j.Logger;
import de.tu.dresden.dud.dc.Connection;
import de.tu.dresden.dud.dc.Participant;
import de.tu.dresden.dud.dc.ParticipantManager;
import de.tu.dresden.dud.dc.ParticipantMgmntInfo;
import de.tu.dresden.dud.dc.Server;
import de.tu.dresden.dud.dc.InfoService.InfoServiceInfo;
import de.tu.dresden.dud.dc.KeyGenerators.KeyGenerator;
import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessage;
import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageAdd;
import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageAdded;
import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageInfo;
import de.tu.dresden.dud.dc.Util;
/**
* @author klobs
*
*/
public class WorkCycleManager implements Observer{
// Logging
private static Logger log = Logger.getLogger(WorkCycleManager.class);
public static final short MESSAGE_LENGTHS_FIXED = 0;
public static final short MESSAGE_LENGTHS_VARIABLE = 1;
public static final short EARLY_QUIT_MUST_NOT_HAPPEN= 0;
public static final short EARLY_QUIT_CONTINUE_WC = 1;
public static final short EARLY_QUIT_RESTART_WC = 2;
public static final short MIN_DC_PARTICIPANTS = 2;
private KeyGenerator assocKeyGenerator = null;
private ParticipantManager assocParticipantManager = null;
private long currentWorkCycle = -1;
private short earlyQuitReaction = EARLY_QUIT_MUST_NOT_HAPPEN;
private LinkedList<Connection> earlyQuitConnections = new LinkedList<Connection>();
private int infoOffset = 0;
private int joinOffset = 0;
private int leaveOffset = 5;
private short messageLengthMode = MESSAGE_LENGTHS_FIXED;
private int packettimeout = 0;
private Participant participant = null;
private boolean participantmode = false;
private int payloadlengths = 0;
private LinkedList<byte[]> payloads = new LinkedList<byte[]>();
private TreeSet<WorkCycle> workcycless = new TreeSet<WorkCycle>(new WorkCycleComparator());
private TreeSet<WorkCycle> oldworkcycles = new TreeSet<WorkCycle>(new WorkCycleComparator());
private long tickPause = 1000;
private Server server = null;
private boolean servermode = false;
public WorkCycleManager(short keyGenerationMethod,long workCycleNumber, int payloadLengths, short messageLengthMode, short earlyQuitReaction){
payloadlengths = payloadLengths;
assocKeyGenerator = KeyGenerator.keyGeneratorFactory(keyGenerationMethod,this);
this.messageLengthMode = messageLengthMode;
this.earlyQuitReaction = earlyQuitReaction;
currentWorkCycle = workCycleNumber;
workcycless.add(getWCByWCNumber(workCycleNumber));
getCurrentWorkCycle().addObserver(this);
}
public synchronized void addExpectedConnection(Connection c){
long rn = c.getExpectedEntryWorkCycle();
if (servermode)
getWCByWCNumber(rn - infoOffset).addJoinerNotification(c);
getWCByWCNumber(rn).addExpectedConnection(c);
}
public synchronized void addLeavingConnection(Connection c){
long rn = c.getExpectedLeavingWorkCycle();
if (servermode)
getWCByWCNumber(rn - infoOffset).addLeaverNotification(c);
getWCByWCNumber(rn).addLeavingConnection(c);
}
/**
* Tell the {@link WorkCycleManager} to schedule a message for delivery in future work cycles.
* Messages get delivered as soon as it is possible.
* It checks, whether the service uses the fixed size message protocol and then
* paddes the messages with 0s, or rejects messages, if they are too long.
* @param p
*/
public synchronized int addMessage(byte[] p){
if (p.length <= payloadlengths){
if(messageLengthMode == MESSAGE_LENGTHS_FIXED){
p = Util.fillAndMergeSending(p, new byte[payloadlengths]);
}
payloads.add(p);
return 0;
}
else {
log.error("Message too long to enter the send queue. Rejected");
return 1;
}
}
public synchronized void addMessageArrived(Connection c, ManagementMessageAdd m){
getWCByWCNumber(m.getWorkCycleNumber()).addMessageArrived(c,m);
}
public synchronized void addedMessageArrived(ManagementMessageAdded m){
// if (becomeActiveAfter <= m.getWCNumber()) // do we actually need this check? I dont think so.
getWCByWCNumber(m.getWorkCycleNumber()).addedMessageArrived(m);
}
public void broadcastToActiveParticipants(InfoServiceInfo i){
LinkedList<ParticipantMgmntInfo> apl = assocParticipantManager.getActivePartMgmtInfo();
ManagementMessageInfo m = new ManagementMessageInfo(i);
for (ParticipantMgmntInfo pmi : apl){
try {
pmi.getAssocConnection().sendMessage(m.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void broadcastToActiveParticipants(ManagementMessage m){
LinkedList<ParticipantMgmntInfo> apl = assocParticipantManager.getActivePartMgmtInfo();
for (ParticipantMgmntInfo i : apl){
try {
i.getAssocConnection().sendMessage(m.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void broadcastToPassiveParticipants(InfoServiceInfo i){
LinkedList<ParticipantMgmntInfo> apl = assocParticipantManager.getPassivePartMgmtInfo();
ManagementMessageInfo m = new ManagementMessageInfo(i);
for (ParticipantMgmntInfo pmi : apl){
try {
pmi.getAssocConnection().sendMessage(m.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void broadcastToPassiveParticipants(ManagementMessage m){
LinkedList<ParticipantMgmntInfo> ppl = assocParticipantManager.getPassivePartMgmtInfo();
for (ParticipantMgmntInfo i : ppl){
try {
i.getAssocConnection().sendMessage(m.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public ParticipantManager getAssocParticipantManager(){
return assocParticipantManager;
}
public synchronized WorkCycle getCurrentWorkCycle(){
return getWCByWCNumber(currentWorkCycle);
}
public synchronized long getCurrentWorkCycleNumber(){
return currentWorkCycle;
}
public short getEarlyQuitReaction(){
return earlyQuitReaction;
}
/**
* This returns the work cycle number when a new participant
* expected to start.
* @return
*/
public synchronized long getEntryWorkCycleNumber(){
return getCurrentWorkCycle().getWorkCycleNumber() + joinOffset;
}
public short getMessageLengthMode(){
return messageLengthMode;
}
public long getInfoOffset(){
return infoOffset;
}
public long getLeaveOffset(){
return leaveOffset;
}
public synchronized LinkedList<byte []> getPayloadList(){
return payloads;
}
public KeyGenerator getKeyGenerator(){
return assocKeyGenerator;
}
public short getKeyGenerationMethod(){
if (assocKeyGenerator != null)
return assocKeyGenerator.getKeyGenerationMethod();
return -1;
}
/**
* This method returns the next work cycle.
* But there is still work to be done, which means it is not ready to use, yet.
*
* E.g. the expected participants have to be set up, etc.
* this will be done by the next() method.
*
* @return A skeleton for the next work cycle.
*/
public synchronized WorkCycle getNextWorkCycle(){
return getWCByWCNumber(getCurrentWorkCycle().getWorkCycleNumber()+1);
}
public synchronized Participant getParticipant(){
return this.participant;
}
public synchronized boolean getParticipantMode(){
return participantmode;
}
public synchronized int getRealtimeMessageTimeout(){
return this.packettimeout;
}
/**
* Returns a work cycle with a desired work cycle number.
* If the work cycle does not exsist yet in the database,
* it will be created.
*
* After creation, the work cycle will be saved in the database,
* and be returned on the next request.
*
* @param d
* @return The desired work cycle
*/
public synchronized WorkCycle getWCByWCNumber(long d){
Iterator<WorkCycle> i = workcycless.iterator();
WorkCycle r = null;
long rn = 0; // Long.MIN_VALUE;
while(i.hasNext()){
r = i.next();
rn = r.getWorkCycleNumber();
if (rn < d){
if (! i.hasNext()) r = null;
continue;
}
else if (rn == d)
break;
else {
r = null;
break;
}
}
if(r == null){
r = new WorkCycle(d, packettimeout, payloadlengths, this);
workcycless.add(r);
}
return r;
}
public synchronized Server getServer(){
return server;
}
/**
* How long is one symbol payload?
* @return character length in bytes
*/
public int getSymbolLength() {
return payloadlengths;
}
public long getTickPause(){
return tickPause;
}
public void handleEarlyQuitOnServerside(Connection c){
if (getEarlyQuitReaction() == WorkCycleManager.EARLY_QUIT_MUST_NOT_HAPPEN)
log.error("Participant left, although this behaviour is not allowed! Let's all panic!!11");
else if (getEarlyQuitReaction() == WorkCycleManager.EARLY_QUIT_RESTART_WC) {
earlyQuitConnections.add(c);
if (servermode)
tickServerSide();
}
else if (getEarlyQuitReaction() == WorkCycleManager.EARLY_QUIT_CONTINUE_WC) {
log.error("Continuing WorkCycle after an early quit of a participant is not implemented, yet");
}
}
public boolean isRunning(){
return getCurrentWorkCycle().workCycleHasStarted();
}
public boolean isServerMode(){
return servermode;
}
public void setAssocParticipantManager(ParticipantManager p){
assocParticipantManager = p;
}
/**
* Advance to the next work cycle.
*
* Migrate all important data from the current to the next work cycle.
* Delete the old work cycle and make the next work cycle the new current one
*/
private synchronized void setupNextWorkCycle(){
Set<Connection> o = null;
WorkCycle c = getCurrentWorkCycle();
WorkCycle n = getNextWorkCycle();
synchronized (c) {
c.deleteObserver(this);
n.addObserver(this);
if (c.hasWorkCycleBeenSuccessful() && !servermode) {
if (getPayloadList().size() > 0)
getPayloadList().removeFirst();
}
// the next work cycle should contain all the connection that
// were also in the current work cycle.
// those connections that were marked for leaving will be
// subtracted internally
o = c.getConnections();
if (earlyQuitConnections.size() > 0)
o.removeAll(earlyQuitConnections);
n.addExpectedConnections(o);
o = c.getBroadcastConnections();
if (earlyQuitConnections.size() > 0)
o.removeAll(earlyQuitConnections);
n.setBroadcastConnections(o);
earlyQuitConnections.clear();
oldworkcycles.add(c); // save the old work cycle
workcycless.remove(c);
currentWorkCycle = n.getWorkCycleNumber();
}
}
public void setInfoOffset(int i){
infoOffset = i;
}
public void setJoinOffset(int j){
joinOffset = j;
}
public synchronized void setPacketTimeout(int t){
packettimeout = t;
}
public synchronized void setParticipant(Participant p){
participantmode = true;
participant = p;
}
public synchronized void setServer(Server s){
servermode = true;
server = s;
}
public void setTickPause(long p){
tickPause = p;
}
/**
* A this message is called when a tick arrived at the participant side.
* @param t
*/
public synchronized void tickArrived(long t){
if (getCurrentWorkCycle().workCycleHasStarted()){
// the current work cycle has already started, so it seems that we are
// in a hot system.
// for the case, that nobody inteded to send anything.
getCurrentWorkCycle().getSemaphore().release();
setupNextWorkCycle();
}
currentWorkCycle = t;
if (assocParticipantManager.getMyInfo().isActive()) {
getWCByWCNumber(t).performSendingOnParticipantSide();
}
}
/**
* Advance to next work cycle.
* This method detects whether the work cycles are in timeout / notimeout mode
* and advances correspondingly.
*
* In notimeout mode, the WorkCycleManager waits until all expected participants sent their part.
*
* This method also sends notification about the new work cycle start to all active participants.
*/
public synchronized void tickServerSide() {
if (getCurrentWorkCycle().workCycleHasStarted()){
// the current work cycle has already started, so it seems that we
// are in a hot system.
setupNextWorkCycle();
}
if ( getCurrentWorkCycle().getExpectedConnections().size() >= MIN_DC_PARTICIPANTS) {
setJoinOffset(3);
setInfoOffset(1);
getCurrentWorkCycle().performDCWorkCycleOnServerSide();
return;
} else if (getCurrentWorkCycle().getExpectedConnections().size() < MIN_DC_PARTICIPANTS) {
setJoinOffset(0);
setInfoOffset(0);
return;
}
}
@Override
public synchronized void update(Observable o, Object arg) {
synchronized (o) {
if (o instanceof WorkCycle) {
switch (((Integer) arg).intValue()) {
case WorkCycle.WC_COUNT_CHANGED:
if (servermode) {
int p = ((WorkCycle) o).getConnections().size();
if ((p == MIN_DC_PARTICIPANTS) && !((WorkCycle) o).workCycleHasStarted()) {
tickServerSide();
}
}
break;
case WorkCycle.WC_FINISHED:
if (servermode) {
tickServerSide();
}
if (!servermode) {
if (assocParticipantManager.getParticipantMgmntInfoFor(
participant).getInactiveInWorkCycle() == currentWorkCycle + 1) {
assocParticipantManager
.update(currentWorkCycle + 1);
if (assocParticipantManager
.getParticipantMgmntInfoFor(participant)
.getAssocConnection()
.checkWhetherQuitRequestedOnParticipantSide())
assocParticipantManager
.getParticipantMgmntInfoFor(participant)
.getAssocConnection()
.quitService(participant);
}
}
break;
}
}
}
}
}