/* * Copyright (c) 2006 jDiameter. * https://jdiameter.dev.java.net/ * * License: Lesser General Public License (LGPL) * * e-mail: erick.svenson@yahoo.com * */ package org.jdiameter.client.impl.app.acc; import static org.jdiameter.api.Avp.ACCOUNTING_REALTIME_REQUIRED; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.IDLE; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.OPEN; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.PENDING_BUFFERED; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.PENDING_CLOSE; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.PENDING_EVENT; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.PENDING_INTERIM; import static org.jdiameter.common.api.app.acc.ClientAccSessionState.PENDING_START; import java.util.concurrent.TimeUnit; import org.jdiameter.api.Answer; import org.jdiameter.api.ApplicationId; import org.jdiameter.api.Avp; import org.jdiameter.api.AvpDataException; import org.jdiameter.api.EventListener; import org.jdiameter.api.InternalException; import org.jdiameter.api.Message; 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.ClientAccSession; import org.jdiameter.api.acc.ClientAccSessionListener; import org.jdiameter.api.acc.events.AccountAnswer; import org.jdiameter.api.acc.events.AccountRequest; import org.jdiameter.api.app.AppEvent; import org.jdiameter.api.app.StateChangeListener; import org.jdiameter.api.app.StateEvent; import org.jdiameter.common.api.app.IAppSessionState; import org.jdiameter.common.api.app.acc.ClientAccSessionState; import org.jdiameter.common.api.app.acc.IClientAccActionContext; import org.jdiameter.common.impl.app.acc.AccountRequestImpl; import org.jdiameter.common.impl.app.acc.AppAccSessionImpl; /** * Client Accounting session implementation */ public class ClientAccSessionImpl extends AppAccSessionImpl implements EventListener<Request, Answer>, ClientAccSession { private static final long serialVersionUID = 1L; public static final int DELIVER_AND_GRANT = 1; public static final int GRANT_AND_LOSE = 3; protected ClientAccSessionState state = IDLE; protected String destHost, destRealm; protected IClientAccActionContext context; protected AppEvent buffer; protected ClientAccSessionListener listener; public ClientAccSessionImpl(SessionFactory sf, ClientAccSessionListener lst, ApplicationId app) { if (lst == null) throw new IllegalArgumentException("Listener can not be null"); if (app == null) throw new IllegalArgumentException("ApplicationId can not be null"); appId = app; listener = lst; if (listener instanceof IClientAccActionContext) { context = (IClientAccActionContext) listener; } try { session = sf.getNewSession(); session.setRequestListener(this); } catch (InternalException e) { throw new IllegalArgumentException(e); } } public ClientAccSessionImpl(SessionFactory sf, String sessionId, ClientAccSessionListener lst, ApplicationId app) { this(sf, lst, app); try { session = sf.getNewSession(sessionId); session.setRequestListener(this); } catch (InternalException e) { throw new IllegalArgumentException(e); } } public void sendAccountRequest(AccountRequest accountRequest) throws InternalException, IllegalStateException, RouteException, OverloadException { try { sendAndStateLock.lock(); handleEvent(new Event(accountRequest)); try { session.send(accountRequest.getMessage(), this); // Store last destinmation information destRealm = accountRequest.getMessage().getAvps().getAvp(Avp.DESTINATION_REALM).getOctetString(); destHost = accountRequest.getMessage().getAvps().getAvp(Avp.DESTINATION_HOST).getOctetString(); } catch (Throwable t) { handleEvent(new Event(Event.Type.FAILED_SEND_RECORD, accountRequest)); } } catch (Exception exc) { throw new InternalException(exc); } finally { sendAndStateLock.unlock(); } } protected synchronized void storeToBuffer(AccountRequest accountRequest) { buffer = accountRequest; } protected synchronized boolean checkBufferSpace() { return buffer == null; } protected void setState(IAppSessionState newState) { IAppSessionState oldState = state; state = (ClientAccSessionState) newState; for (StateChangeListener i : stateListeners) { i.stateChanged( (Enum) oldState, (Enum) newState); } } public boolean isStateless() { return false; } public boolean handleEvent(StateEvent event) throws InternalException, OverloadException { ClientAccSessionState oldState = state; try { switch (state) { // Idle ========== case IDLE: { switch ((Event.Type) event.getType()) { // Client or device requests access case SEND_START_RECORD: setState(PENDING_START); break; // Client or device requests a one-time service case SEND_EVENT_RECORD: setState(PENDING_EVENT); break; // Send buffered message action in other section of this method see below default: throw new IllegalStateException("Current state " + state + " action " + event.getType()); } break; } // PendingS ========== case PENDING_START: { switch ((Event.Type) event.getType()) { case FAILED_SEND_RECORD: AccountRequest request = (AccountRequest) event.getData(); Avp accRtReq = request.getMessage().getAvps().getAvp(ACCOUNTING_REALTIME_REQUIRED); // Failure to send and buffer space available and realtime not equal to DELIVER_AND_GRANT if (checkBufferSpace() && accRtReq != null && accRtReq.getInteger32() != DELIVER_AND_GRANT) { storeToBuffer(request); setState(OPEN); } else { // Failure to send and no buffer space available and realtime equal to GRANT_AND_LOSE if (!checkBufferSpace() && accRtReq != null && accRtReq.getInteger32() == GRANT_AND_LOSE) { setState(OPEN); } else { // Failure to send and no buffer space available and realtime not equal to GRANT_AND_LOSE if (!checkBufferSpace() && accRtReq != null && accRtReq.getInteger32() != GRANT_AND_LOSE) { sendAndStateLock.lock(); if (context != null) { Request str = createSessionTermRequest(); context.disconnectUserOrDev(str); session.send(str, this); } setState(IDLE); sendAndStateLock.unlock(); } } } break; // Successful accounting start answer received case RECEIVED_RECORD: processInterimIntervalAvp(event); setState(OPEN); break; case FAILED_RECEIVE_RECORD: try { AccountAnswer answer = (AccountAnswer) event.getData(); accRtReq = answer.getMessage().getAvps().getAvp(ACCOUNTING_REALTIME_REQUIRED); // Failed accounting start answer received and realtime equal to GRANT_AND_LOSE if (accRtReq != null && accRtReq.getInteger32() == GRANT_AND_LOSE) { setState(OPEN); } else { // Failed accounting start answer received and realtime not equal to GRANT_AND_LOSE if (accRtReq != null && accRtReq.getInteger32() != GRANT_AND_LOSE) { sendAndStateLock.lock(); if (context != null) { Request str = createSessionTermRequest(); context.disconnectUserOrDev(str); session.send(str, this); } setState(IDLE); sendAndStateLock.unlock(); } } } catch (Exception e) { logger.debug("Can not processed answer", e); setState(IDLE); } break; // User service terminated case SEND_STOP_RECORD: if (context != null) { Request str = createSessionTermRequest(); context.disconnectUserOrDev(str); storeToBuffer(createAccountRequest(str)); } break; } break; } // OPEN ========== case OPEN: { switch ((Event.Type) event.getType()) { // User service terminated case SEND_STOP_RECORD: setState(PENDING_CLOSE); break; // Create timer for "Interim interval elapses" event case RECEIVED_RECORD: processInterimIntervalAvp(event); break; } } break; // PendingI ========== case PENDING_INTERIM: { switch ((Event.Type) event.getType()) { // Successful accounting interim answer received case RECEIVED_RECORD: processInterimIntervalAvp(event); setState(OPEN); break; case FAILED_SEND_RECORD: AccountRequest request = (AccountRequest) event.getData(); Avp accRtReq = ((Message) event.getData()).getAvps().getAvp(ACCOUNTING_REALTIME_REQUIRED); // Failure to send and realtime not equal to DELIVER_AND_GRANT if (checkBufferSpace() && accRtReq != null && accRtReq.getInteger32() != DELIVER_AND_GRANT) { storeToBuffer(request); setState(OPEN); } else { // Failure to send and no buffer space available and realtime equal to GRANT_AND_LOSE if (!checkBufferSpace() && accRtReq != null && accRtReq.getInteger32() == GRANT_AND_LOSE) { setState(OPEN); } else { // Failure to send and no buffer space available and realtime not equal to GRANT_AND_LOSE if (!checkBufferSpace() && accRtReq != null && accRtReq.getInteger32() != GRANT_AND_LOSE) { sendAndStateLock.lock(); if (context != null) { Request str = createSessionTermRequest(); context.disconnectUserOrDev(str); session.send(str, this); } setState(IDLE); sendAndStateLock.unlock(); } } } break; case FAILED_RECEIVE_RECORD: try { AccountAnswer answer = (AccountAnswer) event.getData(); accRtReq = answer.getMessage().getAvps().getAvp(ACCOUNTING_REALTIME_REQUIRED); // Failed accounting interim answer received and realtime equal to GRANT_AND_LOSE if (accRtReq != null && accRtReq.getInteger32() == GRANT_AND_LOSE) { setState(OPEN); } else { // Failed accounting interim answer received and realtime not equal to GRANT_AND_LOSE if (accRtReq != null && accRtReq.getInteger32() != GRANT_AND_LOSE) { sendAndStateLock.lock(); if (context != null) { Request str = createSessionTermRequest(); context.disconnectUserOrDev(str); session.send(str, this); } setState(IDLE); sendAndStateLock.unlock(); } } } catch (Exception e) { logger.debug(e.getMessage(), e); setState(IDLE); } break; // User service terminated case SEND_STOP_RECORD: if (context != null) { Request str = createSessionTermRequest(); context.disconnectUserOrDev(str); storeToBuffer(createAccountRequest(str)); } break; } break; } // PendingE ========== case PENDING_EVENT: { switch ((Event.Type) event.getType()) { // Successful accounting event answer received case RECEIVED_RECORD: setState(IDLE); break; case FAILED_SEND_RECORD: if (checkBufferSpace()) { // Failure to send and buffer space available AccountRequest data = (AccountRequest) event.getData(); storeToBuffer(data); } setState(IDLE); break; // Failed accounting event answer received case FAILED_RECEIVE_RECORD: setState(IDLE); break; } break; } // PendingB ========== case PENDING_BUFFERED: { switch ((Event.Type) event.getType()) { // Successful accounting answer received case RECEIVED_RECORD: synchronized (this) { storeToBuffer(null); } setState(IDLE); break; // Failure to send case FAILED_SEND_RECORD: setState(IDLE); break; // Failed accounting answer received case FAILED_RECEIVE_RECORD: synchronized (this) { storeToBuffer(null); } setState(IDLE); break; } break; } // PendingL ========== case PENDING_CLOSE: { switch ((Event.Type) event.getType()) { // Successful accounting stop answer received case RECEIVED_RECORD: setState(IDLE); break; case FAILED_SEND_RECORD: if (checkBufferSpace()) { // Failure to send and buffer space available AccountRequest data = (AccountRequest) event.getData(); storeToBuffer(data); } setState(IDLE); break; // Failed accounting stop answer received case FAILED_RECEIVE_RECORD: setState(IDLE); break; } break; } } // Post processing if (oldState != state) { switch (state) { // IDLE =========== case IDLE: { // Records in storage try { synchronized (this) { if (buffer != null) { session.send(buffer.getMessage(), this); setState(PENDING_BUFFERED); } } } catch (Exception e) { logger.debug("can not send buffered message", e); synchronized (this) { if (!context.failedSendRecord((Request) buffer.getMessage())) { storeToBuffer(null); } if (!IDLE.equals(IDLE)) { setState(IDLE); } } } } } } } catch (Throwable t) { throw new InternalException(t); } return true; } protected void processInterimIntervalAvp(StateEvent event) throws InternalException { Avp interval = ((AppEvent) event.getData()).getMessage().getAvps().getAvp(Avp.ACCT_INTERIM_INTERVAL); if (interval != null) { // create timer try { long v = interval.getUnsigned32(); if (v != 0) { scheduler.schedule( new Runnable() { public void run() { if (context != null) { try { Request interimRecord = createInterimRecord(); context.interimIntervalElapses(interimRecord); sendAndStateLock.lock(); session.send(interimRecord, ClientAccSessionImpl.this); setState(PENDING_INTERIM); } catch (Exception e) { logger.debug(e.getMessage(), e); } finally { sendAndStateLock.unlock(); } } } }, v, TimeUnit.SECONDS ); } } catch (AvpDataException e) { logger.debug(e.getMessage(), e); } } } public <E> E getState(Class<E> eClass) { return eClass == ClientAccSessionState.class ? (E) state : null; } public void receivedSuccessMessage(Request request, Answer answer) { if (request.getCommandCode() == AccountRequestImpl.code) { try { sendAndStateLock.lock(); handleEvent(new Event(createAccountAnswer(answer))); } catch (Exception e) { logger.debug(e.getMessage(), e); } finally { sendAndStateLock.unlock(); } try { listener.doAccAnswerEvent(this, createAccountRequest(request), createAccountAnswer(answer)); } catch (Exception e) { logger.debug(e.getMessage(), e); } } else { try { listener.doOtherEvent(this, createAccountRequest(request), createAccountAnswer(answer)); } catch (Exception e) { logger.debug(e.getMessage(), e); } } } public void timeoutExpired(Request request) { try { handleEvent(new Event(Event.Type.FAILED_RECEIVE_RECORD, createAccountRequest(request))); } catch (Exception e) { logger.debug(e.getMessage(), e); } } public Answer processRequest(Request request) { if (request.getCommandCode() == AccountRequestImpl.code) { try { listener.doAccAnswerEvent(this, createAccountRequest(request), null); } catch (Exception e) { logger.debug(e.getMessage(), e); } } else { try { listener.doOtherEvent(this, createAccountRequest(request), null); } catch (Exception e) { logger.debug(e.getMessage(), e); } } return null; } protected Request createInterimRecord() { Request interimRecord = session.createRequest(AccountRequestImpl.code, appId, destRealm, destHost); interimRecord.getAvps().addAvp(Avp.ACC_RECORD_TYPE, 3); return interimRecord; } protected Request createSessionTermRequest() { return session.createRequest(Message.SESSION_TERMINATION_REQUEST, appId, destRealm, destHost); } }