package com.rayo.server;
import static com.voxeo.utils.Objects.assertion;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.dom4j.Element;
import com.rayo.core.CallCommand;
import com.rayo.core.CallEvent;
import com.rayo.core.CallRef;
import com.rayo.core.DialCommand;
import com.rayo.core.EndCommand;
import com.rayo.core.EndEvent;
import com.rayo.core.MixerEvent;
import com.rayo.core.OfferEvent;
import com.rayo.core.validation.ValidationException;
import com.rayo.core.verb.Verb;
import com.rayo.core.verb.VerbCommand;
import com.rayo.core.verb.VerbEvent;
import com.rayo.core.xml.XmlProvider;
import com.rayo.server.admin.RayoAdminService;
import com.rayo.server.exception.RayoProtocolException;
import com.rayo.server.exception.RayoProtocolException.Condition;
import com.rayo.server.filter.FilterChain;
import com.voxeo.exceptions.NotFoundException;
import com.voxeo.logging.Loggerf;
public class Server implements EventHandler, CommandHandler {
private static final Loggerf log = Loggerf.getLogger(Server.class);
private XmlProvider provider;
private CallManager callManager;
private CallRegistry callRegistry;
private MixerRegistry mixerRegistry;
private CdrManager cdrManager;
private RayoAdminService adminService;
// TODO: Delete
private RayoStatistics rayoStatistics;
// TODO: Delete?
private FilterChain filtersChain;
private List<Transport> transports = new ArrayList<Transport>();
public void start() {
callManager.addEventHandler(this);
}
@Override
public void handle(Object event) throws Exception {
if(event instanceof CallEvent) {
handleCallEvent((CallEvent) event);
}
else if(event instanceof MixerEvent) {
handleMixerEvent((MixerEvent) event);
}
}
private void handleCallEvent(CallEvent event) {
try {
event = filtersChain.handleEvent(event);
} catch (RayoProtocolException e) {
log.error("Failed to dispatch call event. [event=%s]", event, e);
String callId = event.getCallId();
findActor(callId).publish(new EndCommand(callId, EndEvent.Reason.ERROR));
}
if (event == null) {
log.debug("Event was suppressed by message filter [event=%s]", event);
return;
}
// Log incoming call
if (event instanceof OfferEvent) {
rayoStatistics.callReceived();
}
// Serialize to XML
Element xml = provider.toXML(event);
assertion(xml != null, "Could not serialize event [event=%s]", event);
// Log to CDR
cdrManager.append(event.getCallId(), xml.asXML());
// Store the CDR is the call is over
if (event instanceof EndEvent) {
cdrManager.store(event.getCallId());
}
// Extract event properties
String callId = event.getCallId();
String componentId = (event instanceof VerbEvent) ? ((VerbEvent)event).getVerbId() : null;
boolean sent = false;
try {
log.debug("There is %s transports to handle the event.", transports.size());
for(Transport transport : transports) {
if (transport.callEvent(callId, componentId, xml)) {
sent = true;
}
}
if (!sent) {
log.warn("There was no transports interested on event. [event=%s]", event);
}
} catch (Exception e) {
log.error("Failed to dispatch call event. [event=%s]", event, e);
findActor(callId).publish(new EndCommand(callId, EndEvent.Reason.ERROR));
}
rayoStatistics.callEventProcessed();
}
private void handleMixerEvent(MixerEvent event) {
// Serialize the event to XML
Element xml = provider.toXML(event);
assertion(xml != null, "Could not serialize event [event=%s]", event);
for (String callId: event.getParticipantIds()) {
cdrManager.append(callId,xml.asXML());
}
// Extract event properties
String mixerId = event.getMixerId();
try {
for(Transport transport : transports) {
transport.mixerEvent(mixerId, event.getParticipantIds(), xml);
}
}
catch (Exception e) {
log.error("Failed to dispatch mixer event. [event=%s]", event, e);
}
}
@Override
public void handleCommand(final String id, String componentId, Element xml, final TransportCallback callback) {
try {
handleCommand(id, componentId, xml, provider.fromXML(xml), callback);
}
catch (Exception e) {
if(e instanceof ValidationException) {
rayoStatistics.validationError();
}
log.error("Failed to parse incoming command [id=%s, componentId=%s, xml=%s]", id, componentId, xml, e);
TransportCallback.handle(callback, null, e);
}
}
@Override
public void handleCommand(final String id, String componentId, CallCommand command, final TransportCallback callback) {
try {
handleCommand(id, componentId, provider.toXML(command), command, callback);
}
catch (Exception e) {
if(e instanceof ValidationException) {
rayoStatistics.validationError();
}
log.error("Failed to serialize incoming command [id=%s, componentId=%s, command=%s]", id, componentId, command, e);
TransportCallback.handle(callback, null, e);
}
}
private void handleCommand(final String id, String componentId, Element xml, Object command, final TransportCallback callback) {
try {
rayoStatistics.commandReceived(command);
// Special handling for <dial/> command
if (command instanceof DialCommand) {
handleDialCommand(command, callback);
return;
}
assertion(command instanceof CallCommand, "Is this a valid call command?");
assertion(id != null, "Call or Mixer ID cannot be null");
CallCommand callCommand = (CallCommand) command;
// Set the Call ID
callCommand.setCallId(id);
// Invoke filters
callCommand = filtersChain.handleCommandRequest(callCommand);
if (callCommand == null) {
log.debug("Command was suppressed by filter [command=%s]", callCommand);
return;
}
// Find the target actor
AbstractActor<?> actor = findActor(id);
if (actor instanceof CallActor) {
callCommand.setCallId(id);
cdrManager.append(id, xml.asXML());
}
// Resolve component properties
if (callCommand instanceof VerbCommand) {
VerbCommand verbCommand = (VerbCommand) callCommand;
// Starting a new component
if (callCommand instanceof Verb) {
verbCommand.setVerbId(UUID.randomUUID().toString());
}
// Command for existing component
else {
verbCommand.setVerbId(componentId);
}
}
// Dispatch command to actor
actor.command(callCommand, new ResponseHandler() {
public void handle(Response commandResponse) throws Exception {
Object response = null;
try {
response = filtersChain.handleCommandResponse(commandResponse.getValue());
} catch (RayoProtocolException e) {
response = e;
}
if(response == null) {
TransportCallback.handle(callback, null, null);
}
else if (response instanceof Exception) {
TransportCallback.handle(callback, null, (Exception)response);
}
else {
Element responseXml = provider.toXML(response);
cdrManager.append(id, responseXml.asXML());
TransportCallback.handle(callback, responseXml, null);
}
}
});
} catch (Exception e) {
log.error("Failed to handle incoming command [id=%s, componentId=%s, command=%s]", id, componentId, command, e);
TransportCallback.handle(callback, null, e);
}
}
private void handleDialCommand(Object command, final TransportCallback callback) {
// Quiesce Enabled
if ((adminService.isQuiesceMode())) {
log.warn("Quiesce Mode ON. Rejecting <dial/> command [command=%s]", command);
TransportCallback.handle(callback, null, new RayoProtocolException(
Condition.SERVICE_UNAVAILABLE, "This node has been quiesced"
));
// Outbound Disabled
} else if (!adminService.isOutgoingCallsAllowed()) {
log.warn("Outbound calls disabled. Rejecting <dial/> command [command=%s]]", command);
if(callback != null) {
TransportCallback.handle(callback, null, new RayoProtocolException(
Condition.SERVICE_UNAVAILABLE, "This node is not allowing outbound calls"
));
}
// Dial Allowed
} else {
callManager.publish(new Request(command, new ResponseHandler() {
public void handle(Response response) throws Exception {
if (response.isSuccess()) {
CallRef callRef = (CallRef) response.getValue();
Element callRefElement = provider.toXML(callRef);
cdrManager.append(callRef.getCallId(), callRefElement.asXML());
TransportCallback.handle(callback, callRefElement, null);
}
else {
TransportCallback.handle(callback, null, (Exception)response.getValue());
}
}
}));
}
}
private AbstractActor<?> findActor(String id) throws NotFoundException {
CallActor<?> callActor = callRegistry.get(id);
if (callActor != null) {
return callActor;
}
MixerActor mixerActor = mixerRegistry.get(id);
if (mixerActor != null) {
return mixerActor;
}
throw new NotFoundException("Could not find a matching call or mixer [id=%s]", id);
}
public XmlProvider getProvider() {
return provider;
}
public void setProvider(XmlProvider provider) {
this.provider = provider;
}
public CallManager getCallManager() {
return callManager;
}
public void setCallManager(CallManager callManager) {
this.callManager = callManager;
}
public CallRegistry getCallRegistry() {
return callRegistry;
}
public void setCallRegistry(CallRegistry callRegistry) {
this.callRegistry = callRegistry;
}
public MixerRegistry getMixerRegistry() {
return mixerRegistry;
}
public void setMixerRegistry(MixerRegistry mixerRegistry) {
this.mixerRegistry = mixerRegistry;
}
public RayoStatistics getRayoStatistics() {
return rayoStatistics;
}
public void setRayoStatistics(RayoStatistics rayoStatistics) {
this.rayoStatistics = rayoStatistics;
}
public CdrManager getCdrManager() {
return cdrManager;
}
public void setCdrManager(CdrManager cdrManager) {
this.cdrManager = cdrManager;
}
public FilterChain getFiltersChain() {
return filtersChain;
}
public void setFiltersChain(FilterChain filtersChain) {
this.filtersChain = filtersChain;
}
public RayoAdminService getAdminService() {
return adminService;
}
public void setAdminService(RayoAdminService adminService) {
this.adminService = adminService;
}
public List<Transport> getTransports() {
return transports;
}
public void setTransports(List<Transport> transports) {
this.transports = transports;
}
public void addTransport(Transport transport) {
transports.add(transport);
}
}