/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2016, TeleStax Inc. and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* JBoss, Home of Professional Open Source
* Copyright 2007-2011, 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.client.impl.app.sh;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jdiameter.api.Answer;
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.app.AppEvent;
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.PushNotificationRequest;
import org.jdiameter.api.sh.events.SubscribeNotificationsRequest;
import org.jdiameter.api.sh.events.UserDataRequest;
import org.jdiameter.client.api.ISessionFactory;
import org.jdiameter.common.api.app.sh.IShMessageFactory;
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.PushNotificationRequestImpl;
import org.jdiameter.common.impl.app.sh.ShSession;
import org.jdiameter.common.impl.app.sh.SubscribeNotificationsAnswerImpl;
import org.jdiameter.common.impl.app.sh.UserDataAnswerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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>
*
* @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 Logger logger = LoggerFactory.getLogger(ShClientSessionImpl.class);
// Session State Handling ---------------------------------------------------
protected Lock sendAndStateLock = new ReentrantLock();
// Factories and Listeners --------------------------------------------------
protected transient IShMessageFactory factory = null;
protected transient ClientShSessionListener listener;
protected IShClientSessionData sessionData;
public ShClientSessionImpl(IShClientSessionData sessionData, IShMessageFactory fct, ISessionFactory sf, ClientShSessionListener lst) {
super(sf, sessionData);
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");
}
this.listener = lst;
this.factory = fct;
this.sessionData = sessionData;
}
@Override
public Answer processRequest(Request request) {
RequestDelivery rd = new RequestDelivery();
rd.session = this;
rd.request = request;
super.scheduler.execute(rd);
return null;
}
@Override
@SuppressWarnings("unchecked")
public <E> E getState(Class<E> stateType) {
return null;
}
@Override
public boolean handleEvent(StateEvent event) throws InternalException, OverloadException {
try {
sendAndStateLock.lock();
Event localEvent = (Event) event;
// Do the delivery
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, (ProfileUpdateRequest) localEvent.getRequest(),
new ProfileUpdateAnswerImpl((Answer) localEvent.getAnswer().getMessage()));
break;
case RECEIVE_USER_DATA_ANSWER:
listener.doUserDataAnswerEvent(this, (UserDataRequest) localEvent.getRequest(), new UserDataAnswerImpl((Answer) localEvent.getAnswer().getMessage()));
break;
case RECEIVE_SUBSCRIBE_NOTIFICATIONS_ANSWER:
listener.doSubscribeNotificationsAnswerEvent(this, (SubscribeNotificationsRequest) localEvent.getRequest(),
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:
Message m = null;
Object data = event.getData();
m = data instanceof AppEvent ? ((AppEvent) data).getMessage() : (Message) event.getData();
session.send(m, this);
break;
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 {
sendAndStateLock.unlock();
}
return true;
}
@Override
public void sendProfileUpdateRequest(ProfileUpdateRequest request)
throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_PROFILE_UPDATE_REQUEST, request, null);
}
@Override
public void sendPushNotificationAnswer(PushNotificationAnswer answer)
throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_PUSH_NOTIFICATION_ANSWER, null, answer);
}
@Override
public void sendSubscribeNotificationsRequest(SubscribeNotificationsRequest request)
throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_SUBSCRIBE_NOTIFICATIONS_REQUEST, request, null);
}
@Override
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));
}
}
catch (Exception e) {
throw new InternalException(e);
}
finally {
sendAndStateLock.unlock();
}
}
@Override
public void receivedSuccessMessage(Request request, Answer answer) {
AnswerDelivery rd = new AnswerDelivery();
rd.session = this;
rd.request = request;
rd.answer = answer;
super.scheduler.execute(rd);
}
@Override
public void timeoutExpired(Request request) {
try {
if (request.getApplicationId() == factory.getApplicationId()) {
if (request.getCommandCode() == ProfileUpdateRequest.code) {
handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, factory.createProfileUpdateRequest(request), null));
return;
}
else if (request.getCommandCode() == UserDataRequest.code) {
handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, factory.createUserDataRequest(request), null));
return;
}
else if (request.getCommandCode() == SubscribeNotificationsRequest.code) {
handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, factory.createSubscribeNotificationsRequest(request), null));
return;
}
}
}
catch (Exception e) {
logger.debug("Failed to process timeout message", e);
}
}
@Override
public void release() {
if (isValid()) {
try {
sendAndStateLock.lock();
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());
}
}
@Override
public boolean isStateless() {
return true;
}
/* (non-Javadoc)
* @see org.jdiameter.common.impl.app.AppSessionImpl#isReplicable()
*/
@Override
public boolean isReplicable() {
return true;
}
@Override
public void onTimer(String timerName) {
if (timerName.equals(IDLE_SESSION_TIMER_NAME)) {
checkIdleAppSession();
}
else {
logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId());
}
}
private class RequestDelivery implements Runnable {
ClientShSession session;
Request request;
@Override
public void run() {
try {
if (request.getApplicationId() == factory.getApplicationId()) {
if (request.getCommandCode() == PushNotificationRequest.code) {
handleEvent(new Event(Event.Type.RECEIVE_PUSH_NOTIFICATION_REQUEST, factory.createPushNotificationRequest(request), null));
return;
}
}
listener.doOtherEvent(session, new AppRequestEventImpl(request), null);
}
catch (Exception e) {
logger.debug("Failed to process request {}", request, e);
}
}
}
private class AnswerDelivery implements Runnable {
ClientShSession session;
Answer answer;
Request request;
@Override
public void run() {
try {
sendAndStateLock.lock();
if (request.getApplicationId() == factory.getApplicationId()) {
if (request.getCommandCode() == ProfileUpdateRequest.code) {
handleEvent(
new Event(Event.Type.RECEIVE_PROFILE_UPDATE_ANSWER, factory.createProfileUpdateRequest(request), factory.createProfileUpdateAnswer(answer)));
return;
}
else if (request.getCommandCode() == UserDataRequest.code) {
handleEvent(new Event(Event.Type.RECEIVE_USER_DATA_ANSWER, factory.createUserDataRequest(request), factory.createUserDataAnswer(answer)));
return;
}
else if (request.getCommandCode() == SubscribeNotificationsRequest.code) {
handleEvent(new Event(Event.Type.RECEIVE_SUBSCRIBE_NOTIFICATIONS_ANSWER, factory.createSubscribeNotificationsRequest(request), factory
.createSubscribeNotificationsAnswer(answer)));
return;
}
}
listener.doOtherEvent(session, new AppRequestEventImpl(request), new AppAnswerEventImpl(answer));
}
catch (Exception e) {
logger.debug("Failed to process success message", e);
}
finally {
sendAndStateLock.unlock();
}
}
}
}