package org.jdiameter.client.impl.app.cca; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; 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.AvpDataException; 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.acc.events.AccountAnswer; import org.jdiameter.api.app.AppAnswerEvent; import org.jdiameter.api.app.AppEvent; 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.ReAuthAnswer; import org.jdiameter.api.auth.events.ReAuthRequest; import org.jdiameter.api.auth.events.SessionTermAnswer; import org.jdiameter.api.cca.ClientCCASession; import org.jdiameter.api.cca.ClientCCASessionListener; import org.jdiameter.api.cca.events.JCreditControlAnswer; import org.jdiameter.api.cca.events.JCreditControlRequest; import org.jdiameter.client.impl.app.cca.Event.Type; import org.jdiameter.common.api.app.IAppSessionState; import org.jdiameter.common.api.app.cca.ClientCCASessionState; import org.jdiameter.common.api.app.cca.ICCAMessageFactory; import org.jdiameter.common.api.app.cca.IClientCCASessionContext; import org.jdiameter.common.api.app.cca.IServerCCASessionContext; 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.SessionTermAnswerImpl; import org.jdiameter.common.impl.app.auth.SessionTermRequestImpl; import org.jdiameter.common.impl.app.cca.AppCCASessionImpl; public class ClientCCASessionImpl extends AppCCASessionImpl implements ClientCCASession, NetworkReqListener, EventListener<Request, Answer> { private static final long serialVersionUID = 1L; protected boolean stateless = true; protected boolean statelessModeSet=false; protected ClientCCASessionState state = ClientCCASessionState.IDLE; protected ICCAMessageFactory factory = null; //protected String destHost, destRealm; protected String originHost,originRealm; protected Lock sendAndStateLock = new ReentrantLock(); protected long[] authAppIds = new long[] { 4 }; protected ClientCCASessionListener listener = null; protected IClientCCASessionContext context = null; protected ScheduledFuture txFuture = null; protected static final Set<Integer> temporaryErrorCodes; private static final long TX_TIMER_DEFAULT_VALUE = 10; protected int gatheredCCFH = -300; protected int gatheredDDFH = -300; protected int gatheredRequestedAction = -300; protected static final int CCFH_TERMINATE = 0; protected static final int CCFH_CONTINUE = 1; protected static final int CCFH_RETRY_AND_TERMINATE = 2; protected static final int DIAMETER_END_USER_SERVICE_DENIED = 4010; private static final long CREDIT_CONTROL_NOT_APPLICABLE = 4011; private static final long USER_UNKNOWN = 5030; private static final long DIRECT_DEBITING = 0; private static final long REFUND_ACCOUNT = 1; private static final long CHECK_BALANCE = 2; private static final long PRICE_ENQUIRY = 3; private static final long TERMINATE_OR_BUFFER = 0; private static final long CONTINUE = 1; /** * This is buffered message, there can be only one, specs do not say a thing * about multiple */ private Message buffer = null; static { HashSet<Integer> tmp = new HashSet<Integer>(); // FIXME: add codes // DIAMETER_TOO_BUSY tmp.add(3004); // DIAMETER_UNABLE_TO_DELIVER tmp.add(3002); // DIAMETER_LOOP_DETECTED tmp.add(3005); temporaryErrorCodes = Collections.unmodifiableSet(tmp); } // FIXME: This is not described, but in FSM - transitions go from PendingI // -> PendingI and PendungU -> PendingU in two cases - TermRequest is to be // sent and UpdateRequest // Indicating that messages are queued and sent once response is received // (possibly) - once session goe sinto Open state this queue is looked UP :] // This should be done by app, but if for some reason its not, we handle it. protected ArrayList<Event> eventQueue = new ArrayList<Event>(); public ClientCCASessionImpl(ICCAMessageFactory fct, SessionFactory sf, ClientCCASessionListener lst) { this(null,fct,sf,lst); } public ClientCCASessionImpl(String sessionId, ICCAMessageFactory fct, SessionFactory sf, ClientCCASessionListener 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 = (IClientCCASessionContext)lst; } authAppIds = fct.getApplicationIds(); listener = lst; factory = fct; try { session = sessionId == null ? sf.getNewSession() : sf.getNewSession(sessionId); session.setRequestListener(this); } catch (InternalException e) { throw new IllegalArgumentException(e); } } protected int getLocalCCFH() { return gatheredCCFH >= 0 ? gatheredCCFH : context.getDefaultCCFHValue(); } protected int getLocalDDFH() { return gatheredDDFH >= 0 ? gatheredDDFH : context.getDefaultDDFHValue(); } public void sendCreditControlRequest(JCreditControlRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { extractFHAVPs(request, null); this.handleEvent(new Event(true, request, null)); } public void sendReAuthAnswer(ReAuthAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { this.handleEvent(new Event(Event.Type.SEND_RAA, null, answer)); } public boolean isStateless() { return this.stateless; } public <E> E getState(Class<E> stateType) { return stateType == ClientCCASessionState.class ? (E) state : null; } public boolean handleEvent(StateEvent event) throws InternalException, OverloadException { return this.isStateless() ? handleEventForEventBased(event) : handleEventForSessionBased(event); } protected boolean handleEventForEventBased(StateEvent event) throws InternalException, OverloadException { try { sendAndStateLock.lock(); Event localEvent = (Event) event; Event.Type eventType = (Type) localEvent.getType(); switch (this.state) { case IDLE: switch (eventType) { case SEND_EVENT_REQUEST: // Action: send initial request, starTx, move to PendingI state // However failure handling is complicated, so we shift state first startTx((JCreditControlRequest) localEvent.getRequest()); setState(ClientCCASessionState.PENDING_EVENT); try { dispatchEvent(localEvent.getRequest()); } catch (Exception e) { // This handles failure to send in PendingI state in FSM table logger.debug("Failure handling send event request", e); handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; case PENDING_EVENT: switch (eventType) { case RECEIVE_EVENT_ANSWER: AppAnswerEvent answer = (AppAnswerEvent) localEvent.getAnswer(); try { if (isSuccess(answer.getResultCodeAvp().getUnsigned32())) { // stopTx(); setState(ClientCCASessionState.IDLE, false); } if (isProvisional(answer.getResultCodeAvp().getUnsigned32())) { // } else if (isFailure(answer.getResultCodeAvp().getUnsigned32())) { handleFailureMessage((JCreditControlAnswer) answer, (JCreditControlRequest) localEvent.getRequest(), eventType); } deliverCCAnswer((JCreditControlRequest) localEvent.getRequest(), (JCreditControlAnswer) localEvent.getAnswer()); } catch (AvpDataException e) { logger.debug("Failure handling received answer event", e); setState(ClientCCASessionState.IDLE, false); } break; case Tx_TIMER_FIRED: handleTxExpires(localEvent.getRequest().getMessage()); break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; case PENDING_BUFFERED: switch (eventType) { case RECEIVE_EVENT_ANSWER: // We should delete request but since we remove it once we resend it so.... // Its always IDLE here // FIXME: xxxxxxxxxxxxxx to rfc setState(ClientCCASessionState.IDLE, false); buffer = null; listener.doCreditControlAnswer(this, (JCreditControlRequest) localEvent.getRequest(), (JCreditControlAnswer) localEvent.getAnswer()); break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } doEndChecks(); return true; } catch (Exception e) { throw new InternalException(e); } finally { sendAndStateLock.unlock(); } } protected boolean handleEventForSessionBased(StateEvent event) throws InternalException, OverloadException { try { sendAndStateLock.lock(); Event localEvent = (Event) event; Event.Type eventType = (Type) localEvent.getType(); switch (this.state) { // IDLE BLOCK - only SEND_INITIAL_REQUEST event type is permited! case IDLE: switch (eventType) { case SEND_INITIAL_REQUEST: // Action: send initial request, starTx, move to PendingI state // However failure handling is complicated, so we shift state first startTx((JCreditControlRequest) localEvent.getRequest()); setState(ClientCCASessionState.PENDING_INITIAL); try { dispatchEvent(localEvent.getRequest()); } catch (Exception e) { // This handles failure to send in PendingI state in FSM table handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; // //////////////////////// // PENDING_INITIAL BLOCK // // ///////////////////////// case PENDING_INITIAL: AppAnswerEvent answer = (AppAnswerEvent) localEvent.getAnswer(); switch (eventType) { case RECEIVED_INITIAL_ANSWER: try { if (isSuccess(answer.getResultCodeAvp().getUnsigned32())) { stopTx(); setState(ClientCCASessionState.OPEN); } if (isProvisional(answer.getResultCodeAvp() .getUnsigned32())) { } else if (isFailure(answer.getResultCodeAvp().getUnsigned32())) { handleFailureMessage((JCreditControlAnswer) answer, (JCreditControlRequest) localEvent.getRequest(), eventType); } deliverCCAnswer((JCreditControlRequest) localEvent.getRequest(), (JCreditControlAnswer) localEvent.getAnswer()); } catch (AvpDataException e) { // FIXME? logger.debug("Failure handling received initial answer event", e); setState(ClientCCASessionState.IDLE, false); } break; case Tx_TIMER_FIRED: handleTxExpires(localEvent.getRequest().getMessage()); break; case SEND_UPDATE_REQUEST: case SEND_TERMINATE_REQUEST: // we schedule, once in Open state, those messages can fly eventQueue.add(localEvent); break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; // ///////////////// // // OPEN BLOCK // // //////////////// case OPEN: switch (eventType) { case SEND_UPDATE_REQUEST: startTx((JCreditControlRequest) localEvent.getRequest()); setState(ClientCCASessionState.PENDING_UPDATE); try { dispatchEvent(localEvent.getRequest()); } catch (Exception e) { // This handles failure to send in PendingI state in FSM table handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } break; case SEND_TERMINATE_REQUEST: setState(ClientCCASessionState.PENDING_TERMINATION); try { dispatchEvent(localEvent.getRequest()); } catch (Exception e) { handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } break; case RECEIVED_RAR: deliverRAR((ReAuthRequest) localEvent.getRequest()); break; case SEND_RAA: try { dispatchEvent(localEvent.getAnswer()); } catch (Exception e) { handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; // ///////////////////////// // // PendingUpdate BLOCK // // ///////////////////////// case PENDING_UPDATE: answer = (AppAnswerEvent) localEvent.getAnswer(); switch (eventType) { case RECEIVED_UPDATE_ANSWER: try { if (isSuccess(answer.getResultCodeAvp().getUnsigned32())) { stopTx(); setState(ClientCCASessionState.OPEN); } if (isProvisional(answer.getResultCodeAvp().getUnsigned32())) { // } else if (isFailure(answer.getResultCodeAvp().getUnsigned32())) { handleFailureMessage((JCreditControlAnswer) answer, (JCreditControlRequest) localEvent.getRequest(), eventType); } deliverCCAnswer((JCreditControlRequest) localEvent.getRequest(), (JCreditControlAnswer) localEvent.getAnswer()); } catch (AvpDataException e) { // FIXME? logger.debug("Failure handling received update answer event", e); setState(ClientCCASessionState.IDLE, false); } break; case Tx_TIMER_FIRED: handleTxExpires(localEvent.getRequest().getMessage()); break; case SEND_UPDATE_REQUEST: case SEND_TERMINATE_REQUEST: // we schedule, once in Open state, those messages can fly eventQueue.add(localEvent); break; case RECEIVED_RAR: deliverRAR((ReAuthRequest) localEvent.getRequest()); break; case SEND_RAA: try { dispatchEvent(localEvent.getAnswer()); } catch (Exception e) { handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } break; } break; // //////////////////////////// // // PendingTERMIANTE BLOCK // // //////////////////////////// case PENDING_TERMINATION: switch (eventType) { case SEND_UPDATE_REQUEST: try { dispatchEvent(localEvent.getRequest()); // No transition } catch (Exception e) { // This handles failure to send in PendingI state in FSM table // handleSendFailure(e, eventType); } break; case RECEIVED_TERMINATED_ANSWER: setState(ClientCCASessionState.IDLE, false); deliverCCAnswer((JCreditControlRequest) localEvent.getRequest(), (JCreditControlAnswer) localEvent.getAnswer()); default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } break; default: // any other state is bad setState(ClientCCASessionState.IDLE, true); } doEndChecks(); return true; } catch (Exception e) { throw new InternalException(e); } finally { 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 ReAuthAnswerImpl.code: handleEvent(new Event(Event.Type.RECEIVED_RAR, factory.createReAuthRequest(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("Failure processing request", 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 JCreditControlAnswer.code: JCreditControlAnswer _answer=factory.createCreditControlAnswer(answer); extractFHAVPs(null,_answer ); handleEvent(new Event(false, null, _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("Failure processing success message", e); } } public void timeoutExpired(Request request) { if(request.getCommandCode()== JCreditControlAnswer.code) { try { handleSendFailure(null, null, request); } catch (Exception e) { logger.debug("Failure processing timeout message for request", e); } } } protected void startTx(JCreditControlRequest request) { long txTimerValue = context.getDefaultTxTimerValue(); if (txTimerValue < 0) { txTimerValue = TX_TIMER_DEFAULT_VALUE; } stopTx(); logger.debug("Scheduling TX Timer {}", txTimerValue); this.txFuture = scheduler.schedule(new TxTimerTask(this, request), txTimerValue, TimeUnit.SECONDS); } protected void stopTx() { if (this.txFuture != null) { this.txFuture.cancel(true); this.txFuture = null; } } protected void setState(ClientCCASessionState newState) { setState(newState, true); } protected void setState(ClientCCASessionState newState, boolean release) { try { IAppSessionState oldState = state; state = newState; for (StateChangeListener i : stateListeners) { i.stateChanged((Enum) oldState, (Enum) newState); } if (newState == ClientCCASessionState.IDLE) { if (release) { this.release(); } stopTx(); } } catch (Exception e) { if(logger.isDebugEnabled()) { logger.debug("Failure switching to state " + state + " (release=" + release + ")", e); } } } @Override public void release() { this.stopTx(); if(super.isValid()) { super.release(); } if(super.session != null) { super.session.setRequestListener(null); } super.session = null; if(listener != null) { this.removeStateChangeNotification((StateChangeListener) listener); } this.listener = null; this.factory = null; } protected void handleSendFailure(Exception e, Event.Type eventType, Message request) throws Exception { logger.debug("Failed to send message, type: {} message: {}", eventType, request); // FIXME: do we need check for RAR ? try { if (isStateless()) { switch (state) { case PENDING_EVENT: if (gatheredRequestedAction == CHECK_BALANCE || gatheredRequestedAction == PRICE_ENQUIRY) { // #1 setState(ClientCCASessionState.IDLE); context.indicateServiceError(this); } else if (gatheredRequestedAction == DIRECT_DEBITING && getLocalDDFH() == TERMINATE_OR_BUFFER) { // #7 setState(ClientCCASessionState.IDLE, false); buffer = request; buffer.setReTransmitted(true); // context.grantAccessOnDeliverFailure(this, request); } else if (gatheredRequestedAction == REFUND_ACCOUNT) { // #11 setState(ClientCCASessionState.IDLE, false); buffer = request; buffer.setReTransmitted(true); // context.grantAccessOnDeliverFailure(this, request); } else { // FIXME: no transition is mentioned in specs setState(ClientCCASessionState.IDLE, false); } break; case PENDING_BUFFERED: setState(ClientCCASessionState.IDLE, false); //FIXME: baranowb? buffer = null; break; default: //This happens when message timesout logger.warn("Wrong event type ({}) on state {}", eventType, state); break; } } else { // In all cases it moves to idle setState(ClientCCASessionState.IDLE, false); switch (getLocalCCFH()) { case CCFH_CONTINUE: this.context.grantAccessOnDeliverFailure(this, request); break; default: this.context.denyAccessOnDeliverFailure(this, request); break; } } } finally { doEndChecks(); } } protected void handleFailureMessage(JCreditControlAnswer event, JCreditControlRequest request, Event.Type eventType) { try { if (isStateless()) { // Stateless FSM part is a killer ;[ // This has to be present..... switch (state) { case PENDING_EVENT: int resultCode = event.getRequestTypeAVPValue(); if ((resultCode == DIAMETER_END_USER_SERVICE_DENIED || resultCode == USER_UNKNOWN) && txFuture != null) { // #2 setState(ClientCCASessionState.IDLE); context.denyAccessOnFailureMessage(this); deliverCCAnswer(request, event); } else if (resultCode == CREDIT_CONTROL_NOT_APPLICABLE && gatheredRequestedAction == DIRECT_DEBITING) { // #3 setState(ClientCCASessionState.IDLE); context.grantAccessOnFailureMessage(this); deliverCCAnswer(request, event); } else if (temporaryErrorCodes.contains(resultCode)) { if (gatheredRequestedAction == CHECK_BALANCE || gatheredRequestedAction == PRICE_ENQUIRY) { // #1 setState(ClientCCASessionState.IDLE); context.indicateServiceError(this); deliverCCAnswer(request, event); } else if (gatheredRequestedAction == DIRECT_DEBITING && getLocalDDFH() == CONTINUE) { // #4 setState(ClientCCASessionState.IDLE); context.grantAccessOnFailureMessage(this); deliverCCAnswer(request, event); } else if (gatheredRequestedAction == DIRECT_DEBITING && getLocalDDFH() == CONTINUE && txFuture != null) { // #5 setState(ClientCCASessionState.IDLE); context.denyAccessOnFailureMessage(this); deliverCCAnswer(request, event); } else if (gatheredRequestedAction == REFUND_ACCOUNT) { // #12 buffer = request.getMessage(); setState(ClientCCASessionState.IDLE, false); } else { // FIXME setState(ClientCCASessionState.IDLE, false); deliverCCAnswer(request, event); } } else { // we are in fauilure zone isFailure(true} if (gatheredRequestedAction == CHECK_BALANCE || gatheredRequestedAction == PRICE_ENQUIRY) { // #1 setState(ClientCCASessionState.IDLE); context.indicateServiceError(this); deliverCCAnswer(request, event); } else if (gatheredRequestedAction == DIRECT_DEBITING && getLocalDDFH() == CONTINUE) { // #4 setState(ClientCCASessionState.IDLE); context.grantAccessOnFailureMessage(this); deliverCCAnswer(request, event); } else if (gatheredRequestedAction == DIRECT_DEBITING && getLocalDDFH() == CONTINUE && txFuture != null) { // #5 setState(ClientCCASessionState.IDLE); context.denyAccessOnFailureMessage(this); deliverCCAnswer(request, event); } else if (gatheredRequestedAction == REFUND_ACCOUNT) { // #10 buffer = null; setState(ClientCCASessionState.IDLE); context.indicateServiceError(this); deliverCCAnswer(request, event); } else { // FIXME setState(ClientCCASessionState.IDLE, false); deliverCCAnswer(request, event); } } break; case PENDING_BUFFERED: buffer = null; setState(ClientCCASessionState.IDLE, false); break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); } } else { // FIXME CRAP what a pain long responseCode = event.getResultCodeAvp().getUnsigned32(); switch (state) { case PENDING_INITIAL: if (responseCode == CREDIT_CONTROL_NOT_APPLICABLE) { setState(ClientCCASessionState.IDLE, false); context.grantAccessOnFailureMessage(this); } else if ((responseCode == DIAMETER_END_USER_SERVICE_DENIED) || (responseCode == USER_UNKNOWN)) { setState(ClientCCASessionState.IDLE, false); context.denyAccessOnFailureMessage(this); } else { // Temporary errors and others switch (getLocalCCFH()) { case CCFH_CONTINUE: setState(ClientCCASessionState.IDLE, false); context.grantAccessOnFailureMessage(this); break; case CCFH_TERMINATE: case CCFH_RETRY_AND_TERMINATE: setState(ClientCCASessionState.IDLE, false); context.denyAccessOnFailureMessage(this); break; default: logger.warn("Invalid value for CCFH: {}", getLocalCCFH()); break; } } break; case PENDING_UPDATE: if (responseCode == CREDIT_CONTROL_NOT_APPLICABLE) { setState(ClientCCASessionState.IDLE, false); context.grantAccessOnFailureMessage(this); } else if (responseCode == DIAMETER_END_USER_SERVICE_DENIED) { setState(ClientCCASessionState.IDLE, false); context.denyAccessOnFailureMessage(this); } else { // Temporary errors and others switch (getLocalCCFH()) { case CCFH_CONTINUE: setState(ClientCCASessionState.IDLE, false); context.grantAccessOnFailureMessage(this); break; case CCFH_TERMINATE: case CCFH_RETRY_AND_TERMINATE: setState(ClientCCASessionState.IDLE, false); context.denyAccessOnFailureMessage(this); break; default: logger.warn("Invalid value for CCFH: " + getLocalCCFH()); break; } } break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); } } } catch (Exception e) { if(logger.isDebugEnabled()) { logger.debug("Failure handling failure message for Event " + event + " (" + eventType + ") and Request " + request, e); } } } protected void handleTxExpires(Message m) { ClientCCASessionState newState = state; try { if (isStateless()) { if (gatheredRequestedAction == CHECK_BALANCE || gatheredRequestedAction == PRICE_ENQUIRY) { // #1 setState(ClientCCASessionState.IDLE); context.indicateServiceError(this); } else if (gatheredRequestedAction == DIRECT_DEBITING) { setState(ClientCCASessionState.IDLE); context.grantAccessOnTxExpire(this); } else if (gatheredRequestedAction == REFUND_ACCOUNT) { buffer = m; buffer.setReTransmitted(true); setState(ClientCCASessionState.IDLE, false); } } else { switch (state) { case PENDING_INITIAL: switch (getLocalCCFH()) { case CCFH_CONTINUE: case CCFH_RETRY_AND_TERMINATE: newState = ClientCCASessionState.PENDING_INITIAL; context.grantAccessOnTxExpire(this); break; case CCFH_TERMINATE: context.denyAccessOnTxExpire(this); break; default: logger.warn("Invalid value for CCFH: " + getLocalCCFH()); break; } break; case PENDING_UPDATE: switch (getLocalCCFH()) { case CCFH_CONTINUE: case CCFH_RETRY_AND_TERMINATE: newState = ClientCCASessionState.PENDING_UPDATE; context.grantAccessOnTxExpire(this); break; case CCFH_TERMINATE: context.denyAccessOnTxExpire(this); break; default: logger.error("Bad value of CCFH: " + getLocalCCFH()); break; } break; default: logger.error("Unknown state (" + state + ") on txExpire"); break; } } } finally { if (state == newState) { setState(ClientCCASessionState.IDLE, true); } // else // setState(newState, false); } } /** * This makes checks on queue, moves it to proper state if event there is * present on Open state ;] */ protected void doEndChecks() { if (isStateless()) { if (buffer != null) { setState(ClientCCASessionState.PENDING_BUFFERED); try { dispatchEvent(new AppRequestEventImpl(buffer)); } catch (Exception e) { try { handleSendFailure(e, Event.Type.SEND_EVENT_REQUEST, buffer); } catch (Exception e1) { logger.error("Failure handling send failure", e1); } } } } else { if (state == ClientCCASessionState.OPEN && eventQueue.size() > 0) { try { this.handleEvent(eventQueue.remove(0)); } catch (Exception e) { logger.error("Failure handling event", e); } } } } protected void deliverCCAnswer(JCreditControlRequest request, JCreditControlAnswer answer) { try { listener.doCreditControlAnswer(this, request, answer); } catch (Exception e) { logger.warn("Failure delivering CCA Answer", e); } } protected void extractFHAVPs(JCreditControlRequest request, JCreditControlAnswer answer) { if (answer != null) { try { if (answer.isCreditControlFailureHandlingAVPPresent()) { this.gatheredCCFH = answer.getCredidControlFailureHandlingAVPValue(); } } catch (Exception e) { logger.debug("Failure trying to obtain Credit-Control-Failure-Handling AVP value", e); } try { if (answer.isDirectDebitingFailureHandlingAVPPresent()) { this.gatheredDDFH = answer.getDirectDebitingFailureHandlingAVPValue(); } } catch (Exception e) { logger.debug("Failure trying to obtain Direct-Debit-Failure-Handling AVP value", e); } if(!statelessModeSet) { statelessModeSet = true; if(answer.isRequestTypeAVPPresent()) { stateless = (answer.getRequestTypeAVPValue() == 4); } else { //FIXME: send error ? } } } else if (request != null) { try { if (request.isRequestedActionAVPPresent()) { this.gatheredRequestedAction = request.getRequestedActionAVPValue(); } } catch (Exception e) { logger.debug("Failure trying to obtain Request-Action AVP value", e); } if(!statelessModeSet) { statelessModeSet = true; if(request.isRequestTypeAVPPresent()) { stateless = (request.getRequestTypeAVPValue() == 4); } else { //FIXME: send error ? } } } } protected void deliverRAR(ReAuthRequest request) { try { listener.doReAuthRequest(this, request); } catch (Exception e) { logger.debug("Failure delivering RAR", e); } } protected void dispatchEvent(AppEvent event) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { session.send(event.getMessage(), this); // Store last destinmation information } protected boolean isProvisional(long resultCode) { return resultCode >= 1000 && resultCode < 2000; } protected boolean isSuccess(long resultCode) { return resultCode >= 2000 && resultCode < 3000; } protected boolean isFailure(long code) { return (!isProvisional(code) && !isSuccess(code) && ((code >= 3000 && code < 4000) || (code >= 5000 && code < 6000)) && !temporaryErrorCodes.contains(code)); } private class TxTimerTask implements Runnable { private ClientCCASession session = null; private JCreditControlRequest request = null; private TxTimerTask(ClientCCASession session, JCreditControlRequest request) { super(); this.session = session; this.request = request; } public void run() { try { sendAndStateLock.lock(); logger.debug("Fired TX Timer"); txFuture = null; try { context.txTimerExpired(session); } catch (Exception e) { logger.debug("Failure handling TX Timer Expired", e); } handleEvent(new Event(Event.Type.Tx_TIMER_FIRED, request == null ? null : request, null)); } catch (InternalException e) { logger.error("Internal Exception", e); } catch (OverloadException e) { logger.error("Overload Exception", e); } finally { sendAndStateLock.unlock(); } } } }