org.jdiameter.server.impl.fsm.PeerFSMImpl Maven / Gradle / Ivy
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2016, TeleStax Inc. and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* 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
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* JBoss, Home of Professional Open Source
* Copyright 2007-2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jdiameter.server.impl.fsm;
import static org.jdiameter.client.impl.fsm.FsmState.*;
import org.jdiameter.api.Avp;
import org.jdiameter.api.AvpDataException;
import org.jdiameter.api.Configuration;
import org.jdiameter.api.ConfigurationListener;
import org.jdiameter.api.DisconnectCause;
import org.jdiameter.api.MutableConfiguration;
import org.jdiameter.api.ResultCode;
import org.jdiameter.api.app.State;
import org.jdiameter.api.app.StateEvent;
import org.jdiameter.client.api.IMessage;
import org.jdiameter.client.api.fsm.IContext;
import org.jdiameter.common.api.concurrent.IConcurrentFactory;
import org.jdiameter.common.api.statistic.IStatisticManager;
import org.jdiameter.server.api.IStateMachine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Alexandre Mendonca
* @author Bartosz Baranowski
*/
@SuppressWarnings("all") //3rd party lib
public class PeerFSMImpl extends org.jdiameter.client.impl.fsm.PeerFSMImpl implements IStateMachine, ConfigurationListener {
private static final Logger logger = LoggerFactory.getLogger(org.jdiameter.server.impl.fsm.PeerFSMImpl.class);
public PeerFSMImpl(IContext context, IConcurrentFactory concurrentFactory, Configuration config,
IStatisticManager statisticFactory) {
super(context, concurrentFactory, config, statisticFactory);
}
@Override
protected void loadTimeOuts(Configuration config) {
super.loadTimeOuts(config);
if (config instanceof MutableConfiguration) {
((MutableConfiguration) config).addChangeListener(this, 0);
}
}
@Override
public boolean elementChanged(int i, Object data) {
Configuration newConfig = (Configuration) data;
super.loadTimeOuts(newConfig);
return true;
}
@Override
protected State[] getStates() {
if (states == null) {
states = new State[] {
new MyState() { // OKEY
@Override
public void entryAction() { // todo send buffered messages
setInActiveTimer();
watchdogSent = false;
}
@Override
public boolean processEvent(StateEvent event) {
switch (type(event)) {
case DISCONNECT_EVENT:
doEndConnection();
break;
case TIMEOUT_EVENT:
try {
context.sendDwrMessage();
setTimer(DWA_TIMEOUT);
if (watchdogSent) {
switchToNextState(SUSPECT);
} else {
watchdogSent = true;
}
} catch (Throwable e) {
logger.debug("Can not send DWR", e);
doDisconnect();
doEndConnection();
}
break;
case STOP_EVENT:
try {
if (event.getData() == null) {
context.sendDprMessage(DisconnectCause.BUSY);
} else {
Integer disconnectCause = (Integer) event.getData();
context.sendDprMessage(disconnectCause);
}
setTimer(DPA_TIMEOUT);
switchToNextState(STOPPING);
} catch (Throwable e) {
logger.debug("Can not send DPR", e);
doDisconnect();
switchToNextState(DOWN);
}
break;
case RECEIVE_MSG_EVENT:
setInActiveTimer();
context.receiveMessage(message(event));
break;
case CEA_EVENT:
setInActiveTimer();
if (context.processCeaMessage(key(event), message(event))) {
doDisconnect(); // !
doEndConnection();
}
break;
case CER_EVENT:
// setInActiveTimer();
logger.debug("Rejecting CER in OKAY state. Answering with UNABLE_TO_COMPLY (5012)");
try {
context.sendCeaMessage(ResultCode.UNABLE_TO_COMPLY, message(event),
"Unable to receive CER in OPEN state.");
} catch (Exception e) {
logger.debug("Failed to send CEA.", e);
doDisconnect(); // !
doEndConnection();
}
break;
case DPR_EVENT:
try {
int code = context.processDprMessage((IMessage) event.getData());
context.sendDpaMessage(message(event), code, null);
} catch (Throwable e) {
logger.debug("Can not send DPA", e);
}
IMessage message = (IMessage) event.getData();
try {
Avp discCause = message.getAvps().getAvp(Avp.DISCONNECT_CAUSE);
boolean willReconnect = (discCause != null)
? (discCause.getInteger32() == DisconnectCause.REBOOTING)
: false;
if (willReconnect) {
doDisconnect();
doEndConnection();
} else {
doDisconnect();
switchToNextState(DOWN);
}
} catch (AvpDataException ade) {
logger.warn("Disconnect cause is bad.", ade);
doDisconnect();
switchToNextState(DOWN);
}
break;
case DWR_EVENT:
setInActiveTimer();
try {
context.sendDwaMessage(message(event), ResultCode.SUCCESS, null);
} catch (Throwable e) {
logger.debug("Can not send DWA, reconnecting", e);
doDisconnect();
doEndConnection();
}
break;
case DWA_EVENT:
setInActiveTimer();
watchdogSent = false;
break;
case SEND_MSG_EVENT:
try {
context.sendMessage(message(event));
} catch (Throwable e) {
logger.debug("Can not send message", e);
doDisconnect();
doEndConnection();
}
break;
default:
logger.debug("Unknown event type {} in state {}", type(event), state);
return false;
}
return true;
}
},
new MyState() { // SUSPECT
@Override
public boolean processEvent(StateEvent event) {
switch (type(event)) {
case DISCONNECT_EVENT:
doEndConnection();
break;
case TIMEOUT_EVENT:
doDisconnect();
doEndConnection();
break;
case STOP_EVENT:
try {
if (event.getData() == null) {
context.sendDprMessage(DisconnectCause.REBOOTING);
} else {
Integer disconnectCause = (Integer) event.getData();
context.sendDprMessage(disconnectCause);
}
setInActiveTimer();
switchToNextState(STOPPING);
} catch (Throwable e) {
logger.debug("Can not send DPR", e);
doDisconnect();
switchToNextState(DOWN);
}
break;
case CER_EVENT:
case CEA_EVENT:
case DWA_EVENT:
clearTimer();
switchToNextState(OKAY);
break;
case DPR_EVENT:
try {
int code = context.processDprMessage((IMessage) event.getData());
context.sendDpaMessage(message(event), code, null);
} catch (Throwable e) {
logger.debug("Can not send DPA", e);
}
IMessage message = (IMessage) event.getData();
try {
if (message.getAvps().getAvp(Avp.DISCONNECT_CAUSE) != null &&
message.getAvps().getAvp(Avp.DISCONNECT_CAUSE)
.getInteger32() == DisconnectCause.REBOOTING) {
doDisconnect();
doEndConnection();
} else {
doDisconnect();
switchToNextState(DOWN);
}
} catch (AvpDataException e1) {
logger.warn("Disconnect cause is bad.", e1);
doDisconnect();
switchToNextState(DOWN);
}
break;
case DWR_EVENT:
try {
int code = context.processDwrMessage((IMessage) event.getData());
context.sendDwaMessage(message(event), code, null);
switchToNextState(OKAY);
} catch (Throwable e) {
logger.debug("Can not send DWA", e);
doDisconnect();
switchToNextState(DOWN);
}
break;
case RECEIVE_MSG_EVENT:
clearTimer();
context.receiveMessage(message(event));
switchToNextState(OKAY);
break;
case SEND_MSG_EVENT: // todo buffering
throw new IllegalStateException("Connection is down");
default:
logger.debug("Unknown event type {} in state {}", type(event), state);
return false;
}
return true;
}
},
new MyState() { // DOWN
@Override
public void entryAction() {
setTimer(0);
//FIXME: baranowb: removed this, cause this breaks peers as
// it seems, if peer is not removed, it will linger
// without any way to process messages
// if (context.isRestoreConnection()) {
//PCB added FSM multithread
mustRun = false;
// }
context.removeStatistics();
}
@Override
public boolean processEvent(StateEvent event) {
switch (type(event)) {
case START_EVENT:
try {
context.createStatistics();
if (!context.isConnected()) {
context.connect();
}
context.sendCerMessage();
setTimer(CEA_TIMEOUT);
switchToNextState(INITIAL);
} catch (Throwable e) {
logger.debug("Connect error", e);
doEndConnection();
}
break;
case CER_EVENT:
context.createStatistics();
int resultCode = context.processCerMessage(key(event), message(event));
if (resultCode == ResultCode.SUCCESS) {
try {
context.sendCeaMessage(resultCode, message(event), null);
switchToNextState(OKAY);
} catch (Exception e) {
logger.debug("Failed to send CEA.", e);
doDisconnect(); // !
doEndConnection();
}
} else {
try {
context.sendCeaMessage(resultCode, message(event), null);
} catch (Exception e) {
logger.debug("Failed to send CEA.", e);
}
doDisconnect(); // !
doEndConnection();
}
break;
case SEND_MSG_EVENT:
// todo buffering
throw new IllegalStateException("Connection is down");
case STOP_EVENT:
case TIMEOUT_EVENT:
case DISCONNECT_EVENT:
// those are ~legal, ie. DISCONNECT_EVENT is sent back from connection
break;
default:
logger.debug("Unknown event type {} in state {}", type(event), state);
return false;
}
return true;
}
},
new MyState() { // REOPEN
@Override
public boolean processEvent(StateEvent event) {
switch (type(event)) {
case CONNECT_EVENT:
try {
context.sendCerMessage();
setTimer(CEA_TIMEOUT);
switchToNextState(INITIAL);
} catch (Throwable e) {
logger.debug("Can not send CER", e);
setTimer(REC_TIMEOUT);
}
break;
case TIMEOUT_EVENT:
try {
context.connect();
} catch (Exception e) {
logger.debug("Can not connect to remote peer", e);
setTimer(REC_TIMEOUT);
}
break;
case STOP_EVENT:
setTimer(0);
doDisconnect();
switchToNextState(DOWN);
break;
case DISCONNECT_EVENT:
break;
case SEND_MSG_EVENT:
// todo buffering
throw new IllegalStateException("Connection is down");
default:
logger.debug("Unknown event type {} in state {}", type(event), state);
return false;
}
return true;
}
},
new MyState() { // INITIAL
@Override
public void entryAction() {
setTimer(CEA_TIMEOUT);
}
@Override
public boolean processEvent(StateEvent event) {
switch (type(event)) {
case DISCONNECT_EVENT:
setTimer(0);
doEndConnection();
break;
case TIMEOUT_EVENT:
doDisconnect();
doEndConnection();
break;
case STOP_EVENT:
setTimer(0);
doDisconnect();
switchToNextState(DOWN);
break;
case CEA_EVENT:
setTimer(0);
if (context.processCeaMessage(key(event), message(event))) {
switchToNextState(OKAY);
} else {
doDisconnect(); // !
doEndConnection();
}
break;
case CER_EVENT:
int resultCode = context.processCerMessage(key(event), message(event));
if (resultCode == ResultCode.SUCCESS) {
try {
context.sendCeaMessage(resultCode, message(event), null);
switchToNextState(OKAY); // if other connection is win
} catch (Exception e) {
logger.debug("Can not send CEA", e);
doDisconnect();
doEndConnection();
}
} else if (resultCode == -1 || resultCode == ResultCode.NO_COMMON_APPLICATION) {
doDisconnect();
doEndConnection();
}
break;
case SEND_MSG_EVENT:
// todo buffering
throw new IllegalStateException("Connection is down");
default:
logger.debug("Unknown event type {} in state {}", type(event), state);
return false;
}
return true;
}
},
new MyState() { // STOPPING
@Override
public boolean processEvent(StateEvent event) {
switch (type(event)) {
case TIMEOUT_EVENT:
case DPA_EVENT:
switchToNextState(DOWN);
break;
case RECEIVE_MSG_EVENT:
context.receiveMessage(message(event));
break;
case SEND_MSG_EVENT:
throw new IllegalStateException("Stack now is stopping");
case STOP_EVENT:
case DISCONNECT_EVENT:
break;
default:
logger.debug("Unknown event type {} in state {}", type(event), state);
return false;
}
return true;
}
}
};
}
return states;
}
}