package org.jdiameter.client.impl.app.sh; 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.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.ClientShSession; import org.jdiameter.api.sh.ClientShSessionListener; import org.jdiameter.api.sh.events.ProfileUpdateRequest; import org.jdiameter.api.sh.events.PushNotificationAnswer; import org.jdiameter.api.sh.events.SubscribeNotificationsRequest; 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.ProfileUpdateAnswerImpl; import org.jdiameter.common.impl.app.sh.ProfileUpdateRequestImpl; import org.jdiameter.common.impl.app.sh.PushNotificationRequestImpl; import org.jdiameter.common.impl.app.sh.ShSession; import org.jdiameter.common.impl.app.sh.SubscribeNotificationsAnswerImpl; import org.jdiameter.common.impl.app.sh.SubscribeNotificationsRequestImpl; import org.jdiameter.common.impl.app.sh.UserDataAnswerImpl; import org.jdiameter.common.impl.app.sh.UserDataRequestImpl; /** * Basic implementation of ShClientSession - 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 ShClientSessionImpl extends ShSession implements ClientShSession, 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 ClientShSessionListener listener; protected long appId = -1; protected ScheduledFuture sft = null; public ShClientSessionImpl(IShMessageFactory fct, SessionFactory sf, ClientShSessionListener lst) { this(null, fct, sf, lst); } public ShClientSessionImpl(String sessionId, IShMessageFactory fct, SessionFactory sf, ClientShSessionListener 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 Answer processRequest(Request request) { try { if (request.getApplicationId() == factory.getApplicationId()) { if (request.getCommandCode() == org.jdiameter.common.impl.app.sh.PushNotificationRequestImpl.code) { handleEvent(new Event(Event.Type.RECEIVE_PUSH_NOTIFICATION_REQUEST, factory.createPushNotificationRequest(request), null)); return null; } } listener.doOtherEvent(this, new AppRequestEventImpl(request), null); } catch (Exception e) { logger.debug("Failed to process request {}", request, 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 answer = localEvent.getAnswer(); switch (state) { case NOTSUBSCRIBED: if (event.getType() == Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_ANSWER) { newState = doSNX(answer); } else if (event.getType() == Event.Type.RECEIVE_PUSH_NOTIFICATION_REQUEST) { newState = ShSessionState.SUBSCRIBED; } else if (event.getType() == Event.Type.TIMEOUT_EXPIRES) { newState = ShSessionState.TERMINATED; } else if(event.getType() != Event.Type.SEND_USER_DATA_REQUEST && event.getType() != Event.Type.SEND_PROFILE_UPDATE_REQUEST && event.getType() != Event.Type.SEND_SUBSCRIBE_NOTIFICATIONS_REQUEST ){ // Other messages just make it go into terminated state and release newState = ShSessionState.TERMINATED; } break; case SUBSCRIBED: if (event.getType() == Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_ANSWER) { newState = doSNX(answer); } else if (event.getType() == Event.Type.TIMEOUT_EXPIRES) { if (localEvent.getRequest().getCommandCode() == SubscribeNotificationsRequestImpl.code) { newState = ShSessionState.TERMINATED; } } else { // FIXME: What about timeout here? } break; case TERMINATED: // We shouldnt receive anything here break; } // Do the delivery // FIXME: Should we look if we are in terminated state? try { switch ((Event.Type) localEvent.getType()) { case RECEIVE_PUSH_NOTIFICATION_REQUEST: listener.doPushNotificationRequestEvent(this, new PushNotificationRequestImpl( (Request) localEvent.getRequest().getMessage() )); break; case RECEIVE_PROFILE_UPDATE_ANSWER: listener.doProfileUpdateAnswerEvent(this, null, new ProfileUpdateAnswerImpl( (Answer) localEvent.getAnswer().getMessage())); break; case RECEIVE_USER_DATA_ANSWER: listener.doUserDataAnswerEvent(this, null, new UserDataAnswerImpl((Answer) localEvent.getAnswer().getMessage())); break; case RECEIVE_SUBSCRIBE_NOTIFICATIONS_ANSWER: listener.doSubscribeNotificationsAnswerEvent(this, null, new SubscribeNotificationsAnswerImpl( (Answer) localEvent.getAnswer().getMessage())); break; case SEND_PROFILE_UPDATE_REQUEST: case SEND_PUSH_NOTIFICATION_ANSWER: case SEND_SUBSCRIBE_NOTIFICATIONS_REQUEST: case SEND_USER_DATA_REQUEST: case TIMEOUT_EXPIRES: // TODO Anything here? 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); } } } finally { sendAndStateLock.unlock(); } return true; } protected ShSessionState doSNX(AppEvent answer) throws InternalException { ShSessionState newState = state; AvpSet set = answer.getMessage().getAvps(); long resultCode = -1; try { Avp avp = set.getAvp(Avp.EXPERIMENTAL_RESULT) != null ? set.getAvp(Avp.EXPERIMENTAL_RESULT).getGrouped().getAvp(Avp.EXPERIMENTAL_RESULT_CODE) : set.getAvp(Avp.RESULT_CODE); resultCode = avp.getUnsigned32(); if (resultCode >= 2000 && resultCode < 3000) { long expiryTime = extractExpirationTime(answer.getMessage()); if (expiryTime >= 0) { if (this.sft != null) { this.sft.cancel(true); } 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); } else { // FIXME: We relly on user? } newState = ShSessionState.SUBSCRIBED; } else { // its a failure? newState = ShSessionState.TERMINATED; } } catch (AvpDataException e) { logger.debug("Could not retrieve Result-Code from Message", e); } return newState; } public boolean isStateless() { return stateless; } public void sendProfileUpdateRequest(ProfileUpdateRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { send(Event.Type.SEND_PROFILE_UPDATE_REQUEST, request, null); } public void sendPushNotificationAnswer(PushNotificationAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { send(Event.Type.SEND_PUSH_NOTIFICATION_ANSWER, null, answer); } public void sendSubscribeNotificationsRequest(SubscribeNotificationsRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { send(Event.Type.SEND_SUBSCRIBE_NOTIFICATIONS_REQUEST, request, null); } public void sendUserDataRequest(UserDataRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { send(Event.Type.SEND_USER_DATA_REQUEST, request, null); } protected void send(Event.Type type, AppEvent request, AppEvent answer) throws InternalException { try { sendAndStateLock.lock(); if (type != null) { handleEvent(new Event(type, request, answer)); } AppEvent event = null; if (request != null) { event = request; } else { event = answer; } session.send(event.getMessage(), this); if(request != null) { AvpSet avps = request.getMessage().getAvps(); Avp a = null; // Store last destinmation information if((a = avps.getAvp(Avp.DESTINATION_REALM)) != null) { destRealm = a.getOctetString(); } if((a = avps.getAvp(Avp.DESTINATION_HOST)) != null) { destHost = a.getOctetString(); } } } catch (Exception e) { throw new InternalException(e); } finally { sendAndStateLock.unlock(); } } public void receivedSuccessMessage(Request request, Answer answer) { try { sendAndStateLock.lock(); if (request.getApplicationId() == factory.getApplicationId()) { if (request.getCommandCode() == ProfileUpdateRequestImpl.code) { handleEvent(new Event(Event.Type.RECEIVE_PROFILE_UPDATE_ANSWER, factory.createProfileUpdateRequest(request), factory.createProfileUpdateAnswer(answer))); return; } else if (request.getCommandCode() == UserDataRequestImpl.code) { handleEvent(new Event(Event.Type.RECEIVE_USER_DATA_ANSWER, factory.createUserDataRequest(request), factory.createUserDataAnswer(answer))); return; } else if (request.getCommandCode() == SubscribeNotificationsRequestImpl.code) { handleEvent(new Event(Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_ANSWER, factory.createSubscribeNotificationsRequest(request), factory .createSubscribeNotificationsAnswer(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.createProfileUpdateRequest(request), null)); return; } else if (request.getCommandCode() == UserDataRequestImpl.code) { handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, factory.createUserDataRequest(request), null)); return; } else if (request.getCommandCode() == SubscribeNotificationsRequestImpl.code) { handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, factory.createSubscribeNotificationsRequest(request), null)); return; } } // FIXME: Anything else todo? } catch (Exception e) { logger.debug("Failed to process timeout message", e); } finally { sendAndStateLock.unlock(); } } 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(); } if (sft != null) { sft.cancel(true); sft = null; } } } protected long extractExpirationTime(Message answer) { return -1; } 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(); } } }