package org.jdiameter.server.impl.app.sh;
import java.util.TimerTask;
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.AvpSet;
import org.jdiameter.api.EventListener;
import org.jdiameter.api.IllegalDiameterStateException;
import org.jdiameter.api.InternalException;
import org.jdiameter.api.Message;
import org.jdiameter.api.NetworkReqListener;
import org.jdiameter.api.OverloadException;
import org.jdiameter.api.Request;
import org.jdiameter.api.ResultCode;
import org.jdiameter.api.RouteException;
import org.jdiameter.api.SessionFactory;
import org.jdiameter.api.app.AppEvent;
import org.jdiameter.api.app.StateChangeListener;
import org.jdiameter.api.app.StateEvent;
import org.jdiameter.api.sh.ServerShSession;
import org.jdiameter.api.sh.ServerShSessionListener;
import org.jdiameter.api.sh.events.ProfileUpdateAnswer;
import org.jdiameter.api.sh.events.ProfileUpdateRequest;
import org.jdiameter.api.sh.events.PushNotificationAnswer;
import org.jdiameter.api.sh.events.PushNotificationRequest;
import org.jdiameter.api.sh.events.SubscribeNotificationsAnswer;
import org.jdiameter.api.sh.events.SubscribeNotificationsRequest;
import org.jdiameter.api.sh.events.UserDataAnswer;
import org.jdiameter.api.sh.events.UserDataRequest;
import org.jdiameter.common.api.app.IAppSessionState;
import org.jdiameter.common.api.app.sh.IShMessageFactory;
import org.jdiameter.common.api.app.sh.ShSessionState;
import org.jdiameter.common.impl.app.AppAnswerEventImpl;
import org.jdiameter.common.impl.app.AppRequestEventImpl;
import org.jdiameter.common.impl.app.sh.ProfileUpdateRequestImpl;
import org.jdiameter.common.impl.app.sh.PushNotificationRequestImpl;
import org.jdiameter.common.impl.app.sh.ShSession;
/**
* Basic implementation of ShServerSession - can be one time - for UDR,PUR and
* constant for SNR-PNR pair, in case when SNA contains response code from range
* different than 2001-2004(success codes) user is responsible for maintaing
* state - releasing etc, same goes if result code is contained
* Experimental-Result AVP <br>
* If ShSession moves to ShSessionState.TERMINATED - it means that no further
* messages can be received via it and it should be discarded. <br>
* <br>
* Super project: mobicents-jainslee-server <br>
* 10:53:02 2008-09-05 <br>
*
* @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a>
* @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a>
*/
public class ShServerSessionImpl extends ShSession implements ServerShSession, EventListener<Request, Answer>, NetworkReqListener {
private static final long serialVersionUID = 1L;
protected ShSessionState state = ShSessionState.NOTSUBSCRIBED;
protected boolean stateless = false;
protected IShMessageFactory factory = null;
protected String destHost, destRealm;
protected Lock sendAndStateLock = new ReentrantLock();
protected ServerShSessionListener listener;
protected long appId = -1;
//Subscription timer
protected ScheduledFuture sft = null;
protected ScheduledFuture txSft=null;
protected boolean receivedSubTerm=false;
protected TxTimerTask txTimerTask=null;
public ShServerSessionImpl(IShMessageFactory fct, SessionFactory sf, ServerShSessionListener lst) {
this(null, fct, sf, lst);
}
public ShServerSessionImpl(String sessionId, IShMessageFactory fct, SessionFactory sf, ServerShSessionListener lst) {
if (lst == null) {
throw new IllegalArgumentException("Listener can not be null");
}
if (fct.getApplicationId() < 0) {
throw new IllegalArgumentException("ApplicationId can not be less than zero");
}
appId = fct.getApplicationId();
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 sendProfileUpdateAnswer(ProfileUpdateAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_PROFILE_UPDATE_ANSWER, null, answer);
}
public void sendPushNotificationRequest(PushNotificationRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_PUSH_NOTIFICATION_REQUEST,request,null);
}
public void sendSubscribeNotificationsAnswer(SubscribeNotificationsAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_SUBSCRIBE_NOTIFICATIONS_ANSWER, null, answer);
}
public void sendUserDataAnswer(UserDataAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_USER_DATA_ANSWER, null, answer);
}
public void receivedSuccessMessage(Request request, Answer answer) {
try {
sendAndStateLock.lock();
if (request.getApplicationId() == factory.getApplicationId()) {
if (request.getCommandCode() == PushNotificationRequestImpl.code) {
handleEvent(new Event(Event.Type.RECEIVE_PUSH_NOTIFICATION_ANSWER, factory.createPushNotificationRequest(request), factory.createPushNotificationAnswer(answer)));
return;
}
}
listener.doOtherEvent(this, new AppRequestEventImpl(request), new AppAnswerEventImpl(answer));
}
catch (Exception e) {
logger.debug("Failed to process success message", e);
}
finally {
sendAndStateLock.unlock();
}
}
public void timeoutExpired(Request request) {
try {
sendAndStateLock.lock();
if (request.getApplicationId() == factory.getApplicationId()) {
if (request.getCommandCode() == ProfileUpdateRequestImpl.code) {
handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, factory.createPushNotificationRequest(request), null));
return;
}
}
}
catch (Exception e) {
logger.debug("Failed to process timeout message", e);
}
finally {
sendAndStateLock.unlock();
}
}
public Answer processRequest(Request request) {
try {
if (request.getApplicationId() == factory.getApplicationId()) {
if (request.getCommandCode() == org.jdiameter.common.impl.app.sh.SubscribeNotificationsRequestImpl.code) {
handleEvent(new Event(Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_REQUEST, factory.createSubscribeNotificationsRequest(request), null));
return null;
}
else if(request.getCommandCode() == org.jdiameter.common.impl.app.sh.UserDataRequestImpl.code) {
handleEvent(new Event(Event.Type.RECEIVE_USER_DATA_REQUEST,factory.createUserDataRequest(request),null));
return null;
}
else if(request.getCommandCode() == org.jdiameter.common.impl.app.sh.ProfileUpdateRequestImpl.code) {
handleEvent(new Event(Event.Type.RECEIVE_PROFILE_UPDATE_REQUEST,factory.createProfileUpdateRequest(request),null));
return null;
}
else {
// FIXME: Anything to do here?
}
}
listener.doOtherEvent(this, new AppRequestEventImpl(request), null);
}
catch (Exception e) {
logger.debug("Failed to process request message", e);
}
return null;
}
public <E> E getState(Class<E> stateType) {
return stateType == ShSessionState.class ? (E) state : null;
}
public boolean handleEvent(StateEvent event) throws InternalException, OverloadException {
try {
sendAndStateLock.lock();
ShSessionState oldState = this.state;
ShSessionState newState = this.state;
try {
Event localEvent = (Event) event;
AppEvent request = localEvent.getRequest();
switch (state) {
case NOTSUBSCRIBED:
if (event.getType() == Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_REQUEST) {
//Do nothing, we have to wait for response send callback
startTxTimer(request);
//We use newState in case of first request, it can be unsubscribe or bad
newState=doSNX(request);
}
else if (event.getType() == Event.Type.RECEIVE_PROFILE_UPDATE_REQUEST) {
//newState=ShSessionState.TERMINATED;
startTxTimer(request);
}
else if (event.getType() == Event.Type.RECEIVE_USER_DATA_REQUEST) {
//newState=ShSessionState.TERMINATED;
startTxTimer(request);
}
else if(event.getType()== Event.Type.SEND_SUBSCRIBE_NOTIFICATIONS_ANSWER) {
newState=doSNX(localEvent.getAnswer());
stopTxTimer();
}
else if (event.getType() == Event.Type.TIMEOUT_EXPIRES) {
newState = ShSessionState.TERMINATED;
//FIXME: What happens here?
}
else if(event.getType()== Event.Type.TX_TIMER_EXPIRED) {
newState = ShSessionState.TERMINATED;
//FIXME Result code ???
try {
Answer answer= ((Request)request.getMessage()).createAnswer(ResultCode.TOO_BUSY);
session.send(answer);
}
catch (Exception e) {
logger.debug("Unable to send failure answer", e);
}
}
else {
// Other messages just make it go into terminated state and release send: UDA
stopTxTimer();
newState = ShSessionState.TERMINATED;
}
break;
case SUBSCRIBED:
//FIXME: should we also use here startTx, stopTx?
if(event.getType()== Event.Type.SEND_SUBSCRIBE_NOTIFICATIONS_ANSWER) {
newState=doSNX(localEvent.getAnswer());
stopTxTimer();
}
else if(event.getType() == Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_REQUEST) {
newState=doSNX(request);
//startTxTimer(request);
}
//FIXME: Any change here - even on timeout?
break;
case TERMINATED:
break;
}
// Do the delivery
// FIXME: Should we look if we are in terminated state?
try {
switch ((Event.Type) localEvent.getType()) {
case RECEIVE_PROFILE_UPDATE_REQUEST:
listener.doProfileUpdateRequestEvent(this, (ProfileUpdateRequest) localEvent.getRequest());
break;
case RECEIVE_PUSH_NOTIFICATION_ANSWER:
listener.doPushNotificationAnswerEvent(this, (PushNotificationRequest) localEvent.getRequest(), (PushNotificationAnswer) localEvent.getAnswer());
break;
case RECEIVE_SUBSCRIBE_NOTIFICATIONS_REQUEST:
listener.doSubscribeNotificationsRequestEvent(this, (SubscribeNotificationsRequest) localEvent.getRequest());
break;
case RECEIVE_USER_DATA_REQUEST:
listener.doUserDataRequestEvent(this, (UserDataRequest) localEvent.getRequest());
break;
case SEND_PROFILE_UPDATE_ANSWER:
dispatchEvent(localEvent.getAnswer());
stopTxTimer();
break;
case SEND_PUSH_NOTIFICATION_REQUEST:
dispatchEvent(localEvent.getRequest());
break;
case SEND_SUBSCRIBE_NOTIFICATIONS_ANSWER:
dispatchEvent(localEvent.getAnswer());
stopTxTimer();
break;
case SEND_USER_DATA_ANSWER:
dispatchEvent(localEvent.getAnswer());
stopTxTimer();
break;
case TIMEOUT_EXPIRES:
break;
default:
logger.error("Wrong message type={} req={} ans={}", new Object[]{localEvent.getType(), localEvent.getRequest(), localEvent.getAnswer()});
}
}
catch (IllegalDiameterStateException idse) {
throw new InternalException(idse);
}
catch (RouteException re) {
throw new InternalException(re);
}
}
finally {
if (newState != oldState) {
setState(newState, true);
}
}
}
finally {
sendAndStateLock.unlock();
}
return true;
}
public boolean isStateless() {
return stateless;
}
protected void send(Event.Type type, AppEvent request, AppEvent 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 exc) {
throw new InternalException(exc);
}
finally {
sendAndStateLock.unlock();
}
}
protected void dispatchEvent(AppEvent event) throws InternalException
{
try{
session.send(event.getMessage(), this);
//FIXME: add differentation on server/client request
}
catch(Exception e) {
logger.debug("Failed to dispatch event", e);
}
}
protected void setState(ShSessionState newState) {
setState(newState, true);
}
protected void setState(ShSessionState newState, boolean release) {
IAppSessionState oldState = state;
state = newState;
for (StateChangeListener i : stateListeners) {
i.stateChanged((Enum) oldState, (Enum) newState);
}
if (newState == ShSessionState.TERMINATED) {
if (release) {
this.release();
}
stopSubscriptionTimer();
stopTxTimer();
}
}
public void release() {
try {
sendAndStateLock.lock();
if (state != ShSessionState.TERMINATED) {
setState(ShSessionState.TERMINATED, false);
//session.release();
}
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;
}
catch (Exception e) {
logger.debug("Failed to release session", e);
}
finally {
sendAndStateLock.unlock();
}
}
protected long extractExpirationTime(Message answer) {
return -1;
}
protected ShSessionState doSNX(AppEvent message) throws InternalException {
ShSessionState newState = state;
AvpSet set = message.getMessage().getAvps();
long resultCode = -1;
// Experimental-Result-Code:297, Result-Code:268
Avp avp = null;
try{
if(message.getMessage().isRequest())
{
Avp subsReqType = set.getAvp(705);
if(subsReqType == null || subsReqType.getInteger32() < 0 || subsReqType.getInteger32() > 1) {
// This is wrong!
newState = ShSessionState.TERMINATED;
}
else {
switch(subsReqType.getInteger32())
{
case 0:
// Subscribe
startSubscriptionTimer(message.getMessage());
break;
case 1:
receivedSubTerm = true;
break;
}
}
}
else {
if(receivedSubTerm) {
newState = ShSessionState.TERMINATED;
stopSubscriptionTimer();
}
else {
avp = set.getAvp(Avp.EXPERIMENTAL_RESULT) != null ? set.getAvp(Avp.EXPERIMENTAL_RESULT).getGrouped().getAvp(Avp.EXPERIMENTAL_RESULT_CODE) :
set.getAvp(Avp.RESULT_CODE);
try {
resultCode = avp.getUnsigned32();
if (resultCode >= 2000 && resultCode < 3000) {
startSubscriptionTimer(message.getMessage());
newState = ShSessionState.SUBSCRIBED;
}
else {
// its a failure?
newState= ShSessionState.TERMINATED;
}
}
catch (AvpDataException e) {
logger.debug("Could not retrieve Result-Code from message", e);
}
}
}
}
catch(Exception e) {
logger.debug("Unable to process event", e);
newState = ShSessionState.TERMINATED;
}
return newState;
}
private void startSubscriptionTimer( Message message) {
long expiryTime = extractExpirationTime(message);
if (expiryTime >= 0) {
stopSubscriptionTimer();
this.sft = scheduler.schedule(new Runnable() {
public void run() {
try {
sendAndStateLock.lock();
if (state != ShSessionState.TERMINATED) {
setState(ShSessionState.TERMINATED);
}
}
finally {
sendAndStateLock.unlock();
}
}
}, expiryTime, TimeUnit.SECONDS);
}
}
private void stopSubscriptionTimer() {
if(this.sft != null) {
this.sft.cancel(false);
this.sft = null;
}
}
private void startTxTimer(AppEvent request) {
try {
//FIXME: isnt this bad? Shouldnt send be before state change?
sendAndStateLock.lock();
this.stopTxTimer();
this.txTimerTask = new TxTimerTask(request);
this.txSft = ShSession.scheduler.schedule(this.txTimerTask, this.factory.getMessageTimeout(), TimeUnit.MILLISECONDS);
}
catch (Exception e) {
}
finally {
sendAndStateLock.unlock();
}
}
private void stopTxTimer()
{
try {
//FIXME: isnt this bad? Shouldnt send be before state change?
sendAndStateLock.lock();
if (this.txTimerTask != null) {
this.txSft.cancel(false);
this.txSft = null;
this.txTimerTask.cancel();
this.txTimerTask=null;
}
}
catch (Exception exc) {
}
finally {
sendAndStateLock.unlock();
}
}
private class TxTimerTask extends TimerTask
{
private AppEvent request = null;
public TxTimerTask(AppEvent request2) {
super();
this.request = request2;
}
@Override
public boolean cancel() {
this.request = null;
return super.cancel();
}
@Override
public void run() {
try {
//FIXME: isnt this bad? Shouldnt send be before state change?
sendAndStateLock.lock();
handleEvent(new Event(Event.Type.TX_TIMER_EXPIRED,request,null));
}
catch (InternalException e) {
logger.error("Internal Exception", e);
}
catch (OverloadException e) {
logger.error("Overload Exception", e);
}
finally {
this.request = null;
sendAndStateLock.unlock();
}
}
}
protected boolean isProvisional(long result) {
return result >= 1000 && result < 2000;
}
protected boolean isSuccess(long result) {
return result >= 2000 && result < 3000;
}
}