Java tutorial
/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2013, Telestax Inc and individual contributors * by the @authors tag. * * 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.mobicents.servlet.restcomm.mscontrol.mgcp; import jain.protocol.ip.mgcp.message.parms.ConnectionDescriptor; import jain.protocol.ip.mgcp.message.parms.ConnectionMode; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.sound.sampled.UnsupportedAudioFileException; import org.apache.commons.configuration.Configuration; import org.joda.time.DateTime; import org.mobicents.servlet.restcomm.dao.DaoManager; import org.mobicents.servlet.restcomm.dao.RecordingsDao; import org.mobicents.servlet.restcomm.entities.Recording; import org.mobicents.servlet.restcomm.entities.Sid; import org.mobicents.servlet.restcomm.fsm.FiniteStateMachine; import org.mobicents.servlet.restcomm.fsm.State; import org.mobicents.servlet.restcomm.fsm.Transition; import org.mobicents.servlet.restcomm.mgcp.CloseConnection; import org.mobicents.servlet.restcomm.mgcp.CloseLink; import org.mobicents.servlet.restcomm.mgcp.ConnectionStateChanged; import org.mobicents.servlet.restcomm.mgcp.CreateBridgeEndpoint; import org.mobicents.servlet.restcomm.mgcp.CreateConnection; import org.mobicents.servlet.restcomm.mgcp.CreateLink; import org.mobicents.servlet.restcomm.mgcp.DestroyEndpoint; import org.mobicents.servlet.restcomm.mgcp.DestroyLink; import org.mobicents.servlet.restcomm.mgcp.EndpointState; import org.mobicents.servlet.restcomm.mgcp.EndpointStateChanged; import org.mobicents.servlet.restcomm.mgcp.GetMediaGatewayInfo; import org.mobicents.servlet.restcomm.mgcp.InitializeConnection; import org.mobicents.servlet.restcomm.mgcp.InitializeLink; import org.mobicents.servlet.restcomm.mgcp.LinkStateChanged; import org.mobicents.servlet.restcomm.mgcp.MediaGatewayInfo; import org.mobicents.servlet.restcomm.mgcp.MediaGatewayResponse; import org.mobicents.servlet.restcomm.mgcp.MediaSession; import org.mobicents.servlet.restcomm.mgcp.OpenConnection; import org.mobicents.servlet.restcomm.mgcp.OpenLink; import org.mobicents.servlet.restcomm.mgcp.UpdateConnection; import org.mobicents.servlet.restcomm.mgcp.UpdateLink; import org.mobicents.servlet.restcomm.mscontrol.MediaServerController; import org.mobicents.servlet.restcomm.mscontrol.messages.CloseMediaSession; import org.mobicents.servlet.restcomm.mscontrol.messages.Collect; import org.mobicents.servlet.restcomm.mscontrol.messages.CreateMediaSession; import org.mobicents.servlet.restcomm.mscontrol.messages.JoinBridge; import org.mobicents.servlet.restcomm.mscontrol.messages.JoinComplete; import org.mobicents.servlet.restcomm.mscontrol.messages.JoinConference; import org.mobicents.servlet.restcomm.mscontrol.messages.Leave; import org.mobicents.servlet.restcomm.mscontrol.messages.Left; import org.mobicents.servlet.restcomm.mscontrol.messages.MediaGroupStateChanged; import org.mobicents.servlet.restcomm.mscontrol.messages.MediaServerControllerStateChanged; import org.mobicents.servlet.restcomm.mscontrol.messages.MediaServerControllerStateChanged.MediaServerControllerState; import org.mobicents.servlet.restcomm.mscontrol.messages.MediaSessionInfo; import org.mobicents.servlet.restcomm.mscontrol.messages.Mute; import org.mobicents.servlet.restcomm.mscontrol.messages.Play; import org.mobicents.servlet.restcomm.mscontrol.messages.Record; import org.mobicents.servlet.restcomm.mscontrol.messages.StartMediaGroup; import org.mobicents.servlet.restcomm.mscontrol.messages.StartRecording; import org.mobicents.servlet.restcomm.mscontrol.messages.Stop; import org.mobicents.servlet.restcomm.mscontrol.messages.StopMediaGroup; import org.mobicents.servlet.restcomm.mscontrol.messages.StopRecording; import org.mobicents.servlet.restcomm.mscontrol.messages.Unmute; import org.mobicents.servlet.restcomm.mscontrol.messages.UpdateMediaSession; import org.mobicents.servlet.restcomm.patterns.Observe; import org.mobicents.servlet.restcomm.patterns.Observing; import org.mobicents.servlet.restcomm.patterns.StopObserving; import org.mobicents.servlet.restcomm.util.WavUtils; import akka.actor.ActorRef; import akka.actor.Props; import akka.actor.UntypedActor; import akka.actor.UntypedActorFactory; import akka.event.Logging; import akka.event.LoggingAdapter; /** * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class MmsCallController extends MediaServerController { private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); // Finite State Machine private final FiniteStateMachine fsm; private final State uninitialized; private final State acquiringMediaGatewayInfo; private final State acquiringMediaSession; private final State acquiringBridge; private final State creatingMediaGroup; private final State acquiringRemoteConnection; private final State initializingRemoteConnection; private final State openingRemoteConnection; private final State updatingRemoteConnection; private final State pending; private final State active; private final State muting; private final State unmuting; private final State acquiringInternalLink; private final State initializingInternalLink; private final State openingInternalLink; private final State updatingInternalLink; private final State closingInternalLink; private final State closingRemoteConnection; private final State stopping; private final State failed; private final State inactive; // Call runtime stuff private ActorRef call; private Sid callId; private String localSdp; private String remoteSdp; private String connectionMode; private boolean callOutbound; private boolean webrtc; // CallMediaGroup private ActorRef mediaGroup; private ActorRef bridge; private ActorRef outboundCallBridgeEndpoint; // MGCP runtime stuff private final ActorRef mediaGateway; private MediaGatewayInfo gatewayInfo; private MediaSession session; private ActorRef bridgeEndpoint; private ActorRef remoteConn; private ActorRef internalLink; private ActorRef internalLinkEndpoint; private ConnectionMode internalLinkMode; // Call Recording private Sid accountId; private Sid recordingSid; private URI recordingUri; private Boolean recording = false; private DateTime recordStarted; private DaoManager daoManager; private Boolean collecting = false; // Runtime Setting private Configuration runtimeSettings; // Observer pattern private final List<ActorRef> observers; public MmsCallController(final ActorRef mediaGateway) { super(); final ActorRef source = self(); // Initialize the states for the FSM. this.uninitialized = new State("uninitialized", null, null); this.acquiringMediaGatewayInfo = new State("acquiring media gateway info", new AcquiringMediaGatewayInfo(source), null); this.acquiringMediaSession = new State("acquiring media session", new AcquiringMediaSession(source), null); this.acquiringBridge = new State("acquiring media bridge", new AcquiringBridge(source), null); this.creatingMediaGroup = new State("creating media group", new CreatingMediaGroup(source), null); this.acquiringRemoteConnection = new State("acquiring connection", new AcquiringRemoteConnection(source), null); this.initializingRemoteConnection = new State("initializing connection", new InitializingRemoteConnection(source), null); this.openingRemoteConnection = new State("opening connection", new OpeningRemoteConnection(source), null); this.updatingRemoteConnection = new State("updating connection", new UpdatingRemoteConnection(source), null); this.pending = new State("pending", new Pending(source), null); this.active = new State("active", new Active(source), null); this.muting = new State("muting", new Muting(source), null); this.unmuting = new State("unmuting", new Unmuting(source), null); this.acquiringInternalLink = new State("acquiring link", new AcquiringInternalLink(source), null); this.initializingInternalLink = new State("initializing link", new InitializingInternalLink(source), null); this.openingInternalLink = new State("opening link", new OpeningInternalLink(source), null); this.updatingInternalLink = new State("updating link", new UpdatingInternalLink(source), null); this.closingInternalLink = new State("closing link", new EnterClosingInternalLink(source), new ExitClosingInternalLink(source)); this.closingRemoteConnection = new State("closing connection", new ClosingRemoteConnection(source), null); this.stopping = new State("stopping", new Stopping(source)); this.inactive = new State("inactive", new Inactive(source)); this.failed = new State("failed", new Failed(source)); // Transitions for the FSM. final Set<Transition> transitions = new HashSet<Transition>(); transitions.add(new Transition(this.uninitialized, this.acquiringMediaGatewayInfo)); transitions.add(new Transition(this.uninitialized, this.closingRemoteConnection)); transitions.add(new Transition(this.acquiringMediaGatewayInfo, this.acquiringMediaSession)); transitions.add(new Transition(this.acquiringMediaSession, this.acquiringBridge)); transitions.add(new Transition(this.acquiringMediaSession, this.stopping)); transitions.add(new Transition(this.acquiringBridge, this.creatingMediaGroup)); transitions.add(new Transition(this.acquiringBridge, this.stopping)); transitions.add(new Transition(this.creatingMediaGroup, this.acquiringRemoteConnection)); transitions.add(new Transition(this.creatingMediaGroup, this.stopping)); transitions.add(new Transition(this.creatingMediaGroup, this.failed)); transitions.add(new Transition(this.acquiringRemoteConnection, this.initializingRemoteConnection)); transitions.add(new Transition(this.initializingRemoteConnection, this.openingRemoteConnection)); transitions.add(new Transition(this.openingRemoteConnection, this.active)); transitions.add(new Transition(this.openingRemoteConnection, this.failed)); transitions.add(new Transition(this.openingRemoteConnection, this.pending)); transitions.add(new Transition(this.active, this.muting)); transitions.add(new Transition(this.active, this.unmuting)); transitions.add(new Transition(this.active, this.updatingRemoteConnection)); transitions.add(new Transition(this.active, this.stopping)); transitions.add(new Transition(this.active, this.acquiringInternalLink)); transitions.add(new Transition(this.active, this.closingInternalLink)); transitions.add(new Transition(this.active, this.creatingMediaGroup)); transitions.add(new Transition(this.pending, this.active)); transitions.add(new Transition(this.pending, this.failed)); transitions.add(new Transition(this.pending, this.updatingRemoteConnection)); transitions.add(new Transition(this.pending, this.stopping)); transitions.add(new Transition(this.muting, this.active)); transitions.add(new Transition(this.muting, this.closingRemoteConnection)); transitions.add(new Transition(this.unmuting, this.active)); transitions.add(new Transition(this.unmuting, this.closingRemoteConnection)); transitions.add(new Transition(this.updatingRemoteConnection, this.active)); transitions.add(new Transition(this.updatingRemoteConnection, this.stopping)); transitions.add(new Transition(this.updatingRemoteConnection, this.failed)); transitions.add(new Transition(this.closingRemoteConnection, this.inactive)); transitions.add(new Transition(this.closingRemoteConnection, this.closingInternalLink)); transitions.add(new Transition(this.acquiringInternalLink, this.closingRemoteConnection)); transitions.add(new Transition(this.acquiringInternalLink, this.initializingInternalLink)); transitions.add(new Transition(this.initializingInternalLink, this.closingRemoteConnection)); transitions.add(new Transition(this.initializingInternalLink, this.openingInternalLink)); transitions.add(new Transition(this.openingInternalLink, this.stopping)); transitions.add(new Transition(this.openingInternalLink, this.updatingInternalLink)); transitions.add(new Transition(this.updatingInternalLink, this.stopping)); transitions.add(new Transition(this.updatingInternalLink, this.closingInternalLink)); transitions.add(new Transition(this.updatingInternalLink, this.active)); transitions.add(new Transition(this.closingInternalLink, this.closingRemoteConnection)); transitions.add(new Transition(this.closingInternalLink, this.active)); transitions.add(new Transition(this.closingInternalLink, this.inactive)); transitions.add(new Transition(this.stopping, this.inactive)); transitions.add(new Transition(this.stopping, this.failed)); // Initialize the FSM. this.fsm = new FiniteStateMachine(uninitialized, transitions); // MGCP runtime stuff this.mediaGateway = mediaGateway; // Call runtime stuff this.localSdp = ""; this.remoteSdp = ""; this.callOutbound = false; this.connectionMode = "inactive"; this.webrtc = false; // Observers this.observers = new ArrayList<ActorRef>(1); } /** * Checks whether the actor is currently in a certain state. * * @param state The state to be checked * @return Returns true if the actor is currently in the state. Returns false otherwise. */ private boolean is(State state) { return this.fsm.state().equals(state); } private void broadcast(final Object message) { if (!this.observers.isEmpty()) { final ActorRef self = self(); for (ActorRef observer : observers) { observer.tell(message, self); } } } private ActorRef createMediaGroup(final Object message) { // No need to create new media group is current one is active if (this.mediaGroup != null && !this.mediaGroup.isTerminated()) { return this.mediaGroup; } return getContext().actorOf(new Props(new UntypedActorFactory() { private static final long serialVersionUID = 1L; @Override public UntypedActor create() throws Exception { return new MgcpMediaGroup(mediaGateway, session, bridgeEndpoint); } })); } private void startRecordingCall() throws Exception { if (logger.isInfoEnabled()) { logger.info("Start recording call"); } String finishOnKey = "1234567890*#"; int maxLength = 3600; int timeout = 5; this.recordStarted = DateTime.now(); this.recording = true; // Tell media group to start recording Record record = new Record(recordingUri, timeout, maxLength, finishOnKey); this.mediaGroup.tell(record, null); } private void stopCollect(Stop message) throws Exception { if (logger.isInfoEnabled()) { logger.info("Stop DTMF collect"); } if (this.mediaGroup != null) { // Tell media group to stop recording mediaGroup.tell(message, null); } collecting = false; } private void stopRecordingCall(Stop message) throws Exception { if (logger.isInfoEnabled()) { logger.info("Stop recording call"); } if (this.mediaGroup != null) { // Tell media group to stop recording mediaGroup.tell(message, null); this.recording = false; if (message.createRecord() && recordingUri != null) { Double duration; try { duration = WavUtils.getAudioDuration(recordingUri); } catch (UnsupportedAudioFileException | IOException e) { logger.error("Could not measure recording duration: " + e.getMessage(), e); duration = 0.0; } if (duration.equals(0.0)) { if (logger.isInfoEnabled()) { logger.info("Call wraping up recording. File doesn't exist since duration is 0"); } final DateTime end = DateTime.now(); duration = new Double((end.getMillis() - recordStarted.getMillis()) / 1000); } else { if (logger.isInfoEnabled()) { logger.info("Call wraping up recording. File already exists, length: " + (new File(recordingUri).length())); } } final Recording.Builder builder = Recording.builder(); builder.setSid(recordingSid); builder.setAccountSid(accountId); builder.setCallSid(callId); builder.setDuration(duration); builder.setApiVersion(runtimeSettings.getString("api-version")); StringBuilder buffer = new StringBuilder(); buffer.append("/").append(runtimeSettings.getString("api-version")).append("/Accounts/") .append(accountId.toString()); buffer.append("/Recordings/").append(recordingSid.toString()); builder.setUri(URI.create(buffer.toString())); final Recording recording = builder.build(); RecordingsDao recordsDao = daoManager.getRecordingsDao(); recordsDao.addRecording(recording); } } else if (logger.isInfoEnabled()) { logger.info("Tried to stop recording but group was null."); } } /* * EVENTS */ @Override public void onReceive(Object message) throws Exception { final Class<?> klass = message.getClass(); final ActorRef self = self(); final ActorRef sender = sender(); final State state = fsm.state(); if (logger.isInfoEnabled()) { logger.info("********** MmsCallController Current State: \"" + state.toString()); logger.info("********** MmsCallController Processing Message: \"" + klass.getName() + " sender : " + sender.getClass()); } if (Observe.class.equals(klass)) { onObserve((Observe) message, self, sender); } else if (StopObserving.class.equals(klass)) { onStopObserving((StopObserving) message, self, sender); } else if (CreateMediaSession.class.equals(klass)) { onCreateMediaSession((CreateMediaSession) message, self, sender); } else if (CloseMediaSession.class.equals(klass)) { onCloseMediaSession((CloseMediaSession) message, self, sender); } else if (UpdateMediaSession.class.equals(klass)) { onUpdateMediaSession((UpdateMediaSession) message, self, sender); } else if (MediaGatewayResponse.class.equals(klass)) { onMediaGatewayResponse((MediaGatewayResponse<?>) message, self, sender); } else if (ConnectionStateChanged.class.equals(klass)) { onConnectionStateChanged((ConnectionStateChanged) message, self, sender); } else if (LinkStateChanged.class.equals(klass)) { onLinkStateChanged((LinkStateChanged) message, self, sender); } else if (Leave.class.equals(klass)) { onLeave((Leave) message, self, sender); } else if (Mute.class.equals(klass)) { onMute((Mute) message, self, sender); } else if (Unmute.class.equals(klass)) { onUnmute((Unmute) message, self, sender); } else if (StartRecording.class.equals(klass)) { onStartRecordingCall((StartRecording) message, self, sender); } else if (StopRecording.class.equals(klass)) { onStopRecordingCall((StopRecording) message, self, sender); } else if (Stop.class.equals(klass)) { onStop((Stop) message, self, sender); } else if (StopMediaGroup.class.equals(klass)) { onStopMediaGroup((StopMediaGroup) message, self, sender); } else if (JoinConference.class.equals(klass)) { onJoinConference((JoinConference) message, self, sender); } else if (JoinBridge.class.equals(klass)) { onJoinBridge((JoinBridge) message, self, sender); } else if (Leave.class.equals(klass)) { onLeave((Leave) message, self, sender); } else if (MediaGroupStateChanged.class.equals(klass)) { onMediaGroupStateChanged((MediaGroupStateChanged) message, self, sender); } else if (Record.class.equals(klass)) { onRecord((Record) message, self, sender); } else if (Play.class.equals(klass)) { onPlay((Play) message, self, sender); } else if (Collect.class.equals(klass)) { onCollect((Collect) message, self, sender); } else if (EndpointStateChanged.class.equals(klass)) { onEndpointStateChanged((EndpointStateChanged) message, self, sender); } } private void onObserve(Observe message, ActorRef self, ActorRef sender) { final ActorRef observer = message.observer(); if (observer != null) { synchronized (this.observers) { this.observers.add(observer); observer.tell(new Observing(self), self); } } } private void onStopObserving(StopObserving message, ActorRef self, ActorRef sender) { final ActorRef observer = message.observer(); if (observer != null) { this.observers.remove(observer); } } private void onCreateMediaSession(CreateMediaSession message, ActorRef self, ActorRef sender) throws Exception { this.call = sender; this.connectionMode = message.getConnectionMode(); this.callOutbound = message.isOutbound(); this.remoteSdp = message.getSessionDescription(); this.webrtc = message.isWebrtc(); fsm.transition(message, acquiringMediaGatewayInfo); } private void onCloseMediaSession(CloseMediaSession message, ActorRef self, ActorRef sender) throws Exception { if (is(pending) || is(updatingRemoteConnection) || is(active) || is(acquiringInternalLink) || is(updatingInternalLink) || is(creatingMediaGroup) || is(acquiringBridge) || is(acquiringMediaSession)) { fsm.transition(message, stopping); } } private void onUpdateMediaSession(UpdateMediaSession message, ActorRef self, ActorRef sender) throws Exception { this.remoteSdp = message.getSessionDescription(); this.fsm.transition(message, updatingRemoteConnection); } private void onMediaGatewayResponse(MediaGatewayResponse<?> message, ActorRef self, ActorRef sender) throws Exception { if (is(acquiringMediaGatewayInfo)) { fsm.transition(message, acquiringMediaSession); } else if (is(acquiringMediaSession)) { fsm.transition(message, acquiringBridge); } else if (is(acquiringBridge)) { this.bridgeEndpoint = (ActorRef) message.get(); this.bridgeEndpoint.tell(new Observe(self), self); fsm.transition(message, creatingMediaGroup); } else if (is(acquiringRemoteConnection)) { fsm.transition(message, initializingRemoteConnection); } else if (is(acquiringInternalLink)) { fsm.transition(message, initializingInternalLink); } } private void onConnectionStateChanged(ConnectionStateChanged message, ActorRef self, ActorRef sender) throws Exception { switch (message.state()) { case CLOSED: if (is(initializingRemoteConnection)) { fsm.transition(message, openingRemoteConnection); } else if (is(openingRemoteConnection)) { fsm.transition(message, failed); } else if (is(muting) || is(unmuting)) { fsm.transition(message, closingRemoteConnection); } else if (is(closingRemoteConnection)) { remoteConn = null; if (this.internalLink != null) { fsm.transition(message, closingInternalLink); } else { fsm.transition(message, inactive); } } else if (is(updatingRemoteConnection)) { fsm.transition(message, failed); } break; case HALF_OPEN: fsm.transition(message, pending); break; case OPEN: fsm.transition(message, active); break; default: break; } } private void onLinkStateChanged(LinkStateChanged message, ActorRef self, ActorRef sender) throws Exception { switch (message.state()) { case CLOSED: if (is(initializingInternalLink)) { fsm.transition(message, openingInternalLink); } else if (is(openingInternalLink)) { fsm.transition(message, stopping); } else if (is(closingInternalLink)) { if (remoteConn != null) { fsm.transition(message, active); } else { fsm.transition(message, inactive); } } break; case OPEN: if (is(openingInternalLink)) { fsm.transition(message, updatingInternalLink); } else if (is(updatingInternalLink)) { fsm.transition(message, active); } break; default: break; } } private void onMute(Mute message, ActorRef self, ActorRef sender) throws Exception { fsm.transition(message, muting); } private void onUnmute(Unmute message, ActorRef self, ActorRef sender) throws Exception { fsm.transition(message, unmuting); } private void onStartRecordingCall(StartRecording message, ActorRef self, ActorRef sender) throws Exception { if (runtimeSettings == null) { this.runtimeSettings = message.getRuntimeSetting(); } if (daoManager == null) { daoManager = message.getDaoManager(); } if (accountId == null) { accountId = message.getAccountId(); } this.callId = message.getCallId(); this.recordingSid = message.getRecordingSid(); this.recordingUri = message.getRecordingUri(); this.recording = true; startRecordingCall(); } private void onStopRecordingCall(StopRecording message, ActorRef self, ActorRef sender) throws Exception { if (this.recording) { if (runtimeSettings == null) { this.runtimeSettings = message.getRuntimeSetting(); } if (daoManager == null) { this.daoManager = message.getDaoManager(); } if (accountId == null) { this.accountId = message.getAccountId(); } onStop(new Stop(false), self, sender); } } private void onStop(Stop message, ActorRef self, ActorRef sender) throws Exception { if (this.recording) { stopRecordingCall(message); } else if (this.collecting) { stopCollect(message); } } private void onStopMediaGroup(StopMediaGroup message, ActorRef self, ActorRef sender) throws Exception { if (is(active) && this.mediaGroup != null) { this.mediaGroup.tell(new Stop(), sender); } } private void onLeave(Leave message, ActorRef self, ActorRef sender) throws Exception { if ((is(active) && internalLinkEndpoint != null) || is(updatingInternalLink)) { fsm.transition(message, closingInternalLink); } } private void onJoinBridge(JoinBridge message, ActorRef self, ActorRef sender) throws Exception { // Get bridge endpoint data this.bridge = sender; this.internalLinkEndpoint = (ActorRef) message.getEndpoint(); this.internalLinkMode = message.getConnectionMode(); // Start join operation this.fsm.transition(message, acquiringInternalLink); } private void onJoinConference(JoinConference message, ActorRef self, ActorRef sender) throws Exception { // Ask the remote media session controller for the bridge endpoint this.bridge = sender; this.internalLinkEndpoint = (ActorRef) message.getEndpoint(); this.internalLinkMode = message.getConnectionMode(); // Start join operation this.fsm.transition(message, acquiringInternalLink); } private void onMediaGroupStateChanged(MediaGroupStateChanged message, ActorRef self, ActorRef sender) throws Exception { switch (message.state()) { case ACTIVE: if (is(creatingMediaGroup)) { fsm.transition(message, acquiringRemoteConnection); } break; case INACTIVE: if (is(creatingMediaGroup)) { fsm.transition(message, failed); } else if (is(stopping)) { this.mediaGroup.tell(new StopObserving(self), self); context().stop(mediaGroup); this.mediaGroup = null; if (this.mediaGroup == null && this.bridgeEndpoint == null) { fsm.transition(message, inactive); } } break; default: break; } } private void onRecord(Record message, ActorRef self, ActorRef sender) { if (is(active)) { this.recording = Boolean.TRUE; // Forward message to media group to handle this.mediaGroup.tell(message, sender); } } private void onPlay(Play message, ActorRef self, ActorRef sender) { if (is(active) || is(muting)) { // Forward message to media group to handle this.mediaGroup.tell(message, sender); } } private void onCollect(Collect message, ActorRef self, ActorRef sender) { if (is(active)) { // Forward message to media group to handle this.mediaGroup.tell(message, sender); this.collecting = true; } } private void onEndpointStateChanged(EndpointStateChanged message, ActorRef self, ActorRef sender) throws Exception { if (is(stopping)) { if (sender.equals(this.bridgeEndpoint) && EndpointState.DESTROYED.equals(message.getState())) { this.bridgeEndpoint.tell(new StopObserving(self), self); context().stop(bridgeEndpoint); bridgeEndpoint = null; if (this.mediaGroup == null && this.bridgeEndpoint == null) { this.fsm.transition(message, inactive); } } } } /* * ACTIONS */ private final class AcquiringMediaGatewayInfo extends AbstractAction { public AcquiringMediaGatewayInfo(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { mediaGateway.tell(new GetMediaGatewayInfo(), self()); } } private final class AcquiringMediaSession extends AbstractAction { public AcquiringMediaSession(final ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(final Object message) throws Exception { final MediaGatewayResponse<MediaGatewayInfo> response = (MediaGatewayResponse<MediaGatewayInfo>) message; gatewayInfo = response.get(); mediaGateway.tell(new org.mobicents.servlet.restcomm.mgcp.CreateMediaSession(), source); } } public final class AcquiringBridge extends AbstractAction { public AcquiringBridge(final ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(final Object message) throws Exception { final MediaGatewayResponse<MediaSession> response = (MediaGatewayResponse<MediaSession>) message; session = response.get(); mediaGateway.tell(new CreateBridgeEndpoint(session), source); } } private final class AcquiringRemoteConnection extends AbstractAction { public AcquiringRemoteConnection(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { mediaGateway.tell(new CreateConnection(session), source); } } private final class InitializingRemoteConnection extends AbstractAction { public InitializingRemoteConnection(ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(final Object message) throws Exception { final MediaGatewayResponse<ActorRef> response = (MediaGatewayResponse<ActorRef>) message; remoteConn = response.get(); remoteConn.tell(new Observe(source), source); remoteConn.tell(new InitializeConnection(bridgeEndpoint), source); } } private final class OpeningRemoteConnection extends AbstractAction { public OpeningRemoteConnection(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { OpenConnection open = null; if (callOutbound) { open = new OpenConnection(ConnectionMode.SendRecv, webrtc); } else { final ConnectionDescriptor descriptor = new ConnectionDescriptor(remoteSdp); open = new OpenConnection(descriptor, ConnectionMode.SendRecv, webrtc); } remoteConn.tell(open, source); } } private final class UpdatingRemoteConnection extends AbstractAction { public UpdatingRemoteConnection(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final ConnectionDescriptor descriptor = new ConnectionDescriptor(remoteSdp); final UpdateConnection update = new UpdateConnection(descriptor); remoteConn.tell(update, source); } } private final class Active extends AbstractAction { public Active(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { if (is(updatingInternalLink)) { call.tell(new JoinComplete(), super.source); } else if (is(closingInternalLink)) { call.tell(new Left(), super.source); } else if (is(openingRemoteConnection) || is(updatingRemoteConnection)) { ConnectionStateChanged connState = (ConnectionStateChanged) message; localSdp = connState.descriptor().toString(); MediaSessionInfo mediaSessionInfo = new MediaSessionInfo(gatewayInfo.useNat(), gatewayInfo.externalIP(), localSdp, remoteSdp); broadcast( new MediaServerControllerStateChanged(MediaServerControllerState.ACTIVE, mediaSessionInfo)); } } } private final class Pending extends AbstractAction { public Pending(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { ConnectionStateChanged connState = (ConnectionStateChanged) message; localSdp = connState.descriptor().toString(); MediaSessionInfo mediaSessionInfo = new MediaSessionInfo(gatewayInfo.useNat(), gatewayInfo.externalIP(), localSdp, remoteSdp); broadcast(new MediaServerControllerStateChanged(MediaServerControllerState.PENDING, mediaSessionInfo)); } } private final class AcquiringInternalLink extends AbstractAction { public AcquiringInternalLink(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { mediaGateway.tell(new CreateLink(session), source); } } private final class InitializingInternalLink extends AbstractAction { public InitializingInternalLink(final ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(Object message) throws Exception { final MediaGatewayResponse<ActorRef> response = (MediaGatewayResponse<ActorRef>) message; if (self().path().toString().equalsIgnoreCase("akka://RestComm/user/$j")) { if (logger.isInfoEnabled()) { logger.info("Initializing Internal Link for the Outbound call"); } } if (bridgeEndpoint != null) { if (logger.isInfoEnabled()) { logger.info("##################### $$ Bridge for Call " + self().path() + " is terminated: " + bridgeEndpoint.isTerminated()); } if (bridgeEndpoint.isTerminated()) { // fsm.transition(message, acquiringMediaGatewayInfo); // return; if (logger.isInfoEnabled()) { logger.info("##################### $$ Call :" + self().path() + " bridge is terminated."); } // final Timeout expires = new Timeout(Duration.create(60, TimeUnit.SECONDS)); // Future<Object> future = (Future<Object>) akka.pattern.Patterns.ask(gateway, new // CreateBridgeEndpoint(session), expires); // MediaGatewayResponse<ActorRef> futureResponse = (MediaGatewayResponse<ActorRef>) Await.result(future, // Duration.create(10, TimeUnit.SECONDS)); // bridge = futureResponse.get(); // if (!bridge.isTerminated() && bridge != null) { // logger.info("Bridge for call: "+self().path()+" acquired and is not terminated"); // } else { // logger.info("Bridge endpoint for call: "+self().path()+" is still terminated or null"); // } } } // if (bridge == null || bridge.isTerminated()) { // System.out.println("##################### $$ Bridge for Call "+self().path()+" is null or terminated: "+bridge.isTerminated()); // } internalLink = response.get(); internalLink.tell(new Observe(source), source); internalLink.tell(new InitializeLink(bridgeEndpoint, internalLinkEndpoint), source); } } private final class OpeningInternalLink extends AbstractAction { public OpeningInternalLink(final ActorRef source) { super(source); } @Override public void execute(Object message) throws Exception { internalLink.tell(new OpenLink(internalLinkMode), source); } } private final class UpdatingInternalLink extends AbstractAction { public UpdatingInternalLink(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final UpdateLink update = new UpdateLink(ConnectionMode.SendRecv, UpdateLink.Type.PRIMARY); internalLink.tell(update, source); } } private final class CreatingMediaGroup extends AbstractAction { public CreatingMediaGroup(ActorRef source) { super(source); } @Override public void execute(Object message) throws Exception { // Always reuse current media group if active if (mediaGroup == null) { mediaGroup = createMediaGroup(message); // start monitoring state changes in the media group mediaGroup.tell(new Observe(super.source), super.source); // start the media group to enable media operations mediaGroup.tell(new StartMediaGroup(), super.source); } } } private final class Muting extends AbstractAction { public Muting(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final UpdateConnection update = new UpdateConnection(ConnectionMode.SendOnly); remoteConn.tell(update, source); } } private final class Unmuting extends AbstractAction { public Unmuting(final ActorRef source) { super(source); } @Override public void execute(Object message) throws Exception { final UpdateConnection update = new UpdateConnection(ConnectionMode.SendRecv); remoteConn.tell(update, source); } } private final class EnterClosingInternalLink extends AbstractAction { public EnterClosingInternalLink(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { internalLink.tell(new CloseLink(), source); } } private final class ExitClosingInternalLink extends AbstractAction { public ExitClosingInternalLink(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { mediaGateway.tell(new DestroyLink(internalLink), source); internalLink = null; internalLinkEndpoint = null; internalLinkMode = null; } } private class ClosingRemoteConnection extends AbstractAction { public ClosingRemoteConnection(final ActorRef source) { super(source); } @Override public void execute(Object message) throws Exception { if (remoteConn != null) { remoteConn.tell(new CloseConnection(), source); } } } private class Stopping extends AbstractAction { public Stopping(ActorRef source) { super(source); } @Override public void execute(Object message) throws Exception { if (mediaGroup != null) { // Stop the media group mediaGroup.tell(new StopMediaGroup(), super.source); } if (bridgeEndpoint != null) { // Stop bridge endpoint bridgeEndpoint.tell(new DestroyEndpoint(), super.source); } } } private abstract class FinalState extends AbstractAction { private final MediaServerControllerState state; public FinalState(ActorRef source, final MediaServerControllerState state) { super(source); this.state = state; } @Override public void execute(Object message) throws Exception { // Notify observers the controller has stopped broadcast(new MediaServerControllerStateChanged(state)); } } protected void cleanup() { if (logger.isInfoEnabled()) { logger.info("De-activating Call Controller"); } if (mediaGroup != null) { mediaGroup.tell(new StopObserving(self()), self()); mediaGroup.tell(new StopMediaGroup(), null); context().stop(mediaGroup); mediaGroup = null; } if (remoteConn != null) { // mediaGateway.tell(new DestroyConnection(remoteConn), source); context().stop(remoteConn); remoteConn = null; } if (internalLink != null) { // mediaGateway.tell(new DestroyLink(internalLink), source); context().stop(internalLink); internalLink = null; } if (bridgeEndpoint != null) { if (logger.isInfoEnabled()) { logger.info("Call Controller: " + self().path() + " about to stop bridge endpoint: " + bridgeEndpoint.path()); } mediaGateway.tell(new DestroyEndpoint(bridgeEndpoint), self()); context().stop(bridgeEndpoint); bridgeEndpoint = null; } bridge = null; outboundCallBridgeEndpoint = null; } private final class Inactive extends FinalState { public Inactive(final ActorRef source) { super(source, MediaServerControllerState.INACTIVE); } } private final class Failed extends FinalState { public Failed(final ActorRef source) { super(source, MediaServerControllerState.FAILED); } } @Override public void postStop() { // Cleanup resources cleanup(); // Clean observers observers.clear(); // Terminate actor getContext().stop(self()); } }