package com.rayo.server; import static com.voxeo.utils.Objects.iterable; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import com.rayo.core.AcceptCommand; import com.rayo.core.AnswerCommand; import com.rayo.core.EndEvent.Reason; import com.rayo.core.OfferEvent; import com.rayo.core.RedirectCommand; import com.rayo.core.RejectCommand; import com.rayo.core.exception.RecoverableException; import com.rayo.server.exception.RayoProtocolException; import com.rayo.server.exception.RayoProtocolException.Condition; import com.voxeo.moho.ApplicationContext; import com.voxeo.moho.Call; import com.voxeo.moho.Endpoint; import com.voxeo.moho.IncomingCall; import com.voxeo.moho.SignalException; import com.voxeo.moho.common.event.AutowiredEventListener; import com.voxeo.moho.event.AcceptableEvent; import com.voxeo.moho.sip.SIPCallImpl; public class IncomingCallActor extends CallActor<IncomingCall> { public IncomingCallActor(IncomingCall call) { super(call); } // Outgoing Calls // ================================================================================ @Message public void onCall(Call call) throws Exception { Map<String, String> headers = new HashMap<String, String>(); OfferEvent offer = new OfferEvent(getParticipantId()); offer.setFrom(call.getInvitor().getURI()); offer.setTo(call.getInvitee().getURI()); Iterator<String> headerNames = call.getHeaderNames(); for (String headerName : iterable(headerNames)) { if (headerName.equalsIgnoreCase("route")) { StringBuffer value = new StringBuffer(); for (String route : iterable(call.getHeaders(headerName))) { value.append(route).append("|||"); } headers.put(headerName, value.substring(0, value.lastIndexOf("|||"))); } else { headers.put(headerName, call.getHeader(headerName)); } } offer.setHeaders(headers); // Now we setup the moho handlers mohoListeners.add(new AutowiredEventListener(this)); call.addObserver(new ActorEventListener(this)); // Send the OfferEvent fire(offer); getCallStatistics().incomingCall(); // There is a tiny chance that the call ended before we could registered // the Moho handler. We need to check that the call is still active and // if it's not raise an EndEvent and dispose of the fiber. if (call.getCallState() != Call.State.ACCEPTED) { // TODO: There should be a way to tell why the call ended if we missed the event end(Reason.HANGUP); } } // Call Commands // ================================================================================ @Message public void accept(AcceptCommand message) { switch (((SIPCallImpl)participant).getSIPCallState()) { case RINGING: case ANSWERING: case ANSWERED: case REDIRECTED: case PROXIED: throw new RecoverableException("Call is already accepted"); case DISCONNECTED: case FAILED: case REJECTED: throw new RecoverableException("Call is either already disconnected or failed"); default: } Map<String, String> headers = message.getHeaders(); if (message.isEarlyMedia()) { try { participant.acceptWithEarlyMedia(headers); } catch (SignalException se) { throw new RayoProtocolException(Condition.CONFLICT,"Call does not accept early media"); } } else { participant.accept(headers); } getCallStatistics().callAccepted(); } @Message public void redirect(RedirectCommand message) throws Exception { if (isAnswered(participant)) { throw new IllegalStateException("You can't redirect a call that has already been answered"); } ApplicationContext applicationContext = participant.getApplicationContext(); Endpoint destination = applicationContext.createEndpoint(message.getTo().toString()); participant.redirect(destination, message.getHeaders()); getCallStatistics().callRedirected(); } @Message public void answer(AnswerCommand message) { switch (participant.getCallState()) { case CONNECTED : //NOOP This serves as support for early media (http://www.ietf.org/rfc/rfc3960.txt) break; case DISCONNECTED: case FAILED: throw new RecoverableException("Call is either already disconnected or failed"); default: participant.answer(message.getHeaders()); getCallStatistics().callAnswered(); } } @Message public void reject(RejectCommand message) { switch (message.getReason()) { case BUSY: participant.reject(AcceptableEvent.Reason.BUSY, message.getHeaders()); break; case DECLINE: participant.reject(AcceptableEvent.Reason.DECLINE, message.getHeaders()); break; case ERROR: participant.reject(AcceptableEvent.Reason.ERROR, message.getHeaders()); break; default: throw new UnsupportedOperationException("Reason not handled: " + message.getReason()); } } }