package com.rayo.server.verb;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import javax.media.mscontrol.join.Joinable.Direction;
import com.rayo.server.ActorEventListener;
import com.rayo.core.verb.Ssml;
import com.rayo.core.verb.Transfer;
import com.rayo.core.verb.TransferCompleteEvent;
import com.rayo.core.verb.TransferCompleteEvent.Reason;
import com.rayo.core.verb.VerbCompleteEvent;
import com.rayo.core.verb.VerbCompleteReason;
import com.voxeo.logging.Loggerf;
import com.voxeo.moho.Call;
import com.voxeo.moho.CallableEndpoint;
import com.voxeo.moho.Endpoint;
import com.voxeo.moho.Joint;
import com.voxeo.moho.Participant;
import com.voxeo.moho.RedirectException;
import com.voxeo.moho.State;
import com.voxeo.moho.event.HangupEvent;
import com.voxeo.moho.event.InputCompleteEvent;
import com.voxeo.moho.event.JoinCompleteEvent;
import com.voxeo.moho.event.Observer;
import com.voxeo.moho.media.Input;
import com.voxeo.moho.media.Prompt;
import com.voxeo.moho.media.input.DigitInputCommand;
import com.voxeo.moho.media.input.InputCommand;
import com.voxeo.moho.media.output.OutputCommand;
import com.voxeo.moho.media.output.OutputCommand.BargeinType;
public class TransferHandler extends AbstractLocalVerbHandler<Transfer, Call> implements Observer {
private static final Loggerf log = Loggerf.getLogger(TransferHandler.class);
private boolean running;
private Input<Participant> input;
private Prompt<Participant> ringBack;
private Timer timer = new Timer();
private Map<Call, Joint> joints = new HashMap<Call, Joint>();
private Call peer;
private boolean isRunning() {
return running;
}
@Override
public void start() {
Ssml ringbackSsml = model.getRingbackTone();
if(ringbackSsml != null) {
OutputCommand outputCommand = output(ringbackSsml);
outputCommand.setBargeinType(BargeinType.NONE);
outputCommand.setVoiceName(ringbackSsml.getVoice());
ringBack = getMediaService().prompt(outputCommand, null, 30);
}
running = true;
// Timeout Timer
timer.schedule(new TimerTask() {
public void run() {
if (isRunning()) {
complete(Reason.TIMEOUT);
}
}
}, model.getTimeout().getMillis());
for (URI destination : model.getTo()) {
Endpoint endpoint = participant.getApplicationContext().createEndpoint(destination.toString());
if (endpoint instanceof CallableEndpoint) {
dial((CallableEndpoint) endpoint);
}
}
}
// Commands
// ================================================================================
@Override
public synchronized void stop(boolean hangup) {
if(hangup) {
complete(VerbCompleteEvent.Reason.HANGUP);
}
else {
complete(VerbCompleteEvent.Reason.STOP);
}
}
private void stopDialing() {
// Stop Ringing
if (ringBack != null) {
ringBack.getOutput().stop();
}
// Stop Terminator
if (input != null) {
input.stop();
}
// Stop Timer
if(timer != null) {
timer.cancel();
}
// Cancel Active Calls.
Set<Call> calls = joints.keySet();
for (Call call : calls) {
Joint joint = joints.get(call);
joint.cancel(true);
call.disconnect();
}
joints.clear();
}
// Moho Events
// ================================================================================
@State
public synchronized void onDisconnect(HangupEvent event) {
if(event.getSource() == peer) {
complete(Reason.SUCCESS);
}
}
@State
public synchronized void onJoinComplete(JoinCompleteEvent event) {
if (event.getSource() != participant) {
// Make sure we're running or still interested in joining someone
if(!isRunning() || peer != null) {
log.info("Received JoinCompleteEvent but Transfer verb is either already joined or no longer running");
return;
}
// Remove the candidate from the list
joints.remove(event.getSource());
switch (event.getCause()) {
case JOINED:
peer = (Call)event.getSource();
participant.join(peer, resolveJoinType(), Direction.DUPLEX);
stopDialing();
startInputIfNeeded();
break;
case TIMEOUT:
if (joints.size() == 0) {
complete(Reason.TIMEOUT);
}
break;
case DISCONNECTED:
if (joints.size() == 0) {
complete(VerbCompleteEvent.Reason.HANGUP);
}
break;
case BUSY:
if (joints.size() == 0) {
complete(Reason.BUSY);
}
break;
case REJECT:
if (joints.size() == 0) {
complete(Reason.REJECT);
}
break;
case REDIRECT:
// Determine the redirect target and spin up a new candidate
List<String> redirectTargets = ((RedirectException) event.getException()).getTargets();
for(String redirectTarget : redirectTargets) {
CallableEndpoint to = (CallableEndpoint) participant.getApplicationContext().createEndpoint(redirectTarget);
dial(to);
}
break;
case ERROR:
log.error("Error transfering call", event.getException());
complete(VerbCompleteEvent.Reason.ERROR);
default:
log.error("Unhandled join cause [cause=%s]", event.getCause());
complete(VerbCompleteEvent.Reason.ERROR);
}
}
}
private void startInputIfNeeded() {
Character terminator = model.getTerminator();
if(terminator != null) {
InputCommand inputCommand = new DigitInputCommand(terminator);
input = getMediaService().input(inputCommand);
}
}
@State
public synchronized void onTermChar(InputCompleteEvent<Participant> event) {
if (event.getSource() == participant) {
switch (event.getCause()) {
case MATCH:
if (isRunning()) {
complete(Reason.TERMINATOR);
}
break;
}
}
}
// Utility
// ================================================================================
private void complete(VerbCompleteReason reason) {
running = false;
TransferCompleteEvent event = new TransferCompleteEvent(model, reason);
// Stop active dialing
stopDialing();
// Join back to the media server
if(peer != null) {
try {
peer.unjoin(participant).get(10, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("Failed to unjoin peer [%s]", peer);
}
}
complete(event);
}
private void dial(CallableEndpoint to) {
Endpoint from = resolveFrom();
log.info("Dialing endpoint. To: [%s]. From: [%s]. Headers: [%s]",to,from,model.getHeaders());
Call destination = to.createCall(from, model.getHeaders());
destination.addObserver(new ActorEventListener(actor));
Joint joint = destination.join();
joints.put(destination, joint);
}
private Endpoint resolveFrom() {
Endpoint fromEndpoint = null;
URI from = model.getFrom();
if (from != null) {
fromEndpoint = participant.getApplicationContext().createEndpoint(from.toString());
}
else {
fromEndpoint = participant.getAddress();
}
return fromEndpoint;
}
private Participant.JoinType resolveJoinType() {
switch(model.getMedia()) {
case BRIDGE : return Participant.JoinType.BRIDGE;
case DIRECT : return Participant.JoinType.DIRECT;
}
return null;
}
}