package org.jdiameter.server.impl.app.cca;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jdiameter.api.Answer;
import org.jdiameter.api.Avp;
import org.jdiameter.api.AvpDataException;
import org.jdiameter.api.EventListener;
import org.jdiameter.api.IllegalDiameterStateException;
import org.jdiameter.api.InternalException;
import org.jdiameter.api.NetworkReqListener;
import org.jdiameter.api.OverloadException;
import org.jdiameter.api.Request;
import org.jdiameter.api.RouteException;
import org.jdiameter.api.SessionFactory;
import org.jdiameter.api.acc.events.AccountAnswer;
import org.jdiameter.api.app.AppAnswerEvent;
import org.jdiameter.api.app.AppEvent;
import org.jdiameter.api.app.AppRequestEvent;
import org.jdiameter.api.app.StateChangeListener;
import org.jdiameter.api.app.StateEvent;
import org.jdiameter.api.auth.events.AbortSessionAnswer;
import org.jdiameter.api.auth.events.ReAuthRequest;
import org.jdiameter.api.auth.events.SessionTermAnswer;
import org.jdiameter.api.cca.ServerCCASession;
import org.jdiameter.api.cca.ServerCCASessionListener;
import org.jdiameter.api.cca.events.JCreditControlAnswer;
import org.jdiameter.api.cca.events.JCreditControlRequest;
import org.jdiameter.common.api.app.IAppSessionState;
import org.jdiameter.common.api.app.cca.ICCAMessageFactory;
import org.jdiameter.common.api.app.cca.IServerCCASessionContext;
import org.jdiameter.common.api.app.cca.ServerCCASessionState;
import org.jdiameter.common.impl.app.AppAnswerEventImpl;
import org.jdiameter.common.impl.app.AppRequestEventImpl;
import org.jdiameter.common.impl.app.acc.AccountAnswerImpl;
import org.jdiameter.common.impl.app.acc.AccountRequestImpl;
import org.jdiameter.common.impl.app.auth.AbortSessionAnswerImpl;
import org.jdiameter.common.impl.app.auth.AbortSessionRequestImpl;
import org.jdiameter.common.impl.app.auth.ReAuthAnswerImpl;
import org.jdiameter.common.impl.app.auth.ReAuthRequestImpl;
import org.jdiameter.common.impl.app.auth.SessionTermAnswerImpl;
import org.jdiameter.common.impl.app.auth.SessionTermRequestImpl;
import org.jdiameter.common.impl.app.cca.AppCCASessionImpl;
public class ServerCCASessionImpl extends AppCCASessionImpl implements ServerCCASession, NetworkReqListener,EventListener<Request, Answer> {
private static final long serialVersionUID = 1L;
//Is there any state?
protected boolean stateless = true;
protected ServerCCASessionState state = ServerCCASessionState.IDLE;
protected ICCAMessageFactory factory = null;
protected String originHost, originRealm;
protected Lock sendAndStateLock = new ReentrantLock();
protected long[] authAppIds = new long[]{4};
protected ServerCCASessionListener listener = null;
protected IServerCCASessionContext context = null;
protected ScheduledFuture tccFuture = null;
public ServerCCASessionImpl(ICCAMessageFactory fct, SessionFactory sf, ServerCCASessionListener lst) {
this(null,fct,sf,lst);
}
public ServerCCASessionImpl(String sessionId, ICCAMessageFactory fct, SessionFactory sf, ServerCCASessionListener lst) {
if (lst == null) {
throw new IllegalArgumentException("Listener can not be null");
}
if (fct.getApplicationIds() == null) {
throw new IllegalArgumentException("ApplicationId can not be less than zero");
}
if(lst instanceof IServerCCASessionContext) {
context = (IServerCCASessionContext)lst;
}
authAppIds = fct.getApplicationIds();
listener = lst;
factory = fct;
try {
if (sessionId == null) {
session = sf.getNewSession();
}
else {
session = sf.getNewSession(sessionId);
}
session.setRequestListener(this);
}
catch (InternalException e) {
throw new IllegalArgumentException(e);
}
}
public void sendCreditControlAnswer(JCreditControlAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
//could be send but
handleEvent(new Event(false,null,answer));
}
public void sendReAuthRequest(ReAuthRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SENT_RAR, request, null);
}
public boolean isStateless() {
return stateless;
}
public <E> E getState(Class<E> stateType) {
return stateType == ServerCCASessionState.class ? (E) state : null;
}
public boolean handleEvent(StateEvent event) throws InternalException, OverloadException {
ServerCCASessionState newState = null;
try {
sendAndStateLock.lock();
//Can be null if there is no state transition, transition to IDLE state should terminate this app session
Event localEvent = (Event) event;
//Its kind of akward, but with two state on server side its easier to go through event types?
//but for sake of FSM readability
Event.Type eventType = (Event.Type) localEvent.getType();
switch(state)
{
case IDLE:
switch(eventType)
{
case RECEIVED_INITIAL:
listener.doCreditControlRequest(this, (JCreditControlRequest)localEvent.getRequest());
break;
case RECEIVED_EVENT:
listener.doCreditControlRequest(this, (JCreditControlRequest)localEvent.getRequest());
break;
//Do nothing, only deliver
case SENT_EVENT_RESPONSE:
//We dont care about code, its always IDLE == terminate
newState = ServerCCASessionState.IDLE;
dispatchEvent(localEvent.getAnswer());
break;
case SENT_INITIAL_RESPONSE:
JCreditControlAnswer answer = (JCreditControlAnswer) localEvent.getAnswer();
try {
if(isSuccess(answer.getResultCodeAvp().getUnsigned32())) {
startTcc(answer.getValidityTimeAvp());
newState = ServerCCASessionState.OPEN;
}
else if(isProvisional(answer.getResultCodeAvp().getUnsigned32())) {
//
}
else {
newState = ServerCCASessionState.IDLE;
}
dispatchEvent(localEvent.getAnswer());
}
catch (AvpDataException e) {
throw new InternalException(e);
}
break;
default:
throw new InternalException("Wrong state: "+ServerCCASessionState.IDLE+" one event: "+eventType+" "+localEvent.getRequest()+" "+localEvent.getAnswer());
}
case OPEN:
switch(eventType)
{
case RECEIVED_UPDATE:
listener.doCreditControlRequest(this, (JCreditControlRequest)localEvent.getRequest());
break;
//Do nothing, deliver
case SENT_UPDATE_RESPONSE:
JCreditControlAnswer answer = (JCreditControlAnswer) localEvent.getAnswer();
try {
if(isSuccess(answer.getResultCodeAvp().getUnsigned32())) {
startTcc(answer.getValidityTimeAvp());
}
else {
//its a failure, we wait for Tcc to fire
}
}
catch (AvpDataException e) {
throw new InternalException(e);
}
dispatchEvent(localEvent.getAnswer());
break;
case RECEIVED_TERMINATE:
listener.doCreditControlRequest(this, (JCreditControlRequest)localEvent.getRequest());
break;
case SENT_TERMINATE_RESPONSE:
answer = (JCreditControlAnswer) localEvent.getAnswer();
try {
if(isSuccess(answer.getResultCodeAvp().getUnsigned32())) {
stopTcc(false);
}
else {
//its a failure, we wait for Tcc to fire
}
}
catch (AvpDataException e) {
throw new InternalException(e);
}
finally {
newState = ServerCCASessionState.IDLE;
}
dispatchEvent(localEvent.getAnswer());
break;
case RECEIVED_RAA:
listener.doReAuthAnswer(this, new ReAuthRequestImpl(localEvent.getRequest().getMessage()),new ReAuthAnswerImpl(localEvent.getAnswer().getMessage()));
break;
case SENT_RAR:
dispatchEvent(localEvent.getAnswer());
break;
}
}
return true;
}
catch (Exception e) {
throw new InternalException(e);
}
finally {
if(newState != null) {
setState(newState);
}
sendAndStateLock.unlock();
}
}
public Answer processRequest(Request request) {
try{
//FIXME: baranowb: add message validation here!!!
//We handle CCR,STR,ACR,ASR other go into extension
switch(request.getCommandCode())
{
case JCreditControlAnswer.code:
this.handleEvent(new Event(true,factory.createCreditControlRequest(request),null));
break;
//All other go straight to listner, they dont change state machine
//Suprisingly there is no factory.... ech
//FIXME: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
case SessionTermAnswer.code:
listener.doSessionTerminationRequest(this, new SessionTermRequestImpl(request));
break;
case AbortSessionAnswer.code:
listener.doAbortSessionRequest(this, new AbortSessionRequestImpl(request));
break;
case AccountAnswer.code:
listener.doAccountingRequest(this, new AccountRequestImpl(request));
break;
default:
listener.doOtherEvent(this, new AppRequestEventImpl(request), null);
break;
}
}
catch(Exception e) {
logger.debug("Failed to process request message", e);
}
return null;
}
public void receivedSuccessMessage(Request request, Answer answer) {
try{
//FIXME: baranowb: add message validation here!!!
//We handle CCR,STR,ACR,ASR other go into extension
switch(request.getCommandCode())
{
case ReAuthRequest.code:
handleEvent(new Event(Event.Type.RECEIVED_RAA,factory.createReAuthRequest(request ),factory.createReAuthAnswer(answer)));
break;
//All other go straight to listner, they dont change state machine
//Suprisingly there is no factory.... ech
//FIXME: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
case SessionTermAnswer.code:
listener.doSessionTerminationAnswer(this,null ,new SessionTermAnswerImpl(answer));
break;
case AbortSessionAnswer.code:
listener.doAbortSessionAnswer(this,null, new AbortSessionAnswerImpl(answer));
break;
case AccountAnswer.code:
listener.doAccountingAnswer(this,null, new AccountAnswerImpl(answer));
break;
default:
listener.doOtherEvent(this, null, new AppAnswerEventImpl(answer));
break;
}
}
catch(Exception e) {
logger.debug("Failed to process success message", e);
}
}
public void timeoutExpired(Request request) {
context.timeoutExpired(request);
//FIXME: Should we release ?
}
private void startTcc(Avp validityAvp) {
long defaultValue = 2 * context.getDefaultValidityTime();
if(validityAvp != null) {
try {
defaultValue = 2 * validityAvp.getUnsigned32();
}
catch (AvpDataException e) {
logger.debug("Unable to retrieve Validity-Time AVP value, using default.", e);
}
}
if(tccFuture != null) {
stopTcc(true);
tccFuture = scheduler.schedule(new TccScheduledTask(this),defaultValue,TimeUnit.SECONDS);
context.sessionSupervisionTimerReStarted(this, tccFuture);
}
else {
tccFuture = scheduler.schedule(new TccScheduledTask(this),defaultValue,TimeUnit.SECONDS);
}
}
private void stopTcc(boolean willRestart)
{
if(tccFuture != null) {
tccFuture.cancel(false);
ScheduledFuture f = tccFuture;
tccFuture = null;
if(!willRestart) {
context.sessionSupervisionTimerStopped(this, f);
}
}
}
protected boolean isProvisional(long resultCode) {
return resultCode >= 1000 && resultCode < 2000;
}
protected boolean isSuccess(long resultCode) {
return resultCode >= 2000 && resultCode < 3000;
}
protected void setState(ServerCCASessionState newState) {
setState(newState, true);
}
protected void setState(ServerCCASessionState newState, boolean release) {
IAppSessionState oldState = state;
state = newState;
for (StateChangeListener i : stateListeners) {
i.stateChanged((Enum) oldState, (Enum) newState);
}
if (newState == ServerCCASessionState.IDLE) {
if (release) {
this.release();
}
stopTcc(false);
}
}
@Override
public void release() {
this.stopTcc(false);
if(super.isValid()) {
super.release();
}
if(super.session != null) {
super.session.setRequestListener(null);
}
this.session = null;
if(listener != null) {
this.removeStateChangeNotification((StateChangeListener) listener);
}
this.listener = null;
this.factory = null;
}
private class TccScheduledTask implements Runnable
{
ServerCCASession session = null;
private TccScheduledTask(ServerCCASession session) {
super();
this.session = session;
}
public void run() {
context.sessionSupervisionTimerExpired(session);
try {
sendAndStateLock.lock();
tccFuture = null;
setState(ServerCCASessionState.IDLE);
}
finally {
sendAndStateLock.unlock();
}
}
}
protected void send(Event.Type type, AppRequestEvent request, AppAnswerEvent answer) throws InternalException {
try {
//FIXME: isnt this bad? Shouldnt send be before state change?
sendAndStateLock.lock();
if (type != null) {
handleEvent(new Event(type, request, answer));
}
}
catch (Exception e) {
throw new InternalException(e);
}
finally {
sendAndStateLock.unlock();
}
}
protected void dispatchEvent(AppEvent event) throws InternalException
{
try{
session.send(event.getMessage(), this);
// Store last destinmation information
//FIXME: add differentation on server/client request
originRealm = event.getMessage().getAvps().getAvp(Avp.ORIGIN_REALM).getOctetString();
originHost = event.getMessage().getAvps().getAvp(Avp.ORIGIN_HOST).getOctetString();
}
catch(Exception e) {
//throw new InternalException(e);
logger.debug("Failure trying to dispatch event", e);
}
}
}