/*
* JBoss, Home of Professional Open Source
* Copyright 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.restcomm.media.control.mgcp.connection;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.restcomm.media.component.audio.AudioComponent;
import org.restcomm.media.component.oob.OOBComponent;
import org.restcomm.media.scheduler.PriorityQueueScheduler;
import org.restcomm.media.scheduler.Task;
import org.restcomm.media.spi.Connection;
import org.restcomm.media.spi.ConnectionEvent;
import org.restcomm.media.spi.ConnectionFailureListener;
import org.restcomm.media.spi.ConnectionListener;
import org.restcomm.media.spi.ConnectionMode;
import org.restcomm.media.spi.ConnectionState;
import org.restcomm.media.spi.ConnectionType;
import org.restcomm.media.spi.Endpoint;
import org.restcomm.media.spi.ModeNotSupportedException;
import org.restcomm.media.spi.listener.Listeners;
import org.restcomm.media.spi.listener.TooManyListenersException;
import org.restcomm.media.spi.utils.Text;
/**
* Implements connection's FSM.
*
* @author Oifa Yulian
*/
public abstract class BaseConnection implements Connection {
private int id;
// Identifier of this connection
private String textualId;
// scheduler instance
private PriorityQueueScheduler scheduler;
/** FSM current state */
private volatile ConnectionState state = ConnectionState.NULL;
private final Object stateMonitor = new Integer(0);
// connection event listeners
private Listeners<ConnectionListener> listeners = new Listeners<ConnectionListener>();
// events
private ConnectionEvent stateEvent;
/** Remaining time to live in current state */
private volatile long ttl;
private HeartBeat heartBeat;
private Endpoint activeEndpoint;
private ConnectionMode connectionMode = ConnectionMode.INACTIVE;
private static final Logger logger = Logger.getLogger(BaseConnection.class);
/**
* Creates basic connection implementation.
*
* @param id
* the unique identifier of this connection within endpoint.
* @param endpoint
* the endpoint owner of this connection.
*/
public BaseConnection(int id, PriorityQueueScheduler scheduler) {
this.id = id;
this.textualId = Integer.toHexString(id);
this.scheduler = scheduler;
heartBeat = new HeartBeat();
// initialize event objects
this.stateEvent = new ConnectionEventImpl(ConnectionEvent.STATE_CHANGE, this);
}
public abstract AudioComponent getAudioComponent();
public abstract OOBComponent getOOBComponent();
@Override
public int getId() {
return id;
}
@Override
public String getTextualId() {
return textualId;
}
@Override
public ConnectionState getState() {
synchronized (stateMonitor) {
return state;
}
}
/**
* Modifies state of the connection.
*
* @param state
* the new value for the state.
*/
private void setState(ConnectionState state) {
// change state
this.state = state;
this.ttl = state.getTimeout() * 10 + 1;
switch (state) {
case HALF_OPEN:
scheduler.submitHeatbeat(heartBeat);
break;
case NULL:
heartBeat.cancel();
break;
default:
break;
}
// notify listeners
try {
listeners.dispatch(stateEvent);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public String getDescriptor() {
return null;
}
@Override
public String getLocalDescriptor() {
return null;
}
@Override
public String getRemoteDescriptor() {
return null;
}
@Override
public void setEndpoint(Endpoint endpoint) {
this.activeEndpoint = endpoint;
}
@Override
public Endpoint getEndpoint() {
return this.activeEndpoint;
}
@Override
public void addListener(ConnectionListener listener) {
try {
listeners.add(listener);
} catch (TooManyListenersException e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void removeListener(ConnectionListener listener) {
listeners.remove(listener);
}
/**
* Initiates transition from NULL to HALF_OPEN state.
*/
public void bind() throws Exception {
synchronized (stateMonitor) {
// check current state
if (this.state != ConnectionState.NULL) {
throw new IllegalStateException("Connection already bound");
}
// execute call back
this.onCreated();
// update state
setState(ConnectionState.HALF_OPEN);
}
}
/**
* Initiates transition from HALF_OPEN to OPEN state.
*/
public void join() throws Exception {
synchronized (stateMonitor) {
if (this.state == ConnectionState.NULL) {
throw new IllegalStateException("Connection not bound yet");
}
if (this.state == ConnectionState.OPEN) {
throw new IllegalStateException("Connection opened already");
}
// execute callback
this.onOpened();
// update state
setState(ConnectionState.OPEN);
}
}
/**
* Initiates transition from any state to state NULL.
*/
public void close() {
synchronized (stateMonitor) {
if (this.state != ConnectionState.NULL) {
this.onClosed();
setState(ConnectionState.NULL);
}
}
}
private void fail() {
synchronized (stateMonitor) {
if (this.state != ConnectionState.NULL) {
this.onFailed();
setState(ConnectionState.NULL);
}
}
}
/**
* Gets the current mode of this connection.
*
* @return integer constant indicating mode.
*/
@Override
public ConnectionMode getMode() {
return connectionMode;
}
/**
* Modify mode of this connection for all known media types.
*
* @param mode
* the new mode of the connection.
*/
@Override
public void setMode(ConnectionMode mode) throws ModeNotSupportedException {
if (this.activeEndpoint != null) {
this.activeEndpoint.modeUpdated(connectionMode, mode);
}
this.connectionMode = mode;
}
/**
* Sets connection failure listener.
*/
@Override
public abstract void setConnectionFailureListener(ConnectionFailureListener connectionFailureListener);
/**
* Called when connection created.
*/
protected abstract void onCreated() throws Exception;
/**
* Called when connected moved to OPEN state.
*
* @throws Exception
*/
protected abstract void onOpened() throws Exception;
/**
* Called when connection is moving from OPEN state to NULL state.
*/
protected abstract void onClosed();
/**
* Called if failure has bean detected during transition.
*/
protected abstract void onFailed();
/**
* Joins endpoint wich executes this connection with other party.
*
* @param other
* the connection executed by other party endpoint.
* @throws IOException
*/
@Override
public abstract void setOtherParty(Connection other) throws IOException;
/**
* Joins endpoint which executes this connection with other party.
*
* @param descriptor
* the SDP descriptor of the other party.
* @throws IOException
*/
@Override
public abstract void setOtherParty(byte[] descriptor) throws IOException;
/**
* Joins endpoint which executes this connection with other party.
*
* @param descriptor
* the SDP descriptor of the other party.
* @throws IOException
*/
@Override
public abstract void setOtherParty(Text descriptor) throws IOException;
/**
* Gets whether connection should be bound to local or remote interface.
* <p>
* <b>Supported only for RTP connections.</b>
* </p>
*
* @return boolean value
*/
@Override
public boolean getIsLocal() {
return false;
}
/**
* Sets whether connection should be bound to local or remote interface.
* <p>
* <b>Supported only for RTP connections.</b>
* </p>
*/
@Override
public void setIsLocal(boolean isLocal) {
// do nothing
}
protected void releaseConnection(ConnectionType connectionType) {
if (this.activeEndpoint != null) {
this.activeEndpoint.deleteConnection(this, connectionType);
}
this.activeEndpoint = null;
}
private class HeartBeat extends Task {
public HeartBeat() {
super();
}
public int getQueueNumber() {
return PriorityQueueScheduler.HEARTBEAT_QUEUE;
}
@Override
public long perform() {
synchronized (stateMonitor) {
ttl--;
if (ttl == 0) {
fail();
} else {
scheduler.submitHeatbeat(this);
}
}
return 0;
}
}
}