All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.restcomm.connect.interpreter.ConfVoiceInterpreter Maven / Gradle / Ivy

There is a newer version: 8.4.0-227
Show newest version
package org.restcomm.connect.interpreter;

import akka.actor.Actor;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.ReceiveTimeout;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorContext;
import akka.actor.UntypedActorFactory;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import org.apache.commons.configuration.Configuration;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.joda.time.DateTime;
import org.restcomm.connect.commons.cache.DiskCacheFactory;
import org.restcomm.connect.commons.cache.DiskCacheRequest;
import org.restcomm.connect.commons.cache.DiskCacheResponse;
import org.restcomm.connect.commons.cache.HashGenerator;
import org.restcomm.connect.commons.dao.Sid;
import org.restcomm.connect.commons.faulttolerance.RestcommUntypedActor;
import org.restcomm.connect.commons.fsm.Action;
import org.restcomm.connect.commons.fsm.FiniteStateMachine;
import org.restcomm.connect.commons.fsm.State;
import org.restcomm.connect.commons.fsm.Transition;
import org.restcomm.connect.commons.patterns.Observe;
import org.restcomm.connect.dao.DaoManager;
import org.restcomm.connect.dao.NotificationsDao;
import org.restcomm.connect.dao.entities.CallDetailRecord;
import org.restcomm.connect.dao.entities.Notification;
import org.restcomm.connect.email.EmailService;
import org.restcomm.connect.email.api.EmailRequest;
import org.restcomm.connect.email.api.Mail;
import org.restcomm.connect.http.client.Downloader;
import org.restcomm.connect.http.client.DownloaderResponse;
import org.restcomm.connect.http.client.HttpRequestDescriptor;
import org.restcomm.connect.http.client.HttpResponseDescriptor;
import org.restcomm.connect.interpreter.rcml.Attribute;
import org.restcomm.connect.interpreter.rcml.End;
import org.restcomm.connect.interpreter.rcml.GetNextVerb;
import org.restcomm.connect.interpreter.rcml.Parser;
import org.restcomm.connect.interpreter.rcml.Tag;
import org.restcomm.connect.interpreter.rcml.Verbs;
import org.restcomm.connect.mscontrol.api.messages.CreateMediaGroup;
import org.restcomm.connect.mscontrol.api.messages.MediaGroupResponse;
import org.restcomm.connect.mscontrol.api.messages.MediaGroupStateChanged;
import org.restcomm.connect.mscontrol.api.messages.MediaServerControllerResponse;
import org.restcomm.connect.mscontrol.api.messages.Play;
import org.restcomm.connect.mscontrol.api.messages.StartMediaGroup;
import org.restcomm.connect.mscontrol.api.messages.StopMediaGroup;
import org.restcomm.connect.telephony.api.CallInfo;
import org.restcomm.connect.telephony.api.CallStateChanged;
import org.restcomm.connect.telephony.api.DestroyWaitUrlConfMediaGroup;
import org.restcomm.connect.tts.api.GetSpeechSynthesizerInfo;
import org.restcomm.connect.tts.api.SpeechSynthesizerInfo;
import org.restcomm.connect.tts.api.SpeechSynthesizerRequest;
import org.restcomm.connect.tts.api.SpeechSynthesizerResponse;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.restcomm.connect.interpreter.rcml.Verbs.play;
import static org.restcomm.connect.interpreter.rcml.Verbs.say;

public class ConfVoiceInterpreter extends RestcommUntypedActor {
    private static final int ERROR_NOTIFICATION = 0;
    private static final int WARNING_NOTIFICATION = 1;
    static String EMAIL_SENDER;

    // Logger.
    private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
    // States for the FSM.
    private final State uninitialized;
    private final State acquiringSynthesizerInfo;
    private final State downloadingRcml;
    private final State initializingConfMediaGroup;
    private final State acquiringConfMediaGroup;
    private final State ready;
    private final State notFound;

    private final State caching;
    private final State checkingCache;
    private final State playing;
    private final State synthesizing;
    private final State redirecting;

    private final State finished;
    // FSM.
    private final FiniteStateMachine fsm;
    // The user specific configuration.
    private final Configuration configuration;
    // The block storage cache.
    private final ActorRef cache;
    private final String cachePath;
    // The downloader will fetch resources for us using HTTP.
    private final ActorRef downloader;
    // The mail man that will deliver e-mail.
    private ActorRef mailerNotify = null;

    // The storage engine.
    private final DaoManager storage;
    // The text to speech synthesizer service.
    private final ActorRef synthesizer;

    // The languages supported by the text to speech synthesizer service.
    private SpeechSynthesizerInfo synthesizerInfo;

    // The conference being handled by this interpreter.
    private ActorRef conference;
    private ActorRef conferenceMediaGroup;
    // The information for this call.
    private CallInfo callInfo;

    // The call state.
    private CallStateChanged.State callState;

    // A call detail record.
    private CallDetailRecord callRecord;

    // Information to reach the application that will be executed
    // by this interpreter.
    private final Sid accountId;
    private final String version;
    private final URI url;
    private final String method;
    private final String emailAddress;
    // application data.
    private HttpRequestDescriptor request;
    private HttpResponseDescriptor response;
    private DownloaderResponse downloaderResponse;
    // The RCML parser.
    private ActorRef parser;
    private ActorRef source;
    private Tag verb;

    private ActorRef originalInterpreter;

    public ConfVoiceInterpreter(final ConfVoiceInterpreterParams params) {

        super();

        source = self();
        uninitialized = new State("uninitialized", null, null);

        acquiringSynthesizerInfo = new State("acquiring tts info", new AcquiringSpeechSynthesizerInfo(source), null);
        acquiringConfMediaGroup = new State("acquiring call media group", new AcquiringConferenceMediaGroup(source), null);
        downloadingRcml = new State("downloading rcml", new DownloadingRcml(source), null);
        initializingConfMediaGroup = new State("initializing call media group", new InitializingConferenceMediaGroup(source),
                null);
        ready = new State("ready", new Ready(source), null);
        notFound = new State("notFound", new NotFound(source), null);

        caching = new State("caching", new Caching(source), null);
        checkingCache = new State("checkingCache", new CheckCache(source), null);
        playing = new State("playing", new Playing(source), null);
        synthesizing = new State("synthesizing", new Synthesizing(source), null);
        redirecting = new State("redirecting", new Redirecting(source), null);

        finished = new State("finished", new Finished(source), null);

        // Initialize the transitions for the FSM.
        final Set transitions = new HashSet();
        transitions.add(new Transition(uninitialized, acquiringSynthesizerInfo));
        transitions.add(new Transition(uninitialized, finished));

        transitions.add(new Transition(acquiringSynthesizerInfo, finished));
        transitions.add(new Transition(acquiringSynthesizerInfo, downloadingRcml));

        transitions.add(new Transition(acquiringConfMediaGroup, initializingConfMediaGroup));
        transitions.add(new Transition(acquiringConfMediaGroup, finished));

        transitions.add(new Transition(initializingConfMediaGroup, downloadingRcml));
        transitions.add(new Transition(initializingConfMediaGroup, checkingCache));
        transitions.add(new Transition(initializingConfMediaGroup, caching));
        transitions.add(new Transition(initializingConfMediaGroup, synthesizing));
        transitions.add(new Transition(initializingConfMediaGroup, redirecting));
        transitions.add(new Transition(initializingConfMediaGroup, finished));
        transitions.add(new Transition(initializingConfMediaGroup, ready));
        transitions.add(new Transition(downloadingRcml, ready));
        transitions.add(new Transition(downloadingRcml, notFound));
        transitions.add(new Transition(downloadingRcml, finished));
        transitions.add(new Transition(downloadingRcml, acquiringConfMediaGroup));

        transitions.add(new Transition(ready, checkingCache));
        transitions.add(new Transition(ready, caching));
        transitions.add(new Transition(ready, synthesizing));
        transitions.add(new Transition(ready, redirecting));
        transitions.add(new Transition(ready, finished));

        transitions.add(new Transition(caching, playing));
        transitions.add(new Transition(caching, caching));
        transitions.add(new Transition(caching, redirecting));
        transitions.add(new Transition(caching, synthesizing));

        transitions.add(new Transition(caching, finished));
        transitions.add(new Transition(checkingCache, synthesizing));
        transitions.add(new Transition(checkingCache, playing));
        transitions.add(new Transition(checkingCache, checkingCache));
        transitions.add(new Transition(playing, ready));
        transitions.add(new Transition(playing, finished));

        transitions.add(new Transition(synthesizing, checkingCache));
        transitions.add(new Transition(synthesizing, caching));
        transitions.add(new Transition(synthesizing, redirecting));

        transitions.add(new Transition(synthesizing, synthesizing));
        transitions.add(new Transition(synthesizing, finished));

        transitions.add(new Transition(redirecting, ready));
        transitions.add(new Transition(redirecting, checkingCache));
        transitions.add(new Transition(redirecting, caching));
        transitions.add(new Transition(redirecting, synthesizing));
        transitions.add(new Transition(redirecting, redirecting));

        transitions.add(new Transition(redirecting, finished));

        // Initialize the FSM.
        this.fsm = new FiniteStateMachine(uninitialized, transitions);
        // Initialize the runtime stuff.
        this.accountId = params.getAccount();
        this.version = params.getVersion();
        this.url = params.getUrl();
        this.method = params.getMethod();
        this.emailAddress = params.getEmailAddress();
        this.configuration = params.getConfiguration();

        this.storage = params.getStorage();
        this.synthesizer = tts(configuration.subset("speech-synthesizer"));
        final Configuration runtime = configuration.subset("runtime-settings");
        String path = runtime.getString("cache-path");
        if (!path.endsWith("/")) {
            path = path + "/";
        }
        path = path + accountId.toString();
        cachePath = path;
        String uri = runtime.getString("cache-uri");
        if (!uri.endsWith("/")) {
            uri = uri + "/";
        }
        uri = uri + accountId.toString();
        this.cache = cache(path, uri);
        this.downloader = downloader();

        this.callInfo = params.getCallInfo();
        this.conference = params.getConference();
    }

    public static Props props(final ConfVoiceInterpreterParams params) {
        return new Props(new UntypedActorFactory() {
            @Override
            public Actor create() throws Exception {
                return new ConfVoiceInterpreter(params);
            }
        });
    }

    private ActorRef cache(final String path, final String uri) {
        final Props props = new Props(new UntypedActorFactory() {
            private static final long serialVersionUID = 1L;

            @Override
            public UntypedActor create() throws Exception {
                return new DiskCacheFactory(configuration).getDiskCache(path, uri);
            }
        });
        return getContext().actorOf(props);
    }

    private ActorRef downloader() {
        final Props props = new Props(new UntypedActorFactory() {
            private static final long serialVersionUID = 1L;

            @Override
            public UntypedActor create() throws Exception {
                return new Downloader();
            }
        });
        return getContext().actorOf(props);
    }

    private String e164(final String number) {
        final PhoneNumberUtil numbersUtil = PhoneNumberUtil.getInstance();
        try {
            final PhoneNumber result = numbersUtil.parse(number, "US");
            return numbersUtil.format(result, PhoneNumberFormat.E164);
        } catch (final NumberParseException ignored) {
            return number;
        }
    }

    private void invalidVerb(final Tag verb) {
        final ActorRef self = self();
        // Get the next verb.
        final GetNextVerb next = new GetNextVerb();
        parser.tell(next, self);
    }

    ActorRef mailer(final Configuration configuration) {
        final Props props = new Props(new UntypedActorFactory() {
            private static final long serialVersionUID = 1L;

            @Override
            public Actor create() throws Exception {
                return new EmailService(configuration);
            }
        });
        return getContext().actorOf(props);
    }

    private Notification notification(final int log, final int error, final String message) {
        final Notification.Builder builder = Notification.builder();
        final Sid sid = Sid.generate(Sid.Type.NOTIFICATION);
        builder.setSid(sid);
        builder.setAccountSid(accountId);
        builder.setCallSid(callInfo.sid());
        builder.setApiVersion(version);
        builder.setLog(log);
        builder.setErrorCode(error);
        final String base = configuration.subset("runtime-settings").getString("error-dictionary-uri");
        StringBuilder buffer = new StringBuilder();
        buffer.append(base);
        if (!base.endsWith("/")) {
            buffer.append("/");
        }
        buffer.append(error).append(".html");
        final URI info = URI.create(buffer.toString());
        builder.setMoreInfo(info);
        builder.setMessageText(message);
        final DateTime now = DateTime.now();
        builder.setMessageDate(now);
        if (request != null) {
            builder.setRequestUrl(request.getUri());
            builder.setRequestMethod(request.getMethod());
            builder.setRequestVariables(request.getParametersAsString());
        }
        if (response != null) {
            builder.setResponseHeaders(response.getHeadersAsString());
            final String type = response.getContentType();
            if (type.contains("text/xml") || type.contains("application/xml") || type.contains("text/html")) {
                try {
                    builder.setResponseBody(response.getContentAsString());
                } catch (final IOException exception) {
                    logger.error(
                            "There was an error while reading the contents of the resource " + "located @ " + url.toString(),
                            exception);
                }
            }
        }
        buffer = new StringBuilder();
        buffer.append("/").append(version).append("/Accounts/");
        buffer.append(accountId.toString()).append("/Notifications/");
        buffer.append(sid.toString());
        final URI uri = URI.create(buffer.toString());
        builder.setUri(uri);
        return builder.build();
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onReceive(final Object message) throws Exception {
        final Class klass = message.getClass();
        final State state = fsm.state();
        final ActorRef sender = sender();

        if (logger.isInfoEnabled()) {
            logger.info(" ********** ConfVoiceInterpreter's Current State: " + state.toString());
            logger.info(" ********** ConfVoiceInterpreter's Processing Message: " + klass.getName());
        }

        if (StartInterpreter.class.equals(klass)) {
            originalInterpreter = sender;
            fsm.transition(message, acquiringSynthesizerInfo);
        } else if (SpeechSynthesizerResponse.class.equals(klass)) {
            if (acquiringSynthesizerInfo.equals(state)) {
                fsm.transition(message, downloadingRcml);
            } else if (synthesizing.equals(state)) {
                final SpeechSynthesizerResponse response = (SpeechSynthesizerResponse) message;
                if (response.succeeded()) {
                    fsm.transition(message, caching);
                } else {
                    fsm.transition(message, finished);
                }
            }
        } else if (MediaServerControllerResponse.class.equals(klass)) {
            if (acquiringConfMediaGroup.equals(state)) {
                fsm.transition(message, initializingConfMediaGroup);
            }
        } else if (DownloaderResponse.class.equals(klass)) {
            downloaderResponse = (DownloaderResponse) message;
            if (logger.isDebugEnabled()){
                logger.debug("response succeeded " + downloaderResponse.succeeded() + ", statusCode "
                    + downloaderResponse.get().getStatusCode());
            }
            if (downloaderResponse.succeeded() && HttpStatus.SC_OK == downloaderResponse.get().getStatusCode()) {
                fsm.transition(message, acquiringConfMediaGroup);
            } else if (downloaderResponse.succeeded() && HttpStatus.SC_NOT_FOUND == downloaderResponse.get().getStatusCode()) {
                fsm.transition(message, notFound);
            }
        } else if (MediaGroupStateChanged.class.equals(klass)) {
            final MediaGroupStateChanged event = (MediaGroupStateChanged) message;
            if (MediaGroupStateChanged.State.ACTIVE == event.state()) {
                if (initializingConfMediaGroup.equals(state)) {
                    fsm.transition(message, ready);
                } else if (ready.equals(state)) {
                    if (play.equals(verb.name())) {
                        fsm.transition(message, caching);
                    } else if (say.equals(verb.name())) {
                        fsm.transition(message, checkingCache);
                    } else {
                        invalidVerb(verb);
                    }
                }
            } else if (MediaGroupStateChanged.State.INACTIVE == event.state()) {
                if (acquiringConfMediaGroup.equals(state)) {
                    fsm.transition(message, initializingConfMediaGroup);
                } else if (!finished.equals(state)) {
                    fsm.transition(message, finished);
                }
            }
        } else if (DiskCacheResponse.class.equals(klass)) {
            final DiskCacheResponse response = (DiskCacheResponse) message;
            if(logger.isInfoEnabled()){
                logger.info("DiskCacheResponse " + response.succeeded() + " error=" + response.error());
            }
            if (response.succeeded()) {
                if (caching.equals(state) || checkingCache.equals(state)) {
                    if (play.equals(verb.name()) || say.equals(verb.name())) {
                        fsm.transition(message, playing);
                    }
                }
            } else {

                if (checkingCache.equals(state)) {
                    fsm.transition(message, synthesizing);
                } else {
                    fsm.transition(message, finished);
                }
            }
        } else if (Tag.class.equals(klass)) {
            verb = (Tag) message;
            if(logger.isInfoEnabled()) {
                logger.info("ConfVoiceInterpreter verb = " + verb.name());
            }

            if (Verbs.dial.equals(verb.name()))
                originalInterpreter.tell(new Exception("Dial verb not supported"), source);

            if (play.equals(verb.name())) {
                fsm.transition(message, caching);
            } else if (say.equals(verb.name())) {
                fsm.transition(message, checkingCache);
            } else {
                invalidVerb(verb);
            }
        } else if (End.class.equals(klass)) {
            // TODO kill this interpreter and also the MediaGroup
            fsm.transition(message, finished);
            // originalInterpreter.tell(message, source);
        } else if (MediaGroupResponse.class.equals(klass)) {
            final MediaGroupResponse response = (MediaGroupResponse) message;
            if (response.succeeded()) {
                if (playing.equals(state)) {
                    fsm.transition(message, ready);
                }
            } else {
                originalInterpreter.tell(message, source);
            }
        } else if (StopInterpreter.class.equals(klass)) {
            fsm.transition(message, finished);
        } else if (message instanceof ReceiveTimeout) {
            // TODO?
        }
    }

    private List parameters() {
        final List parameters = new ArrayList();
        final String callSid = callInfo.sid().toString();
        parameters.add(new BasicNameValuePair("CallSid", callSid));
        final String accountSid = accountId.toString();
        parameters.add(new BasicNameValuePair("AccountSid", accountSid));
        final String from = e164(callInfo.from());
        parameters.add(new BasicNameValuePair("From", from));
        final String to = e164(callInfo.to());
        parameters.add(new BasicNameValuePair("To", to));
        // final String state = callState.toString();
        // parameters.add(new BasicNameValuePair("CallStatus", state));
        parameters.add(new BasicNameValuePair("ApiVersion", version));
        final String direction = callInfo.direction();
        parameters.add(new BasicNameValuePair("Direction", direction));
        final String callerName = callInfo.fromName();
        parameters.add(new BasicNameValuePair("CallerName", callerName));
        final String forwardedFrom = callInfo.forwardedFrom();
        parameters.add(new BasicNameValuePair("ForwardedFrom", forwardedFrom));
        return parameters;
    }

    private ActorRef parser(final String xml) {
        final Props props = new Props(new UntypedActorFactory() {
            private static final long serialVersionUID = 1L;

            @Override
            public UntypedActor create() throws Exception {
                return new Parser(xml, self());
            }
        });
        return getContext().actorOf(props);
    }

    private void postCleanup() {
        final ActorRef self = self();
        final UntypedActorContext context = getContext();
        context.stop(self);
    }

    private URI resolve(final URI base, final URI uri) {
        if (base.equals(uri)) {
            return uri;
        } else {
            if (!uri.isAbsolute()) {
                return base.resolve(uri);
            } else {
                return uri;
            }
        }
    }

    private void sendMail(final Notification notification) {
        if (emailAddress == null || emailAddress.isEmpty()) {
            return;
        }

        final String EMAIL_SUBJECT = "RestComm Error Notification - Attention Required";
        final StringBuilder buffer = new StringBuilder();
        buffer.append("").append("Sid: ").append("
"); buffer.append(notification.getSid().toString()).append("
"); buffer.append("").append("Account Sid: ").append("
"); buffer.append(notification.getAccountSid().toString()).append("
"); buffer.append("").append("Call Sid: ").append("
"); buffer.append(notification.getCallSid().toString()).append("
"); buffer.append("").append("API Version: ").append("
"); buffer.append(notification.getApiVersion()).append("
"); buffer.append("").append("Log: ").append("
"); buffer.append(notification.getLog() == ERROR_NOTIFICATION ? "ERROR" : "WARNING").append("
"); buffer.append("").append("Error Code: ").append("
"); buffer.append(notification.getErrorCode()).append("
"); buffer.append("").append("More Information: ").append("
"); buffer.append(notification.getMoreInfo().toString()).append("
"); buffer.append("").append("Message Text: ").append("
"); buffer.append(notification.getMessageText()).append("
"); buffer.append("").append("Message Date: ").append("
"); buffer.append(notification.getMessageDate().toString()).append("
"); buffer.append("").append("Request URL: ").append("
"); buffer.append(notification.getRequestUrl().toString()).append("
"); buffer.append("").append("Request Method: ").append("
"); buffer.append(notification.getRequestMethod()).append("
"); buffer.append("").append("Request Variables: ").append("
"); buffer.append(notification.getRequestVariables()).append("
"); buffer.append("").append("Response Headers: ").append("
"); buffer.append(notification.getResponseHeaders()).append("
"); buffer.append("").append("Response Body: ").append("
"); buffer.append(notification.getResponseBody()).append("
"); final Mail emailMsg = new Mail(EMAIL_SENDER,emailAddress,EMAIL_SUBJECT, buffer.toString()); if (mailerNotify == null){ mailerNotify = mailer(configuration.subset("smtp-notify")); } mailerNotify.tell(new EmailRequest(emailMsg), self()); } private ActorRef tts(final Configuration configuration) { final String classpath = configuration.getString("[@class]"); final Props props = new Props(new UntypedActorFactory() { private static final long serialVersionUID = 1L; @Override public Actor create() throws Exception { return (UntypedActor) Class.forName(classpath).getConstructor(Configuration.class).newInstance(configuration); } }); return getContext().actorOf(props); } private abstract class AbstractAction implements Action { protected final ActorRef source; public AbstractAction(final ActorRef source) { super(); this.source = source; } } private final class AcquiringSpeechSynthesizerInfo extends AbstractAction { public AcquiringSpeechSynthesizerInfo(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final StartInterpreter request = (StartInterpreter) message; conference = request.resource(); synthesizer.tell(new GetSpeechSynthesizerInfo(), source); } } private final class AcquiringConferenceMediaGroup extends AbstractAction { public AcquiringConferenceMediaGroup(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { conference.tell(new CreateMediaGroup(), source); } } private final class InitializingConferenceMediaGroup extends AbstractAction { public InitializingConferenceMediaGroup(final ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(final Object message) throws Exception { final MediaServerControllerResponse response = (MediaServerControllerResponse) message; conferenceMediaGroup = response.get(); conferenceMediaGroup.tell(new Observe(source), source); final StartMediaGroup request = new StartMediaGroup(); conferenceMediaGroup.tell(request, source); } } private final class DownloadingRcml extends AbstractAction { public DownloadingRcml(final ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(final Object message) throws Exception { final Class klass = message.getClass(); if (SpeechSynthesizerResponse.class.equals(klass)) { final SpeechSynthesizerResponse response = (SpeechSynthesizerResponse) message; synthesizerInfo = response.get(); // Ask the downloader to get us the application that will be // executed. final List parameters = parameters(); request = new HttpRequestDescriptor(url, method, parameters); downloader.tell(request, source); } } } private final class Ready extends AbstractAction { public Ready(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { if (parser == null) { response = downloaderResponse.get(); final String type = response.getContentType(); if (type.contains("text/xml") || type.contains("application/xml") || type.contains("text/html")) { parser = parser(response.getContentAsString()); } else if (type.contains("audio/wav") || type.contains("audio/wave") || type.contains("audio/x-wav")) { parser = parser("" + request.getUri() + ""); } else if (type.contains("text/plain")) { parser = parser("" + response.getContentAsString() + ""); } else { final StopInterpreter stop = new StopInterpreter(); source.tell(stop, source); return; } } // Ask the parser for the next action to take. final GetNextVerb next = new GetNextVerb(); parser.tell(next, source); } } private final class NotFound extends AbstractAction { public NotFound(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final DownloaderResponse response = (DownloaderResponse) message; if (logger.isDebugEnabled()) { logger.debug("response succeeded " + response.succeeded() + ", statusCode " + response.get().getStatusCode()); } final Notification notification = notification(WARNING_NOTIFICATION, 21402, "URL Not Found : " + response.get().getURI()); final NotificationsDao notifications = storage.getNotificationsDao(); notifications.addNotification(notification); // Hang up the call. conference.tell(new org.restcomm.connect.telephony.api.NotFound(), source); } } private final class CheckCache extends AbstractAction { public CheckCache(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final Class klass = message.getClass(); if (Tag.class.equals(klass)) { verb = (Tag) message; } String hash = hash(verb); DiskCacheRequest request = new DiskCacheRequest(hash); if (logger.isInfoEnabled()) { logger.info("Checking cache for hash: " + hash); } cache.tell(request, source); } } private final class Caching extends AbstractAction { public Caching(final ActorRef source) { super(source); } @SuppressWarnings("unchecked") @Override public void execute(final Object message) throws Exception { final Class klass = message.getClass(); if (SpeechSynthesizerResponse.class.equals(klass)) { final SpeechSynthesizerResponse response = (SpeechSynthesizerResponse) message; final DiskCacheRequest request = new DiskCacheRequest(response.get()); cache.tell(request, source); } else if (Tag.class.equals(klass) || MediaGroupStateChanged.class.equals(klass)) { if (Tag.class.equals(klass)) { verb = (Tag) message; } // Parse the URL. final String text = verb.text(); if (text != null && !text.isEmpty()) { // Try to cache the media. URI target = null; try { target = URI.create(text); } catch (final Exception exception) { final Notification notification = notification(ERROR_NOTIFICATION, 11100, text + " is an invalid URI."); final NotificationsDao notifications = storage.getNotificationsDao(); notifications.addNotification(notification); sendMail(notification); final StopInterpreter stop = new StopInterpreter(); source.tell(stop, source); return; } final URI base = request.getUri(); final URI uri = resolve(base, target); final DiskCacheRequest request = new DiskCacheRequest(uri); cache.tell(request, source); } else { // Ask the parser for the next action to take. final GetNextVerb next = new GetNextVerb(); parser.tell(next, source); } } } } private final class Playing extends AbstractAction { public Playing(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final Class klass = message.getClass(); if (DiskCacheResponse.class.equals(klass)) { // Issue 202: https://bitbucket.org/telestax/telscale-restcomm/issue/202 // Parse the loop attribute. int loop = Integer.MAX_VALUE; final Attribute attribute = verb.attribute("loop"); if (attribute != null) { final String number = attribute.value(); if (number != null && !number.isEmpty()) { try { loop = Integer.parseInt(number); } catch (final NumberFormatException ignored) { final NotificationsDao notifications = storage.getNotificationsDao(); Notification notification = null; if (say.equals(verb.name())) { notification = notification(WARNING_NOTIFICATION, 13510, loop + " is an invalid loop value."); notifications.addNotification(notification); } else if (play.equals(verb.name())) { notification = notification(WARNING_NOTIFICATION, 13410, loop + " is an invalid loop value."); notifications.addNotification(notification); } } } } final DiskCacheResponse response = (DiskCacheResponse) message; final Play play = new Play(response.get(), loop); conferenceMediaGroup.tell(play, source); } } } private String hash(Object message) { Map details = getSynthesizeDetails(message); if (details == null) { if (logger.isInfoEnabled()) { logger.info("Cannot generate hash, details are null"); } return null; } String voice = details.get("voice"); String language = details.get("language"); String text = details.get("text"); return HashGenerator.hashMessage(voice, language, text); } private Map getSynthesizeDetails(final Object message) { final Class klass = message.getClass(); Map details = new HashMap(); if (Tag.class.equals(klass)) { verb = (Tag) message; } else { return null; } if (!say.equals(verb.name())) return null; // Parse the voice attribute. String voice = "man"; Attribute attribute = verb.attribute("voice"); if (attribute != null) { voice = attribute.value(); if (voice != null && !voice.isEmpty()) { if (!"man".equals(voice) && !"woman".equals(voice)) { final Notification notification = notification(WARNING_NOTIFICATION, 13511, voice + " is an invalid voice value."); final NotificationsDao notifications = storage.getNotificationsDao(); notifications.addNotification(notification); voice = "man"; } } else { voice = "man"; } } // Parse the language attribute. String language = "en"; attribute = verb.attribute("language"); if (attribute != null) { language = attribute.value(); if (language != null && !language.isEmpty()) { if (!synthesizerInfo.languages().contains(language)) { language = "en"; } } else { language = "en"; } } // Synthesize. String text = verb.text(); details.put("voice", voice); details.put("language", language); details.put("text", text); return details; } private final class Synthesizing extends AbstractAction { public Synthesizing(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final Class klass = message.getClass(); if (Tag.class.equals(klass)) { verb = (Tag) message; } Map details = getSynthesizeDetails(verb); if (details != null && !details.isEmpty()) { String voice = details.get("voice"); String language = details.get("language"); String text = details.get("text"); final SpeechSynthesizerRequest synthesize = new SpeechSynthesizerRequest(voice, language, text); synthesizer.tell(synthesize, source); } else { // Ask the parser for the next action to take. final GetNextVerb next = new GetNextVerb(); parser.tell(next, source); } } } private final class Redirecting extends AbstractAction { public Redirecting(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { final Class klass = message.getClass(); if (Tag.class.equals(klass)) { verb = (Tag) message; } final NotificationsDao notifications = storage.getNotificationsDao(); String method = "POST"; Attribute attribute = verb.attribute("method"); if (attribute != null) { method = attribute.value(); if (method != null && !method.isEmpty()) { if (!"GET".equalsIgnoreCase(method) && !"POST".equalsIgnoreCase(method)) { final Notification notification = notification(WARNING_NOTIFICATION, 13710, method + " is not a valid HTTP method for "); notifications.addNotification(notification); method = "POST"; } } else { method = "POST"; } } final String text = verb.text(); if (text != null && !text.isEmpty()) { // Try to redirect. URI target = null; try { target = URI.create(text); } catch (final Exception exception) { final Notification notification = notification(ERROR_NOTIFICATION, 11100, text + " is an invalid URI."); notifications.addNotification(notification); sendMail(notification); final StopInterpreter stop = new StopInterpreter(); source.tell(stop, source); return; } final URI base = request.getUri(); final URI uri = resolve(base, target); final List parameters = parameters(); request = new HttpRequestDescriptor(uri, method, parameters); downloader.tell(request, source); } else { // Ask the parser for the next action to take. final GetNextVerb next = new GetNextVerb(); parser.tell(next, source); } } } private final class Finished extends AbstractAction { public Finished(final ActorRef source) { super(source); } @Override public void execute(final Object message) throws Exception { if(logger.isInfoEnabled()) { logger.info("Finished called for ConfVoiceInterpreter"); } final StopMediaGroup stop = new StopMediaGroup(); // Destroy the media group(s). if (conferenceMediaGroup != null) { conferenceMediaGroup.tell(stop, source); final DestroyWaitUrlConfMediaGroup destroy = new DestroyWaitUrlConfMediaGroup(conferenceMediaGroup); conference.tell(destroy, source); // conferenceMediaGroup = null; } // TODO should the dependencies be stopped here? // Stop the dependencies. final UntypedActorContext context = getContext(); if (mailerNotify != null) context.stop(mailerNotify); context.stop(downloader); context.stop(cache); context.stop(synthesizer); // Stop the interpreter. postCleanup(); } } @Override public void postStop() { // final StopMediaGroup stop = new StopMediaGroup(); // Destroy the media group(s). // if (conferenceMediaGroup != null) { // conferenceMediaGroup.tell(stop, source); // } // if (conference != null && !conference.isTerminated()) { // final DestroyWaitUrlConfMediaGroup destroy = new DestroyWaitUrlConfMediaGroup(conferenceMediaGroup); // conference.tell(destroy, source); // } // // if (conferenceMediaGroup != null && !conferenceMediaGroup.isTerminated()) // getContext().stop(conferenceMediaGroup); // // conferenceMediaGroup = null; super.postStop(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy