package com.rayo.server;
import static com.voxeo.utils.Objects.assertion;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import javax.media.mscontrol.MediaException;
import com.rayo.core.CallCommand;
import com.rayo.core.EndEvent;
import com.rayo.core.EndEvent.Reason;
import com.rayo.core.exception.RecoverableException;
import com.rayo.core.validation.Validator;
import com.rayo.core.verb.Ssml;
import com.rayo.core.verb.StopCommand;
import com.rayo.core.verb.Verb;
import com.rayo.core.verb.VerbCommand;
import com.rayo.core.verb.VerbCompleteEvent;
import com.rayo.core.verb.VerbEvent;
import com.rayo.core.verb.VerbRef;
import com.rayo.server.verb.EventDispatcher;
import com.rayo.server.verb.VerbFactory;
import com.rayo.server.verb.VerbHandler;
import com.rayo.server.verb.VerbManager;
import com.voxeo.logging.Loggerf;
import com.voxeo.moho.NegotiateException;
import com.voxeo.moho.Participant;
import com.voxeo.moho.common.event.AutowiredEventListener;
import com.voxeo.moho.event.Event;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.event.Observer;
import com.voxeo.moho.media.output.AudibleResource;
public abstract class AbstractActor<T extends Participant> extends ReflectiveActor implements Observer {
private static final Loggerf log = Loggerf.getLogger(AbstractActor.class);
private Map<String, VerbHandler<?,?>> verbs = new HashMap<String, VerbHandler<?,?>>();
protected List<AutowiredEventListener> mohoListeners = new CopyOnWriteArrayList<AutowiredEventListener>();
protected VerbManager verbManager;
protected T participant;
private Validator validator = new Validator();
public AbstractActor(T t) {
this.participant = t;
}
// State
// ================================================================================
// Indicates that the Moho Call is disconnected but we are waiting for all
// verbs to complete before sending the EndEvent
private EndEvent pendingEndEvent = null;
// Global Exception Handler
// ================================================================================
@Override
protected boolean handleException(Throwable throwable) {
if (throwable instanceof RecoverableException) {
// Recoverable exceptions are considered as minor issues that should not
// end the calls.
// Validation exceptions are considered as Recoverable exceptions
// so we won't be ending the call when a validation error happens
//TODO: This may be revisited in the future
return true;
}
if (throwable instanceof MediaException) {
end(Reason.ERROR, "Unrecoverable error from the media server");
} else {
end(Reason.ERROR);
}
return true;
}
/**
* Publishes a Request/Reply command to the actor. Must be synchronized to ensure that
* the command is not sent while the actor is shutting down.
*
* @param command
* @param callback
* @return <code>true</code> is the command was published successfuly; <code>false</code> otherwise.
*/
public synchronized boolean command(CallCommand command, ResponseHandler callback) {
if (command.getCallId() == null) {
command.setCallId(getParticipantId());
}
Request request = new Request(command, callback);
return publish(request);
}
// Verbs
// ================================================================================
protected abstract void verbCreated();
@Message
public VerbRef verb(final Verb verb) throws Exception {
VerbFactory verbFactory = verbManager.getVerbFactory(verb.getClass());
assertion(verbFactory != null, "Could not find handler class for " + verb.getClass());
// Generate Verb ID
verb.setVerbId(UUID.randomUUID().toString());
VerbHandler<? extends Verb, Participant> verbHandler = verbFactory.createVerbHandler();
verbHandler.setModel(verb);
verbHandler.setParticipant(participant);
verbHandler.setActor(this);
verbHandler.setEventDispatcher(verbDispatcher);
verbCreated();
AutowiredEventListener listener = new AutowiredEventListener(verbHandler);
mohoListeners.add(listener);
verbs.put(verb.getId(), verbHandler);
try {
validator.validate(verbHandler);
verbHandler.start();
} catch (Exception e) {
unregisterVerb(verb.getId());
throw e;
}
return new VerbRef(participant.getId(), verb.getId());
}
@Message
public void verbCommand(VerbCommand command) {
assertion(command.getVerbId() != null, "Verb id is null. Have you added the verb id to your \"to\" attribute?");
VerbHandler<?,?> handler = verbs.get(command.getVerbId());
assertion(handler != null, "Could not find verb [id=%s]", command.getVerbId());
assertion(handler.isComplete() == false, "Verb is no longer running [id=%s]", command.getVerbId());
if (command instanceof StopCommand) {
handler.stop(false);
} else {
handler.onCommand(command);
}
}
private EventDispatcher verbDispatcher = new EventDispatcher() {
@Override
public void fire(VerbEvent event) {
try {
AbstractActor.this.fire(event);
} catch (Exception e) {
log.error("Uncaght exception while dispatching subscription events", e);
}
if (event instanceof VerbCompleteEvent) {
unregisterVerb(event.getVerbId());
// If this is the last Verb to complete and the call is
/// over then dispatch the pending end event
if (verbs.isEmpty() && pendingEndEvent != null) {
end(pendingEndEvent);
}
}
}
};
private void unregisterVerb(String id) {
VerbHandler<?,?> verbHandler = verbs.remove(id);
for (int i = 0; i < mohoListeners.size(); i++) {
if (mohoListeners.get(i).getTarget() == verbHandler) {
mohoListeners.remove(i);
break;
}
}
}
// Moho Events
// ================================================================================
@Message
public void onMohoEvent(Event<EventSource> event) throws Exception {
for (AutowiredEventListener listener : mohoListeners) {
listener.onEvent(event);
}
}
// Util
// ================================================================================
protected void end(Reason reason) {
end(new EndEvent(getParticipantId(), reason, null));
}
protected void end(Reason reason, Map<String, String> headers) {
end(new EndEvent(getParticipantId(), reason, toHeaders(headers)));
}
protected void end(Reason reason, String errorMessage) {
end(reason,errorMessage,null);
}
protected void end(Reason reason, String errorMessage, Map<String, String> headers) {
end(new EndEvent(getParticipantId(), reason, errorMessage, toHeaders(headers)));
}
protected void end(Reason reason, Exception exception, Map<String, String> headers) {
String errorMessage = null;
if (exception instanceof NegotiateException) {
errorMessage = "Could not negotiate call";
}
end(new EndEvent(getParticipantId(), reason, errorMessage, toHeaders(headers)));
}
private Map<String, String> toHeaders(Map<String, String> headers) {
if (headers == null) return null;
Map<String,String> hdrs = new HashMap<String, String>();
hdrs.putAll(headers);
return hdrs;
}
protected void end(EndEvent endEvent) {
// If the call ended in error then don't bother with a graceful
// shutdown. Just send the EndEvent, stop the actor and make a best
// effort to end any active verbs.
if (endEvent.getReason() == Reason.ERROR || verbs.isEmpty()) {
fire(endEvent);
stop();
unjoinAll();
participant.disconnect();
for (VerbHandler<?,?> handler : verbs.values()) {
try {
handler.stop(false);
} catch (Exception e) {
log.error("Verb Handler did not shut down cleanly", e);
}
}
} else {
log.info("Call ended with active verbs [%s]", verbs.toString());
pendingEndEvent = endEvent;
for (VerbHandler<?,?> handler : verbs.values()) {
try {
handler.stop(true);
} catch (Exception e) {
log.error("Verb Handler did not shut down cleanly", e);
}
}
}
}
protected void unjoinAll() {
for(Participant peer : participant.getParticipants()) {
try {
log.info("Disconnecting. Unjoining peer [%s]", peer);
participant.unjoin(peer).get(10, TimeUnit.SECONDS);
log.info("Peer unjoined [%s]", peer);
} catch (Exception e) {
log.error("Failed to unjoin participant [%s]", peer, e);
}
}
}
protected String getParticipantId() {
return participant.getId();
}
public void setVerbManager(VerbManager verbManager) {
this.verbManager = verbManager;
}
public VerbManager getVerbManager() {
return verbManager;
}
public Collection<VerbHandler<?,?>> getVerbs() {
return new ArrayList<VerbHandler<?,?>>(verbs.values());
}
protected AudibleResource resolveAudio(final Ssml item) {
return new AudibleResource() {
public URI toURI() {
return item.toUri();
}
};
}
}