/servers/diameter/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/ro/ServerRoSessionImpl.java
Java | 541 lines | 351 code | 56 blank | 134 comment | 31 complexity | 91d14f21e5f54916a0a11faf9ff2987c MD5 | raw file
Possible License(s): LGPL-3.0, GPL-3.0, LGPL-2.1, GPL-2.0, CC-BY-SA-3.0, CC0-1.0, Apache-2.0, BSD-3-Clause
- /*
- * JBoss, Home of Professional Open Source
- * Copyright 2006, Red Hat, Inc. and individual contributors
- * by the @authors tag. See the copyright.txt in the distribution for a
- * full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-
- package org.jdiameter.server.impl.app.ro;
-
- import java.io.Serializable;
- 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.ResultCode;
- import org.jdiameter.api.RouteException;
- import org.jdiameter.api.app.AppAnswerEvent;
- import org.jdiameter.api.app.AppEvent;
- import org.jdiameter.api.app.AppRequestEvent;
- import org.jdiameter.api.app.AppSession;
- import org.jdiameter.api.app.StateChangeListener;
- import org.jdiameter.api.app.StateEvent;
- import org.jdiameter.api.auth.events.ReAuthRequest;
- import org.jdiameter.api.ro.ServerRoSession;
- import org.jdiameter.api.ro.ServerRoSessionListener;
- import org.jdiameter.api.ro.events.RoCreditControlAnswer;
- import org.jdiameter.api.ro.events.RoCreditControlRequest;
- import org.jdiameter.client.api.ISessionFactory;
- import org.jdiameter.common.api.app.IAppSessionState;
- import org.jdiameter.common.api.app.ro.IRoMessageFactory;
- import org.jdiameter.common.api.app.ro.IServerRoSessionContext;
- import org.jdiameter.common.api.app.ro.ServerRoSessionState;
- import org.jdiameter.common.impl.app.AppAnswerEventImpl;
- import org.jdiameter.common.impl.app.AppRequestEventImpl;
- import org.jdiameter.common.impl.app.auth.ReAuthAnswerImpl;
- import org.jdiameter.common.impl.app.auth.ReAuthRequestImpl;
- import org.jdiameter.common.impl.app.ro.AppRoSessionImpl;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- /**
- * Ro Application Server session implementation
- *
- * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a>
- * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a>
- */
- public class ServerRoSessionImpl extends AppRoSessionImpl implements ServerRoSession, NetworkReqListener, EventListener<Request, Answer> {
-
- private static final Logger logger = LoggerFactory.getLogger(ServerRoSessionImpl.class);
-
- // Session State Handling ---------------------------------------------------
- protected Lock sendAndStateLock = new ReentrantLock();
-
- // Factories and Listeners --------------------------------------------------
- protected transient IRoMessageFactory factory = null;
- protected transient IServerRoSessionContext context = null;
- protected transient ServerRoSessionListener listener = null;
-
- protected static final String TCC_TIMER_NAME = "TCC_RoSERVER_TIMER";
-
- protected long[] authAppIds = new long[]{4};
- //protected String originHost, originRealm;
-
- protected IServerRoSessionData sessionData;
-
- public ServerRoSessionImpl(IServerRoSessionData sessionData, IRoMessageFactory fct, ISessionFactory sf, ServerRoSessionListener lst, IServerRoSessionContext ctx, StateChangeListener<AppSession> stLst) {
- super(sf,sessionData);
- if(sessionData == null) {
- throw new IllegalArgumentException("SessionData can not be null");
- }
- 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");
- }
- this.sessionData = sessionData;
- this.context = ctx;
-
- this.authAppIds = fct.getApplicationIds();
- this.listener = lst;
- this.factory = fct;
- super.addStateChangeNotification(stLst);
- }
-
- public void sendCreditControlAnswer(RoCreditControlAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
- 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 this.sessionData.isStateless();
- }
-
- @SuppressWarnings("unchecked")
- public <E> E getState(Class<E> stateType) {
- return stateType == ServerRoSessionState.class ? (E) this.sessionData.getServerRoSessionState() : null;
- }
-
- public boolean handleEvent(StateEvent event) throws InternalException, OverloadException {
- ServerRoSessionState newState = null;
- ServerRoSessionState state = sessionData.getServerRoSessionState();
- 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 awkward, 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, (RoCreditControlRequest)localEvent.getRequest());
- break;
-
- case RECEIVED_EVENT:
- // Current State: IDLE
- // Event: CC event request received and successfully processed
- // Action: Send CC event answer
- // New State: IDLE
- listener.doCreditControlRequest(this, (RoCreditControlRequest)localEvent.getRequest());
- break;
-
- case SENT_EVENT_RESPONSE:
- // Current State: IDLE
- // Event: CC event request received and successfully processed
- // Action: Send CC event answer
- // New State: IDLE
-
- // Current State: IDLE
- // Event: CC event request received but not successfully processed
- // Action: Send CC event answer with Result-Code != SUCCESS
- // New State: IDLE
- newState = ServerRoSessionState.IDLE;
- dispatchEvent(localEvent.getAnswer());
- setState(newState);
- break;
-
- case SENT_INITIAL_RESPONSE:
- RoCreditControlAnswer answer = (RoCreditControlAnswer) localEvent.getAnswer();
- try {
- long resultCode = answer.getResultCodeAvp().getUnsigned32();
- // Current State: IDLE
- // Event: CC initial request received and successfully processed
- // Action: Send CC initial answer, reserve units, start Tcc
- // New State: OPEN
- if(isSuccess(resultCode)) {
- startTcc(answer.getValidityTimeAvp());
- newState = ServerRoSessionState.OPEN;
- }
- // Current State: IDLE
- // Event: CC initial request received but not successfully processed
- // Action: Send CC initial answer with Result-Code != SUCCESS
- // New State: IDLE
- else {
- newState = ServerRoSessionState.IDLE;
- }
- dispatchEvent(localEvent.getAnswer());
- setState(newState);
- }
- catch (AvpDataException e) {
- throw new InternalException(e);
- }
- break;
-
- case RECEIVED_UPDATE:
- case RECEIVED_TERMINATE:
- Answer errorAnswer = ((Request) localEvent.getRequest().getMessage()).createAnswer(ResultCode.UNKNOWN_SESSION_ID);
- session.send(errorAnswer);
- logger.debug("Received an UPDATE or TERMINATE for a new session. Answering with 5002 (UNKNOWN_SESSION_ID) and terminating session.");
- // and let it throw exception anyway ...
- default:
- throw new InternalException("Wrong state: " + ServerRoSessionState.IDLE + " one event: " + eventType + " " + localEvent.getRequest() + " " + localEvent.getAnswer());
- }
-
- case OPEN:
- switch(eventType)
- {
- /* This should not happen, it should be silently discarded, right?
- case RECEIVED_INITIAL:
- // only for rtr
- if(((RoRequest)localEvent.getRequest()).getMessage().isReTransmitted()) {
- listener.doCreditControlRequest(this, (RoRequest)localEvent.getRequest());
- }
- else {
- //do nothing?
- }
- break;
- */
- case RECEIVED_UPDATE:
- listener.doCreditControlRequest(this, (RoCreditControlRequest)localEvent.getRequest());
- break;
-
- case SENT_UPDATE_RESPONSE:
- RoCreditControlAnswer answer = (RoCreditControlAnswer) localEvent.getAnswer();
- try {
- if(isSuccess(answer.getResultCodeAvp().getUnsigned32())) {
- // Current State: OPEN
- // Event: CC update request received and successfully processed
- // Action: Send CC update answer, debit used units, reserve new units, restart Tcc
- // New State: OPEN
- startTcc(answer.getValidityTimeAvp());
- }
- else {
- // Current State: OPEN
- // Event: CC update request received but not successfully processed
- // Action: Send CC update answer with Result-Code != SUCCESS, debit used units
- // New State: IDLE
-
- // It's a failure, we wait for Tcc to fire -- FIXME: Alexandre: Should we?
- }
- }
- catch (AvpDataException e) {
- throw new InternalException(e);
- }
- dispatchEvent(localEvent.getAnswer());
- break;
- case RECEIVED_TERMINATE:
- listener.doCreditControlRequest(this, (RoCreditControlRequest)localEvent.getRequest());
- break;
- case SENT_TERMINATE_RESPONSE:
- answer = (RoCreditControlAnswer) localEvent.getAnswer();
- try {
- // Current State: OPEN
- // Event: CC termination request received and successfully processed
- // Action: Send CC termination answer, Stop Tcc, debit used units
- // New State: IDLE
- if(isSuccess(answer.getResultCodeAvp().getUnsigned32())) {
- stopTcc(false);
- }
- else {
- // Current State: OPEN
- // Event: CC termination request received but not successfully processed
- // Action: Send CC termination answer with Result-Code != SUCCESS, debit used units
- // New State: IDLE
-
- // It's a failure, we wait for Tcc to fire -- FIXME: Alexandre: Should we?
- }
- }
- catch (AvpDataException e) {
- throw new InternalException(e);
- }
- newState = ServerRoSessionState.IDLE;
- dispatchEvent(localEvent.getAnswer());
- setState(newState);
- break;
-
- case RECEIVED_RAA:
- listener.doReAuthAnswer(this, new ReAuthRequestImpl(localEvent.getRequest().getMessage()), new ReAuthAnswerImpl((Answer)localEvent.getAnswer().getMessage()));
- break;
- case SENT_RAR:
- dispatchEvent(localEvent.getRequest());
- break;
- }
- }
- return true;
- }
- catch (Exception e) {
- throw new InternalException(e);
- }
- finally {
- sendAndStateLock.unlock();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see org.jdiameter.common.impl.app.AppSessionImpl#isReplicable()
- */
- @Override
- public boolean isReplicable() {
- return true;
- }
-
- private class TccScheduledTask implements Runnable {
- ServerRoSession session = null;
-
- private TccScheduledTask(ServerRoSession session) {
- super();
- this.session = session;
- }
-
- public void run() {
- // Current State: OPEN
- // Event: Session supervision timer Tcc expired
- // Action: Release reserved units
- // New State: IDLE
-
- try {
- sendAndStateLock.lock();
-
- if(context != null) {
- context.sessionSupervisionTimerExpired(session);
- }
- }
- finally {
- try{
- sessionData.setTccTimerId(null);
- setState(ServerRoSessionState.IDLE);
- }
- catch (Exception e) {
- logger.error("",e);
- }
- sendAndStateLock.unlock();
- }
- }
- }
-
- public Answer processRequest(Request request) {
- RequestDelivery rd = new RequestDelivery();
- //rd.session = (ServerRoSession) LocalDataSource.INSTANCE.getSession(request.getSessionId());
- rd.session = this;
- rd.request = request;
- super.scheduler.execute(rd);
- return null;
- }
-
- public void receivedSuccessMessage(Request request, Answer answer) {
- AnswerDelivery rd = new AnswerDelivery();
- rd.session = this;
- rd.request = request;
- rd.answer = answer;
- super.scheduler.execute(rd);
- }
-
- public void timeoutExpired(Request request) {
- context.timeoutExpired(request);
- //FIXME: Should we release ?
- }
-
- private void startTcc(Avp validityAvp) {
- // There is no Validity-Time
- //long tccTimeout;
- //
- //if(validityAvp != null) {
- // try {
- // tccTimeout = 2 * validityAvp.getUnsigned32();
- // }
- // catch (AvpDataException e) {
- // logger.debug("Unable to retrieve Validity-Time AVP value, using default.", e);
- // tccTimeout = 2 * context.getDefaultValidityTime();
- // }
- //}
- //else {
- // tccTimeout = 2 * context.getDefaultValidityTime();
- //}
- //
- //if(tccTimerId != null) {
- // stopTcc(true);
- // //tccFuture = super.scheduler.schedule(new TccScheduledTask(this), defaultValue, TimeUnit.SECONDS);
- // tccTimerId = super.timerFacility.schedule(this.sessionId, TCC_TIMER_NAME, tccTimeout * 1000);
- // // FIXME: this accepts Future!
- // context.sessionSupervisionTimerReStarted(this, null);
- //}
- //else {
- // //tccFuture = super.scheduler.schedule(new TccScheduledTask(this), defaultValue, TimeUnit.SECONDS);
- // tccTimerId = super.timerFacility.schedule(this.sessionId, TCC_TIMER_NAME, tccTimeout * 1000);
- // //FIXME: this accepts Future!
- // context.sessionSupervisionTimerStarted(this, null);
- //}
- //super.sessionDataSource.updateSession(this);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see org.jdiameter.common.impl.app.AppSessionImpl#onTimer(java.lang.String)
- */
- @Override
- public void onTimer(String timerName) {
- if (timerName.equals(TCC_TIMER_NAME)) {
- new TccScheduledTask(this).run();
- }
- }
-
- private void stopTcc(boolean willRestart) {
- Serializable tccTimerId = sessionData.getTccTimerId();
- if (tccTimerId != null) {
- // tccFuture.cancel(false);
- super.timerFacility.cancel(tccTimerId);
- // ScheduledFuture f = tccFuture;
- sessionData.setTccTimerId(null);
- if (!willRestart) {
- context.sessionSupervisionTimerStopped(this, null);
- }
- }
- }
-
- protected boolean isProvisional(long resultCode) {
- return resultCode >= 1000 && resultCode < 2000;
- }
-
- protected boolean isSuccess(long resultCode) {
- return resultCode >= 2000 && resultCode < 3000;
- }
-
- protected void setState(ServerRoSessionState newState) {
- setState(newState, true);
- }
-
- @SuppressWarnings("unchecked")
- protected void setState(ServerRoSessionState newState, boolean release) {
- IAppSessionState oldState = sessionData.getServerRoSessionState();
- sessionData.setServerRoSessionState(newState);
-
- for (StateChangeListener i : stateListeners) {
- i.stateChanged(this, (Enum) oldState, (Enum) newState);
- }
- if (newState == ServerRoSessionState.IDLE) {
- stopTcc(false);
- if (release) {
- this.release();
- }
- }
- }
-
- @Override
- public void release() {
- if (isValid()) {
- try {
- this.sendAndStateLock.lock();
- this.stopTcc(false);
- super.release();
- }
- catch (Exception e) {
- logger.debug("Failed to release session", e);
- }
- finally {
- sendAndStateLock.unlock();
- }
- }
- else {
- logger.debug("Trying to release an already invalid session, with Session ID '{}'", getSessionId());
- }
- }
-
- protected void send(Event.Type type, AppRequestEvent request, AppAnswerEvent answer) throws InternalException {
- try {
- 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);
-
- }
- catch(Exception e) {
- //throw new InternalException(e);
- logger.debug("Failure trying to dispatch event", e);
- }
- }
-
- private class RequestDelivery implements Runnable {
- ServerRoSession session;
- Request request;
-
- public void run() {
- try {
- switch (request.getCommandCode()) {
- case RoCreditControlAnswer.code:
- handleEvent(new Event(true, factory.createCreditControlRequest(request), null));
- break;
-
- default:
- listener.doOtherEvent(session, new AppRequestEventImpl(request), null);
- break;
- }
- }
- catch (Exception e) {
- logger.debug("Failed to process request message", e);
- }
- }
- }
-
- private class AnswerDelivery implements Runnable {
- ServerRoSession session;
- Answer answer;
- Request request;
-
- public void run() {
- 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;
- default:
- listener.doOtherEvent(session, new AppRequestEventImpl(request), new AppAnswerEventImpl(answer));
- break;
- }
- }
- catch (Exception e) {
- logger.debug("Failed to process success message", e);
- }
- }
- }
-
- }