Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.restcomm.connect.interpreter.BaseVoiceInterpreter Maven / Gradle / Ivy
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2014, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see
*
*/
package org.restcomm.connect.interpreter;
import akka.actor.Actor;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorContext;
import akka.actor.UntypedActorFactory;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.util.Timeout;
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.commons.lang.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.joda.time.DateTime;
import org.restcomm.connect.asr.AsrInfo;
import org.restcomm.connect.asr.AsrRequest;
import org.restcomm.connect.asr.AsrResponse;
import org.restcomm.connect.asr.GetAsrInfo;
import org.restcomm.connect.asr.ISpeechAsr;
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.configuration.RestcommConfiguration;
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.commons.util.UriUtils;
import org.restcomm.connect.commons.util.WavUtils;
import org.restcomm.connect.dao.CallDetailRecordsDao;
import org.restcomm.connect.dao.DaoManager;
import org.restcomm.connect.dao.NotificationsDao;
import org.restcomm.connect.dao.RecordingsDao;
import org.restcomm.connect.dao.SmsMessagesDao;
import org.restcomm.connect.dao.TranscriptionsDao;
import org.restcomm.connect.dao.entities.CallDetailRecord;
import org.restcomm.connect.dao.entities.MediaAttributes;
import org.restcomm.connect.dao.entities.Notification;
import org.restcomm.connect.dao.entities.Recording;
import org.restcomm.connect.dao.entities.SmsMessage;
import org.restcomm.connect.dao.entities.SmsMessage.Direction;
import org.restcomm.connect.dao.entities.SmsMessage.Status;
import org.restcomm.connect.dao.entities.Transcription;
import org.restcomm.connect.email.EmailService;
import org.restcomm.connect.email.api.EmailRequest;
import org.restcomm.connect.email.api.EmailResponse;
import org.restcomm.connect.email.api.Mail;
import org.restcomm.connect.extension.api.ExtensionResponse;
import org.restcomm.connect.extension.api.ExtensionType;
import org.restcomm.connect.extension.api.IExtensionFeatureAccessRequest;
import org.restcomm.connect.extension.api.RestcommExtensionGeneric;
import org.restcomm.connect.extension.controller.ExtensionController;
import org.restcomm.connect.fax.FaxRequest;
import org.restcomm.connect.fax.InterfaxService;
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.GetNextVerb;
import org.restcomm.connect.interpreter.rcml.Parser;
import org.restcomm.connect.interpreter.rcml.ParserFailed;
import org.restcomm.connect.interpreter.rcml.Tag;
import org.restcomm.connect.interpreter.rcml.Verbs;
import org.restcomm.connect.interpreter.rcml.domain.GatherAttributes;
import org.restcomm.connect.mscontrol.api.messages.Collect;
import org.restcomm.connect.mscontrol.api.messages.CollectedResult;
import org.restcomm.connect.mscontrol.api.messages.MediaGroupResponse;
import org.restcomm.connect.mscontrol.api.messages.Play;
import org.restcomm.connect.mscontrol.api.messages.Record;
import org.restcomm.connect.sms.api.CreateSmsSession;
import org.restcomm.connect.sms.api.DestroySmsSession;
import org.restcomm.connect.sms.api.SmsServiceResponse;
import org.restcomm.connect.sms.api.SmsSessionAttribute;
import org.restcomm.connect.sms.api.SmsSessionInfo;
import org.restcomm.connect.sms.api.SmsSessionRequest;
import org.restcomm.connect.sms.api.SmsSessionResponse;
import org.restcomm.connect.telephony.api.CallInfo;
import org.restcomm.connect.telephony.api.CallManagerResponse;
import org.restcomm.connect.telephony.api.CallStateChanged;
import org.restcomm.connect.telephony.api.FeatureAccessRequest;
import org.restcomm.connect.telephony.api.GetCallInfo;
import org.restcomm.connect.telephony.api.Hangup;
import org.restcomm.connect.telephony.api.Reject;
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 scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import javax.servlet.sip.SipServletResponse;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import static akka.pattern.Patterns.ask;
/**
* @author [email protected] (Thomas Quintana)
* @author [email protected]
* @author [email protected]
* @author [email protected]
*/
public abstract class BaseVoiceInterpreter extends RestcommUntypedActor {
// Logger.
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
static final int ERROR_NOTIFICATION = 0;
static final int WARNING_NOTIFICATION = 1;
static final Pattern PATTERN = Pattern.compile("[\\*#0-9]{1,12}");
static String EMAIL_SENDER = "[email protected] ";
// States for the FSM.
// ==========================
final State uninitialized;
final State acquiringAsrInfo;
final State acquiringSynthesizerInfo;
final State acquiringCallInfo;
final State playingRejectionPrompt;
final State pausing;
final State caching;
final State checkingCache;
final State playing;
final State synthesizing;
final State redirecting;
final State faxing;
final State processingGatherChildren;
final State gathering;
final State finishGathering;
final State creatingRecording;
final State finishRecording;
final State creatingSmsSession;
final State sendingSms;
final State hangingUp;
final State sendingEmail;
final State continuousGathering;
// final State finished;
// FSM.
FiniteStateMachine fsm = null;
// The user specific configuration.
Configuration configuration = null;
// The block storage cache.
private ActorRef cache;
String cachePath = null;
// The downloader will fetch resources for us using HTTP.
ActorRef downloader = null;
// The mail man that will deliver e-mail.
ActorRef mailerNotify = null;
ActorRef mailerService = null;
// The call manager.
ActorRef callManager = null;
// The conference manager.
ActorRef conferenceManager = null;
// The automatic speech recognition service.
private ActorRef asrService;
int outstandingAsrRequests;
// The fax service.
private ActorRef faxService;
// The SMS service = null.
ActorRef smsService = null;
Map smsSessions = null;
// The storage engine.
DaoManager storage = null;
// The text to speech synthesizer service.
private ActorRef synthesizer;
// The languages supported by the automatic speech recognition service.
AsrInfo asrInfo = null;
// The languages supported by the text to speech synthesizer service.
SpeechSynthesizerInfo synthesizerInfo = null;
// The call being handled by this interpreter.
ActorRef call = null;
// The information for this call.
CallInfo callInfo = null;
// The call state.
CallStateChanged.State callState = null;
// The last outbound call response.
Integer outboundCallResponse = null;
// A call detail record.
CallDetailRecord callRecord = null;
// State for outbound calls.
ActorRef outboundCall = null;
CallInfo outboundCallInfo = null;
// State for the gather verb.
List gatherChildren = null;
List gatherPrompts = null;
// The call recording stuff.
Sid recordingSid = null;
URI recordingUri = null;
URI publicRecordingUri = null;
MediaAttributes.MediaType recordingMediaType = null;
// Information to reach the application that will be executed
// by this interpreter.
Sid accountId;
Sid phoneId;
String version;
URI url;
String method;
URI fallbackUrl;
String fallbackMethod;
URI referUrl;
String referMethod;
String referTarget;
String transferor;
String transferee;
URI viStatusCallback;
String viStatusCallbackMethod;
String emailAddress;
// application data.
HttpRequestDescriptor request;
HttpRequestDescriptor requestCallback;
HttpResponseDescriptor response;
// The RCML parser.
ActorRef parser;
Tag verb;
Tag gatherVerb;
Boolean processingGather = false;
Boolean dtmfReceived = false;
String finishOnKey;
int numberOfDigits = Short.MAX_VALUE;
StringBuffer collectedDigits;
String speechResult;
//Monitoring service
ActorRef monitoring;
//Sdr service
ActorRef sdr;
final Set transitions = new HashSet();
int recordingDuration = -1;
protected RestcommConfiguration restcommConfiguration;
//List of extensions for VoiceInterpreter
List extensions;
public BaseVoiceInterpreter() {
super();
restcommConfiguration = RestcommConfiguration.getInstance();
final ActorRef source = self();
// 20 States in common
uninitialized = new State("uninitialized", null, null);
acquiringAsrInfo = new State("acquiring asr info", new AcquiringAsrInfo(source), null);
acquiringSynthesizerInfo = new State("acquiring tts info", new AcquiringSpeechSynthesizerInfo(source), null);
acquiringCallInfo = new State("acquiring call info", new AcquiringCallInfo(source), null);
playingRejectionPrompt = new State("playing rejection prompt", new PlayingRejectionPrompt(source), null);
pausing = new State("pausing", new Pausing(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);
faxing = new State("faxing", new Faxing(source), null);
gathering = new State("gathering", new Gathering(source), null);
processingGatherChildren = new State("processing gather children", new ProcessingGatherChildren(source), null);
finishGathering = new State("finish gathering", new FinishGathering(source), null);
creatingRecording = new State("creating recording", new CreatingRecording(source), null);
finishRecording = new State("finish recording", new FinishRecording(source), null);
creatingSmsSession = new State("creating sms session", new CreatingSmsSession(source), null);
sendingSms = new State("sending sms", new SendingSms(source), null);
hangingUp = new State("hanging up", new HangingUp(source), null);
sendingEmail = new State("sending Email", new SendingEmail(source), null);
continuousGathering = new State("push partial result", new PartialGathering(source), null);
// Initialize the transitions for the FSM.
transitions.add(new Transition(uninitialized, acquiringAsrInfo));
transitions.add(new Transition(acquiringAsrInfo, acquiringSynthesizerInfo));
transitions.add(new Transition(acquiringSynthesizerInfo, acquiringCallInfo));
transitions.add(new Transition(pausing, hangingUp));
transitions.add(new Transition(playingRejectionPrompt, hangingUp));
transitions.add(new Transition(faxing, faxing));
transitions.add(new Transition(faxing, caching));
transitions.add(new Transition(faxing, pausing));
transitions.add(new Transition(faxing, redirecting));
transitions.add(new Transition(faxing, synthesizing));
transitions.add(new Transition(faxing, processingGatherChildren));
transitions.add(new Transition(faxing, creatingRecording));
transitions.add(new Transition(faxing, creatingSmsSession));
transitions.add(new Transition(faxing, hangingUp));
transitions.add(new Transition(sendingEmail, sendingEmail));
transitions.add(new Transition(sendingEmail, caching));
transitions.add(new Transition(sendingEmail, pausing));
transitions.add(new Transition(sendingEmail, redirecting));
transitions.add(new Transition(sendingEmail, synthesizing));
transitions.add(new Transition(sendingEmail, processingGatherChildren));
transitions.add(new Transition(sendingEmail, creatingRecording));
transitions.add(new Transition(sendingEmail, creatingSmsSession));
transitions.add(new Transition(sendingEmail, hangingUp));
transitions.add(new Transition(caching, faxing));
transitions.add(new Transition(caching, sendingEmail));
transitions.add(new Transition(caching, playing));
transitions.add(new Transition(caching, caching));
transitions.add(new Transition(caching, pausing));
transitions.add(new Transition(caching, redirecting));
transitions.add(new Transition(caching, synthesizing));
transitions.add(new Transition(caching, processingGatherChildren));
transitions.add(new Transition(caching, creatingRecording));
transitions.add(new Transition(caching, creatingSmsSession));
transitions.add(new Transition(caching, hangingUp));
transitions.add(new Transition(checkingCache, synthesizing));
transitions.add(new Transition(checkingCache, playing));
transitions.add(new Transition(checkingCache, checkingCache));
transitions.add(new Transition(playing, hangingUp));
transitions.add(new Transition(synthesizing, faxing));
transitions.add(new Transition(synthesizing, sendingEmail));
transitions.add(new Transition(synthesizing, pausing));
transitions.add(new Transition(synthesizing, checkingCache));
transitions.add(new Transition(synthesizing, caching));
transitions.add(new Transition(synthesizing, redirecting));
transitions.add(new Transition(synthesizing, processingGatherChildren));
transitions.add(new Transition(synthesizing, creatingRecording));
transitions.add(new Transition(synthesizing, creatingSmsSession));
transitions.add(new Transition(synthesizing, synthesizing));
transitions.add(new Transition(synthesizing, hangingUp));
transitions.add(new Transition(redirecting, faxing));
transitions.add(new Transition(redirecting, sendingEmail));
transitions.add(new Transition(redirecting, pausing));
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, processingGatherChildren));
transitions.add(new Transition(redirecting, creatingRecording));
transitions.add(new Transition(redirecting, creatingSmsSession));
transitions.add(new Transition(redirecting, hangingUp));
transitions.add(new Transition(creatingRecording, finishRecording));
transitions.add(new Transition(creatingRecording, hangingUp));
transitions.add(new Transition(finishRecording, faxing));
transitions.add(new Transition(finishRecording, sendingEmail));
transitions.add(new Transition(finishRecording, pausing));
transitions.add(new Transition(finishRecording, checkingCache));
transitions.add(new Transition(finishRecording, caching));
transitions.add(new Transition(finishRecording, synthesizing));
transitions.add(new Transition(finishRecording, redirecting));
transitions.add(new Transition(finishRecording, processingGatherChildren));
transitions.add(new Transition(finishRecording, creatingRecording));
transitions.add(new Transition(finishRecording, creatingSmsSession));
transitions.add(new Transition(finishRecording, hangingUp));
transitions.add(new Transition(processingGatherChildren, processingGatherChildren));
transitions.add(new Transition(processingGatherChildren, gathering));
transitions.add(new Transition(processingGatherChildren, synthesizing));
transitions.add(new Transition(processingGatherChildren, hangingUp));
transitions.add(new Transition(gathering, finishGathering));
transitions.add(new Transition(gathering, hangingUp));
transitions.add(new Transition(gathering, continuousGathering));
transitions.add(new Transition(continuousGathering, continuousGathering));
transitions.add(new Transition(continuousGathering, finishGathering));
transitions.add(new Transition(finishGathering, faxing));
transitions.add(new Transition(finishGathering, sendingEmail));
transitions.add(new Transition(finishGathering, pausing));
transitions.add(new Transition(finishGathering, checkingCache));
transitions.add(new Transition(finishGathering, caching));
transitions.add(new Transition(finishGathering, synthesizing));
transitions.add(new Transition(finishGathering, redirecting));
transitions.add(new Transition(finishGathering, processingGatherChildren));
transitions.add(new Transition(finishGathering, creatingRecording));
transitions.add(new Transition(finishGathering, creatingSmsSession));
transitions.add(new Transition(finishGathering, hangingUp));
transitions.add(new Transition(creatingSmsSession, sendingSms));
transitions.add(new Transition(creatingSmsSession, hangingUp));
transitions.add(new Transition(sendingSms, faxing));
transitions.add(new Transition(sendingSms, sendingEmail));
transitions.add(new Transition(sendingSms, pausing));
transitions.add(new Transition(sendingSms, caching));
transitions.add(new Transition(sendingSms, synthesizing));
transitions.add(new Transition(sendingSms, redirecting));
transitions.add(new Transition(sendingSms, processingGatherChildren));
transitions.add(new Transition(sendingSms, creatingRecording));
transitions.add(new Transition(sendingSms, creatingSmsSession));
transitions.add(new Transition(sendingSms, hangingUp));
extensions = ExtensionController.getInstance().getExtensions(ExtensionType.FeatureAccessControl);
}
@Override
public abstract void onReceive(Object arg0) throws Exception;
abstract List parameters();
protected URI resolve(URI uri){
return UriUtils.resolve(uri);
}
public ActorRef getAsrService() {
if (asrService == null || (asrService != null && asrService.isTerminated())) {
asrService = asr(configuration.subset("speech-recognizer"));
}
return asrService;
}
private boolean checkAsrService() {
boolean AsrActive=false;
Configuration Asrconfiguration=configuration.subset("speech-recognizer");
if (Asrconfiguration.getString("api-key") != null && !Asrconfiguration.getString("api-key").isEmpty()){
AsrActive=true;
}
return AsrActive;
}
ActorRef asr(final Configuration configuration) {
final Props props = new Props(new UntypedActorFactory() {
private static final long serialVersionUID = 1L;
@Override
public Actor create() throws Exception {
return new ISpeechAsr(configuration);
}
});
return getContext().actorOf(props);
}
@SuppressWarnings("unchecked")
void asrResponse(final Object message) {
final Class> klass = message.getClass();
if (AsrResponse.class.equals(klass)) {
final AsrResponse response = (AsrResponse) message;
Transcription transcription = (Transcription) response.attributes().get("transcription");
if (response.succeeded()) {
transcription = transcription.setStatus(Transcription.Status.COMPLETED);
if (response.get() != null ) {
transcription = transcription.setTranscriptionText(response.get());
}
} else {
transcription = transcription.setStatus(Transcription.Status.FAILED);
}
final TranscriptionsDao transcriptions = storage.getTranscriptionsDao();
transcriptions.updateTranscription(transcription);
// Notify the callback listener.
final Object attribute = response.attributes().get("callback");
if (attribute != null) {
final URI callback = (URI) attribute;
final List parameters = parameters();
request = new HttpRequestDescriptor(callback, "POST", parameters);
downloader.tell(request, null);
}
// Update pending asr responses.
outstandingAsrRequests--;
// Try to stop the interpreter.
postCleanup();
}
}
public ActorRef getFaxService() {
if (faxService == null || (faxService != null && faxService.isTerminated())) {
faxService = fax(configuration.subset("fax-service"));
}
return faxService;
}
ActorRef fax(final Configuration configuration) {
final Props props = new Props(new UntypedActorFactory() {
private static final long serialVersionUID = 1L;
@Override
public Actor create() throws Exception {
return new InterfaxService(configuration);
}
});
return getContext().actorOf(props);
}
LinkedList states = new LinkedList(Arrays.asList("queued", "ringing", "in-progress", "completed", "busy", "failed", "no-answer", "canceled"));
//Callback using the Akka ask pattern (http://doc.akka.io/docs/akka/2.2.5/java/untyped-actors.html#Ask__Send-And-Receive-Future) will force VoiceInterpter to wait until
//Downloader finish with this callback before shutdown everything. Issue https://github.com/Mobicents/RestComm/issues/437
void callback(boolean ask) {
if (viStatusCallback != null) {
if (states.remove(callState.toString().toLowerCase())) {
if (logger.isInfoEnabled()) {
logger.info("About to execute viStatusCallback: " + viStatusCallback.toString());
}
if (viStatusCallbackMethod == null) {
viStatusCallbackMethod = "POST";
}
final List parameters = parameters();
requestCallback = new HttpRequestDescriptor(viStatusCallback, viStatusCallbackMethod, parameters);
if (!ask) {
downloader.tell(requestCallback, null);
} else if (ask) {
final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
Future future = (Future) ask(downloader, requestCallback, timeout);
DownloaderResponse downloaderResponse = null;
try {
downloaderResponse = (DownloaderResponse) Await.result(future, Duration.create(10, TimeUnit.SECONDS));
} catch (Exception e) {
logger.error("Exception during callback with ask pattern");
}
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Status Callback has been already executed for state: ",callState.toString());
}
}
} else if(logger.isInfoEnabled()){
logger.info("status callback is null");
}
}
void callback() {
callback(false);
}
public ActorRef getCache() {
if (cache == null || (cache != null && cache.isTerminated())) {
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 + "/";
}
try {
uri = UriUtils.resolve(new URI(uri)).toString();
} catch (URISyntaxException e) {
logger.error("URISyntaxException while trying to resolve Cache URI: " + e);
}
uri = uri + accountId.toString();
cache = cache(path, uri);
}
return cache;
}
protected 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);
}
protected 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);
}
String e164(final String number) {
if (configuration.subset("runtime-settings").getBoolean("normalize-numbers-for-outbound-calls")) {
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;
}
} else {
return number;
}
}
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);
String base = configuration.subset("runtime-settings").getString("error-dictionary-uri");
try {
base = UriUtils.resolve(new URI(base)).toString();
} catch (URISyntaxException e) {
logger.error("URISyntaxException when trying to resolve Error-Dictionary URI: "+e);
}
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();
}
ActorRef parser(final String xml) {
final Props props = new Props(new UntypedActorFactory() {
private static final long serialVersionUID = 1L;
@Override
public UntypedActor create() throws IOException {
return new Parser(xml, self());
}
});
return getContext().actorOf(props);
}
void postCleanup() {
if (smsSessions.isEmpty() && outstandingAsrRequests == 0) {
final UntypedActorContext context = getContext();
if (parser != null)
getContext().stop(parser);
context.stop(self());
}
if (downloader != null && !downloader.isTerminated()) {
getContext().stop(downloader);
}
}
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 final class SendingEmail extends AbstractAction {
public SendingEmail(final ActorRef source){
super(source);
}
@Override
public void execute( final Object message) throws Exception {
final Tag verb = (Tag)message;
// Parse "from".
String from;
Attribute attribute = verb.attribute("from");
if (attribute != null) {
from = attribute.value();
}else{
Exception error = new Exception("From attribute was not defined");
source.tell(new EmailResponse(error,error.getMessage()), source);
return;
}
// Parse "to".
String to;
attribute = verb.attribute("to");
if (attribute != null) {
to = attribute.value();
}else{
Exception error = new Exception("To attribute was not defined");
source.tell(new EmailResponse(error,error.getMessage()), source);
return;
}
// Parse "cc".
String cc="";
attribute = verb.attribute("cc");
if (attribute != null) {
cc = attribute.value();
}
// Parse "bcc".
String bcc="";
attribute = verb.attribute("bcc");
if (attribute != null) {
bcc = attribute.value();
}
// Parse "subject"
String subject;
attribute = verb.attribute("subject");
if (attribute != null) {
subject = attribute.value();
}else{
subject="Restcomm Email Service";
}
// Send the email.
final Mail emailMsg = new Mail(from, to, subject, verb.text(),cc,bcc);
if (mailerService == null){
mailerService = mailer(configuration.subset("smtp-service"));
}
mailerService.tell(new EmailRequest(emailMsg), self());
}
}
void smsResponse(final Object message) {
final Class> klass = message.getClass();
final ActorRef self = self();
if (SmsSessionResponse.class.equals(klass)) {
final SmsSessionResponse response = (SmsSessionResponse) message;
final SmsSessionInfo info = response.info();
SmsMessage record = (SmsMessage) info.attributes().get("record");
if (response.succeeded()) {
final DateTime now = DateTime.now();
record = record.setDateSent(now);
record = record.setStatus(Status.SENT);
} else {
record = record.setStatus(Status.FAILED);
}
final SmsMessagesDao messages = storage.getSmsMessagesDao();
messages.updateSmsMessage(record);
// Notify the callback listener.
final Object attribute = info.attributes().get("callback");
if (attribute != null) {
final URI callback = (URI) attribute;
final List parameters = parameters();
request = new HttpRequestDescriptor(callback, "POST", parameters);
downloader.tell(request, null);
}
// Destroy the sms session.
final ActorRef session = smsSessions.remove(record.getSid());
final DestroySmsSession destroy = new DestroySmsSession(session);
smsService.tell(destroy, self);
}
}
public ActorRef getSynthesizer() {
if (synthesizer == null || (synthesizer != null && synthesizer.isTerminated())) {
String ttsEngine = configuration.subset("speech-synthesizer").getString("[@active]");
Configuration ttsConf = configuration.subset(ttsEngine);
synthesizer = tts(ttsConf);
}
return synthesizer;
}
ActorRef tts(final Configuration ttsConf) {
final String classpath = ttsConf.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(ttsConf);
}
});
return getContext().actorOf(props);
}
protected boolean is(State state) {
return this.fsm.state().equals(state);
}
abstract class AbstractAction implements Action {
protected final ActorRef source;
public AbstractAction(final ActorRef source) {
super();
this.source = source;
}
}
final class AcquiringAsrInfo extends AbstractAction {
public AcquiringAsrInfo(final ActorRef source) {
super(source);
}
@Override
public void execute(final Object message) throws Exception {
getAsrService().tell(new GetAsrInfo(), source);
}
}
final class AcquiringSpeechSynthesizerInfo extends AbstractAction {
public AcquiringSpeechSynthesizerInfo(final ActorRef source) {
super(source);
}
@SuppressWarnings({ "unchecked" })
@Override
public void execute(final Object message) throws Exception {
final AsrResponse response = (AsrResponse) message;
asrInfo = response.get();
getSynthesizer().tell(new GetSpeechSynthesizerInfo(), source);
}
}
final class AcquiringCallInfo extends AbstractAction {
public AcquiringCallInfo(final ActorRef source) {
super(source);
}
@SuppressWarnings({ "unchecked" })
@Override
public void execute(final Object message) throws Exception {
final SpeechSynthesizerResponse response = (SpeechSynthesizerResponse) message;
synthesizerInfo = response.get();
// call.tell(new Observe(source), source);
// //Enable Monitoring Service for the call
// if (monitoring != null)
// call.tell(new Observe(monitoring), source);
call.tell(new GetCallInfo(), source);
}
}
final class NotFound extends AbstractAction {
public NotFound(final ActorRef source) {
super(source);
}
@Override
public void execute(final Object message) throws Exception {
// final Class> klass = message.getClass();
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.
call.tell(new org.restcomm.connect.telephony.api.NotFound(), source);
}
}
final class Rejecting extends AbstractAction {
public Rejecting(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 reason = "rejected";
Attribute attribute = verb.attribute("reason");
if (attribute != null) {
reason = attribute.value();
if (reason != null && !reason.isEmpty()) {
if ("rejected".equalsIgnoreCase(reason)) {
reason = "rejected";
} else if ("busy".equalsIgnoreCase(reason)) {
reason = "busy";
} else {
reason = "rejected";
}
} else {
reason = "rejected";
}
}
// Reject the call.
call.tell(new Reject(reason), source);
}
}
final class PlayingRejectionPrompt extends AbstractAction {
public PlayingRejectionPrompt(final ActorRef source) {
super(source);
}
@Override
public void execute(final Object message) throws Exception {
String path = configuration.subset("runtime-settings").getString("prompts-uri");
if (!path.endsWith("/")) {
path += "/";
}
path += "reject.wav";
URI uri = null;
try {
uri = UriUtils.resolve(new URI(path));
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 12400, exception.getMessage());
final NotificationsDao notifications = storage.getNotificationsDao();
notifications.addNotification(notification);
sendMail(notification);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
final Play play = new Play(uri, 1);
call.tell(play, source);
}
}
final class Faxing extends AbstractAction {
public Faxing(final ActorRef source) {
super(source);
}
@Override
public void execute(Object message) throws Exception {
final DiskCacheResponse response = (DiskCacheResponse) message;
// Parse "from".
String from = callInfo.to();
Attribute attribute = verb.attribute("from");
if (attribute != null) {
from = attribute.value();
if (from != null && from.isEmpty()) {
from = e164(from);
if (from == null) {
from = verb.attribute("from").value();
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
}
}
// Parse "to".
String to = callInfo.from();
attribute = verb.attribute("to");
if (attribute != null) {
to = attribute.value();
if (to != null && !to.isEmpty()) {
to = e164(to);
if (to == null) {
to = verb.attribute("to").value();
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
}
}
// Send the fax.
final String uri = response.get().toString();
final int offset = uri.lastIndexOf("/");
final String path = cachePath + "/" + uri.substring(offset + 1, uri.length());
final FaxRequest fax = new FaxRequest(to, new File(path));
getFaxService().tell(fax, source);
}
}
final class Pausing extends AbstractAction {
public Pausing(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;
}
int length = 1;
final Attribute attribute = verb.attribute("length");
if (attribute != null) {
final String number = attribute.value();
if (number != null && !number.isEmpty()) {
try {
length = Integer.parseInt(number);
} catch (final NumberFormatException exception) {
final Notification notification = notification(WARNING_NOTIFICATION, 13910, "Invalid length value.");
final NotificationsDao notifications = storage.getNotificationsDao();
notifications.addNotification(notification);
}
}
}
final UntypedActorContext context = getContext();
context.setReceiveTimeout(Duration.create(length, TimeUnit.SECONDS));
}
}
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;
}
// else {
// logger.info("Can't check cache, message not verb. Moving to the next verb");
// // final GetNextVerb next = GetNextVerb.instance();
// // parser.tell(next, source);
// return;
// }
String hash = hash(verb);
DiskCacheRequest request = new DiskCacheRequest(hash);
if (logger.isErrorEnabled()) {
logger.info("Checking cache for hash: " + hash);
}
getCache().tell(request, source);
}
}
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());
getCache().tell(request, source);
} else if (Tag.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 = UriUtils.resolve(base, target);
final DiskCacheRequest request = new DiskCacheRequest(uri);
getCache().tell(request, source);
} else {
// Ask the parser for the next action to take.
final GetNextVerb next = new GetNextVerb();
parser.tell(next, source);
}
}
}
}
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)) {
// Parse the loop attribute.
int loop = 1;
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 (Verbs.say.equals(verb.name())) {
notification = notification(WARNING_NOTIFICATION, 13510, loop + " is an invalid loop value.");
notifications.addNotification(notification);
} else if (Verbs.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);
call.tell(play, source);
}
}
}
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);
}
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 (!Verbs.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(GatherAttributes.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;
}
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() && details.get("text") != null) {
String voice = details.get("voice");
String language = details.get("language");
String text = details.get("text");
final SpeechSynthesizerRequest synthesize = new SpeechSynthesizerRequest(voice, language, text);
getSynthesizer().tell(synthesize, source);
} else {
// Ask the parser for the next action to take.
final GetNextVerb next = new GetNextVerb();
parser.tell(next, source);
}
}
}
final class HangingUp extends AbstractAction {
public HangingUp(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;
}
// Hang up the call.
if (ParserFailed.class.equals(klass)) {
call.tell(new Hangup("Problem_to_parse_downloaded_RCML"), source);
} else if (CallManagerResponse.class.equals(klass)) {
String reason = ((CallManagerResponse)message).cause().getMessage().replaceAll("\\s","_");
call.tell(new Hangup(reason), source);
} else if (message instanceof SmsServiceResponse) {
//Blocked SMS Session request
call.tell(new Hangup(((SmsServiceResponse)message).cause().getMessage()), self());
} else if (Tag.class.equals(klass) && Verbs.hangup.equals(verb.name())) {
Integer sipResponse = outboundCallResponse != null ? outboundCallResponse : SipServletResponse.SC_REQUEST_TERMINATED;
call.tell(new Hangup(sipResponse), source);
} else {
call.tell(new Hangup(outboundCallResponse), source);
}
}
}
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(GatherAttributes.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 = UriUtils.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);
}
}
}
abstract class AbstractGatherAction extends AbstractAction {
public AbstractGatherAction(final ActorRef source) {
super(source);
}
protected String finishOnKey(final Tag container) {
String finishOnKey = "#";
Attribute attribute = container.attribute(GatherAttributes.ATTRIBUTE_FINISH_ON_KEY);
if (attribute != null) {
finishOnKey = attribute.value();
if (finishOnKey != null && !finishOnKey.isEmpty()) {
if (!PATTERN.matcher(finishOnKey).matches()) {
final NotificationsDao notifications = storage.getNotificationsDao();
final Notification notification = notification(WARNING_NOTIFICATION, 13310, finishOnKey
+ " is not a valid finishOnKey value");
notifications.addNotification(notification);
finishOnKey = "#";
}
} else {
finishOnKey = "#";
}
}
return finishOnKey;
}
}
final class ProcessingGatherChildren extends AbstractGatherAction {
public ProcessingGatherChildren(final ActorRef source) {
super(source);
}
@SuppressWarnings("unchecked")
@Override
public void execute(final Object message) throws Exception {
processingGather = true;
final Class> klass = message.getClass();
final NotificationsDao notifications = storage.getNotificationsDao();
if (SpeechSynthesizerResponse.class.equals(klass)) {
final SpeechSynthesizerResponse response = (SpeechSynthesizerResponse) message;
final DiskCacheRequest request = new DiskCacheRequest(response.get());
getCache().tell(request, source);
} else {
if (Tag.class.equals(klass)) {
verb = (Tag) message;
gatherPrompts = new ArrayList();
gatherChildren = new ArrayList(verb.children());
} else if (DiskCacheResponse.class.equals(klass)) {
if (gatherPrompts == null)
gatherPrompts = new ArrayList();
if (gatherChildren == null)
gatherChildren = new ArrayList(verb.children());
final DiskCacheResponse response = (DiskCacheResponse) message;
final URI uri = response.get();
Tag child = null;
if (!gatherChildren.isEmpty())
child = gatherChildren.remove(0);
// Parse the loop attribute.
int loop = 1;
Attribute attribute = null;
if (child != null)
attribute = child.attribute("loop");
if (attribute != null) {
final String number = attribute.value();
if (number != null && !number.isEmpty()) {
try {
loop = Integer.parseInt(number);
} catch (final NumberFormatException ignored) {
Notification notification = null;
if (Verbs.say.equals(child.name())) {
notification = notification(WARNING_NOTIFICATION, 13322, loop
+ " is an invalid loop value.");
notifications.addNotification(notification);
}
}
}
}
for (int counter = 0; counter < loop; counter++) {
gatherPrompts.add(uri);
}
}
for (int index = 0; index < gatherChildren.size(); index++) {
final Tag child = gatherChildren.get(index);
if (Verbs.play.equals(child.name())) {
final String text = child.text();
if (text != null && !text.isEmpty()) {
URI target = null;
try {
target = URI.create(text);
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 13325, 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 = UriUtils.resolve(base, target);
// Cache the prompt.
final DiskCacheRequest request = new DiskCacheRequest(uri);
getCache().tell(request, source);
break;
}
} else if (Verbs.say.equals(child.name())) {
// Parse the voice attribute.
String voice = "man";
Attribute attribute = child.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, 13321, voice
+ " is an invalid voice value.");
notifications.addNotification(notification);
voice = "man";
}
} else {
voice = "man";
}
}
// Parse the language attribute.
String language = "en";
attribute = child.attribute("language");
if (attribute != null) {
language = attribute.value();
if (language != null && !language.isEmpty()) {
if (!synthesizerInfo.languages().contains(language)) {
language = "en";
}
} else {
language = "en";
}
}
String text = child.text();
if (text != null && !text.isEmpty()) {
// final SpeechSynthesizerRequest synthesize = new SpeechSynthesizerRequest(voice, language, text);
// synthesizer.tell(synthesize, source);
// break;
String hash = hash(child);
DiskCacheRequest request = new DiskCacheRequest(hash);
getCache().tell(request, source);
break;
}
} else if (Verbs.pause.equals(child.name())) {
int length = 1;
final Attribute attribute = child.attribute("length");
if (attribute != null) {
final String number = attribute.value();
if (number != null && !number.isEmpty()) {
try {
length = Integer.parseInt(number);
} catch (final NumberFormatException ignored) {
}
}
}
String path = configuration.subset("runtime-settings").getString("prompts-uri");
if (!path.endsWith("/")) {
path += "/";
}
path += "one-second-silence.wav";
final URI uri = UriUtils.resolve(new URI(path));
for (int counter = 0; counter < length; counter++) {
gatherPrompts.add(uri);
}
}
}
// Make sure we don't leave any pauses at the beginning
// since we can't concurrently modify the list.
if (!gatherChildren.isEmpty()) {
Tag child = null;
do {
child = gatherChildren.get(0);
if (child != null) {
if (Verbs.pause.equals(child.name())) {
gatherChildren.remove(0);
}
}
} while (Verbs.pause.equals(child.name()));
}
// Start gathering.
if (gatherChildren.isEmpty()) {
if (gatherVerb != null)
verb = gatherVerb;
final StartGathering start = StartGathering.instance();
source.tell(start, source);
processingGather = false;
}
}
}
}
final class Gathering extends AbstractGatherAction {
public Gathering(final ActorRef source) {
super(source);
}
@Override
public void execute(final Object message) throws Exception {
// check mandatory attribute partialResultCallback
Attribute partialResultCallbackAttr = verb.attribute(GatherAttributes.ATTRIBUTE_PARTIAL_RESULT_CALLBACK);
final NotificationsDao notifications = storage.getNotificationsDao();
// parse attribute "input"
Attribute typeAttr = verb.attribute(GatherAttributes.ATTRIBUTE_INPUT);
Collect.Type inputType = null;
if (typeAttr == null) {
inputType = Collect.Type.DTMF;
} else {
inputType = Collect.Type.parseOrDefault(typeAttr.value(), Collect.Type.DTMF);
}
if (inputType.equals(Collect.Type.SPEECH) || inputType.equals(Collect.Type.DTMF_SPEECH)) {
ExtensionController ec = ExtensionController.getInstance();
final IExtensionFeatureAccessRequest far = new FeatureAccessRequest(FeatureAccessRequest.Feature.ASR, accountId);
ExtensionResponse er = ec.executePreInboundAction(far, extensions);
if (!er.isAllowed()) {
if (logger.isDebugEnabled()) {
final String errMsg = "ASR feature is not allowed";
logger.debug(errMsg);
}
String errMsg = "ASR feature is not allowed";
notification(WARNING_NOTIFICATION, 11001, errMsg);
call.tell(new Hangup(errMsg), source);
return;
}
ec.executePostOutboundAction(far, extensions);
}
// parse attribute "language"
Attribute langAttr = verb.attribute(GatherAttributes.ATTRIBUTE_LANGUAGE);
String defaultLang = restcommConfiguration.getMgAsr().getDefaultLanguage();
String lang = parseAttrLanguage(langAttr, defaultLang);
// parse attribute "hints"
String hints = null;
Attribute hintsAttr = verb.attribute(GatherAttributes.ATTRIBUTE_HINTS);
if (hintsAttr != null && !StringUtils.isEmpty(hintsAttr.value())) {
hints = hintsAttr.value();
} else if (inputType != Collect.Type.DTMF) {
logger.warning("'{}' attribute is null or empty", GatherAttributes.ATTRIBUTE_HINTS);
}
// Parse finish on key.
finishOnKey = finishOnKey(verb);
// Parse the number of digits.
Attribute attribute = verb.attribute(GatherAttributes.ATTRIBUTE_NUM_DIGITS);
if (attribute != null) {
final String value = attribute.value();
if (!StringUtils.isEmpty(value)) {
try {
numberOfDigits = Integer.parseInt(value);
} catch (final NumberFormatException exception) {
final Notification notification = notification(WARNING_NOTIFICATION, 13314, numberOfDigits
+ " is not a valid numDigits value");
notifications.addNotification(notification);
}
}
}
// Parse timeout.
int timeout = restcommConfiguration.getMgAsr().getDefaultGatheringTimeout();
attribute = verb.attribute(GatherAttributes.ATTRIBUTE_TIME_OUT);
if (attribute != null) {
final String value = attribute.value();
if (!StringUtils.isEmpty(value)) {
try {
timeout = Integer.parseInt(value);
} catch (final NumberFormatException exception) {
final Notification notification = notification(WARNING_NOTIFICATION, 13313, timeout
+ " is not a valid timeout value");
notifications.addNotification(notification);
}
}
}
// Start gathering.
final Collect collect = new Collect(restcommConfiguration.getMgAsr().getDefaultDriver(), inputType, gatherPrompts,
null, timeout, finishOnKey, numberOfDigits, lang, hints, partialResultCallbackAttr != null && !StringUtils.isEmpty(partialResultCallbackAttr.value()));
call.tell(collect, source);
// Some clean up.
gatherChildren = null;
gatherPrompts = null;
dtmfReceived = false;
collectedDigits = new StringBuffer("");
}
private String parseAttrLanguage(Attribute langAttr, String defaultLang) {
if (langAttr != null && !StringUtils.isEmpty(langAttr.value())) {
return restcommConfiguration.getMgAsr().getLanguages().contains(langAttr.value()) ? langAttr.value() : defaultLang;
} else {
logger.warning("Illegal or unsupported attribute value: '{}'. Will be use default value '{}'", langAttr,
defaultLang);
return defaultLang;
}
}
}
private abstract class CallbackGatherAction extends AbstractGatherAction {
public CallbackGatherAction(ActorRef source) {
super(source);
}
protected void execHttpRequest(final NotificationsDao notifications,
final Attribute callbackAttr, final Attribute methodAttr,
final List parameters) {
if (callbackAttr == null) {
final Notification notification = notification(ERROR_NOTIFICATION, 11101, "Callback attribute is null");
notifications.addNotification(notification);
sendMail(notification);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
logger.error("CallbackAttribute is null, CallbackGatherAction failed");
}
String action = callbackAttr.value();
if (StringUtils.isEmpty(action)) {
final Notification notification = notification(ERROR_NOTIFICATION, 11101, "Callback attribute value is null or empty");
notifications.addNotification(notification);
sendMail(notification);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
logger.error("Action url is null or empty");
}
URI target;
try {
target = URI.create(action);
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 11100, action
+ " is an invalid URI.");
notifications.addNotification(notification);
sendMail(notification);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
String method = "POST";
if (methodAttr != null) {
method = methodAttr.value();
if (!StringUtils.isEmpty(method)) {
if (!"GET".equalsIgnoreCase(method) && !"POST".equalsIgnoreCase(method)) {
final Notification notification = notification(WARNING_NOTIFICATION, 14104, method
+ " is not a valid HTTP method for ");
notifications.addNotification(notification);
method = "POST";
}
} else {
method = "POST";
}
}
final URI base = request.getUri();
final URI uri = UriUtils.resolve(base, target);
request = new HttpRequestDescriptor(uri, method, parameters);
downloader.tell(request, source);
}
}
final class FinishGathering extends CallbackGatherAction {
public FinishGathering(final ActorRef source) {
super(source);
}
@Override
public void execute(final Object message) throws Exception {
final NotificationsDao notifications = storage.getNotificationsDao();
String digits = collectedDigits.toString();
collectedDigits = new StringBuffer();
if (logger.isInfoEnabled()) {
logger.info("Digits collected: " + digits);
}
if (digits.equals(finishOnKey)) {
digits = "";
}
if (logger.isDebugEnabled()) {
logger.debug("Digits collected : " + digits);
}
// https://bitbucket.org/telestax/telscale-restcomm/issue/150/verb-is-looping-by-default-and-never
// If the 'timeout' is reached before the caller enters any digits, or if the caller enters the 'finishOnKey' value
// before entering any other digits, Twilio will not make a request to the 'action' URL but instead continue
// processing
// the current TwiML document with the verb immediately following the
Attribute action = verb.attribute(GatherAttributes.ATTRIBUTE_ACTION);
if (action != null && (!digits.trim().isEmpty() || !StringUtils.isEmpty(speechResult))) {
// Redirect to the action url.
if (digits.endsWith(finishOnKey)) {
final int finishOnKeyIndex = digits.lastIndexOf(finishOnKey);
digits = digits.substring(0, finishOnKeyIndex);
}
final List parameters = parameters();
parameters.add(new BasicNameValuePair("Digits", digits));
parameters.add(new BasicNameValuePair("SpeechResult", speechResult));
execHttpRequest(notifications, action, verb.attribute(GatherAttributes.ATTRIBUTE_METHOD), parameters);
speechResult = null;
return;
}
if (logger.isInfoEnabled()) {
logger.info("Attribute, Action or Digits is null, FinishGathering failed, moving to the next available verb");
}
speechResult = null;
// Ask the parser for the next action to take.
final GetNextVerb next = new GetNextVerb();
parser.tell(next, source);
}
}
final class PartialGathering extends CallbackGatherAction {
public PartialGathering(final ActorRef source) {
super(source);
}
@Override
public void execute(final Object message) throws Exception {
if (verb.attribute(GatherAttributes.ATTRIBUTE_PARTIAL_RESULT_CALLBACK) != null
&& !StringUtils.isEmpty(verb.attribute(GatherAttributes.ATTRIBUTE_PARTIAL_RESULT_CALLBACK).value())) {
final MediaGroupResponse asrResponse = (MediaGroupResponse) message;
final List parameters = parameters();
parameters.add(new BasicNameValuePair("UnstableSpeechResult", asrResponse.get().getResult()));
final NotificationsDao notifications = storage.getNotificationsDao();
execHttpRequest(notifications, verb.attribute(GatherAttributes.ATTRIBUTE_PARTIAL_RESULT_CALLBACK),
verb.attribute(GatherAttributes.ATTRIBUTE_PARTIAL_RESULT_CALLBACK_METHOD), parameters);
}
}
}
final class CreatingRecording extends AbstractAction {
public CreatingRecording(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 finishOnKey = "1234567890*#";
Attribute attribute = verb.attribute(GatherAttributes.ATTRIBUTE_FINISH_ON_KEY);
if (attribute != null) {
finishOnKey = attribute.value();
if (finishOnKey != null && !finishOnKey.isEmpty()) {
//https://github.com/RestComm/Restcomm-Connect/issues/1886
if (!finishOnKey.equals("-1")) {
if (!PATTERN.matcher(finishOnKey).matches()) {
final Notification notification = notification(WARNING_NOTIFICATION, 13613, finishOnKey
+ " is not a valid finishOnKey value");
notifications.addNotification(notification);
//https://github.com/RestComm/Restcomm-Connect/issues/1925
finishOnKey = "#";
}
}
} else {
//https://github.com/RestComm/Restcomm-Connect/issues/1925
finishOnKey = "#";
}
}
boolean playBeep = true;
attribute = verb.attribute("playBeep");
if (attribute != null) {
final String value = attribute.value();
if (value != null && !value.isEmpty()) {
playBeep = Boolean.parseBoolean(value);
}
}
int maxLength = 3600;
attribute = verb.attribute("maxLength");
if (attribute != null) {
final String value = attribute.value();
if (value != null && !value.isEmpty()) {
try {
maxLength = Integer.parseInt(value);
} catch (final NumberFormatException exception) {
final Notification notification = notification(WARNING_NOTIFICATION, 13612, maxLength
+ " is not a valid maxLength value");
notifications.addNotification(notification);
}
}
}
int timeout = 5;
attribute = verb.attribute(GatherAttributes.ATTRIBUTE_TIME_OUT);
if (attribute != null) {
final String value = attribute.value();
if (value != null && !value.isEmpty()) {
try {
timeout = Integer.parseInt(value);
} catch (final NumberFormatException exception) {
final Notification notification = notification(WARNING_NOTIFICATION, 13612, timeout
+ " is not a valid timeout value");
notifications.addNotification(notification);
}
}
}
recordingMediaType = MediaAttributes.MediaType.AUDIO_ONLY;
attribute = verb.attribute("media");
if (attribute != null) {
final String value = attribute.value();
if (value != null && !value.isEmpty()) {
try {
recordingMediaType = MediaAttributes.MediaType.getValueOf(value);
} catch (final IllegalArgumentException exception) {
final Notification notification = notification(WARNING_NOTIFICATION, 13612, value
+ " is not a valid media value");
notifications.addNotification(notification);
}
}
}
// Start recording.
recordingSid = Sid.generate(Sid.Type.RECORDING);
String path = configuration.subset("runtime-settings").getString("recordings-path");
String httpRecordingUri = configuration.subset("runtime-settings").getString("recordings-uri");
if (!path.endsWith("/")) {
path += "/";
}
if (!httpRecordingUri.endsWith("/")) {
httpRecordingUri += "/";
}
String fileExtension = recordingMediaType.equals(MediaAttributes.MediaType.AUDIO_ONLY) ? ".wav" : ".mp4";
path += recordingSid.toString() + fileExtension;
httpRecordingUri += recordingSid.toString() + fileExtension;
recordingUri = URI.create(path);
try {
publicRecordingUri = UriUtils.resolve(new URI(httpRecordingUri));
} catch (URISyntaxException e) {
logger.error("URISyntaxException when trying to resolve Recording URI: "+e);
}
Record record = null;
if (playBeep) {
final List prompts = new ArrayList(1);
path = configuration.subset("runtime-settings").getString("prompts-uri");
if (!path.endsWith("/")) {
path += "/";
}
path += "beep.wav";
try {
prompts.add(UriUtils.resolve(new URI(path)));
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 12400, exception.getMessage());
notifications.addNotification(notification);
sendMail(notification);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
record = new Record(recordingUri, prompts, timeout, maxLength, finishOnKey, recordingMediaType);
} else {
record = new Record(recordingUri, timeout, maxLength, finishOnKey, recordingMediaType);
}
call.tell(record, source);
}
}
final class FinishRecording extends AbstractAction {
public FinishRecording(final ActorRef source) {
super(source);
}
@SuppressWarnings("unchecked")
@Override
public void execute(final Object message) throws Exception {
if (logger.isInfoEnabled()) {
logger.info("##### At FinishRecording, message: " + message.getClass());
}
boolean amazonS3Enabled = configuration.subset("amazon-s3").getBoolean("enabled");
final Class> klass = message.getClass();
if (CallStateChanged.class.equals(klass)) {
final CallStateChanged event = (CallStateChanged) message;
// Update the interpreter state.
callState = event.state();
// Update the storage.
callRecord = callRecord.setStatus(callState.toString());
final DateTime end = DateTime.now();
callRecord = callRecord.setEndTime(end);
recordingDuration = (int) (end.getMillis() - callRecord.getStartTime().getMillis()) / 1000;
callRecord = callRecord.setDuration(recordingDuration);
final CallDetailRecordsDao records = storage.getCallDetailRecordsDao();
records.updateCallDetailRecord(callRecord);
// Update the application.
// callback();
}
Recording recording = null;
final NotificationsDao notifications = storage.getNotificationsDao();
// Create a record of the recording.
Double duration = WavUtils.getAudioDuration(recordingUri);
if (duration.equals(0.0)) {
if (logger.isInfoEnabled()) {
String msg = String.format("Recording file %s, duration is 0 and will return",recordingUri);
logger.info(msg);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Recording File exists, length: " + (new File(recordingUri).length()));
logger.debug("Recording duration: " + duration);
}
final Recording.Builder builder = Recording.builder();
builder.setSid(recordingSid);
builder.setAccountSid(accountId);
builder.setCallSid(callInfo.sid());
builder.setDuration(duration);
builder.setApiVersion(version);
StringBuilder buffer = new StringBuilder();
buffer.append("/").append(version).append("/Accounts/").append(accountId.toString());
buffer.append("/Recordings/").append(recordingSid.toString());
builder.setUri(URI.create(buffer.toString()));
recording = builder.build();
final RecordingsDao recordings = storage.getRecordingsDao();
recordings.addRecording(recording, recordingMediaType);
Attribute attribute = null;
if (checkAsrService()) {
// ASR service is enabled. Start transcription.
URI transcribeCallback = null;
attribute = verb.attribute("transcribeCallback");
if (attribute != null) {
final String value = attribute.value();
if (value != null && !value.isEmpty()) {
try {
transcribeCallback = URI.create(value);
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 11100, transcribeCallback
+ " is an invalid URI.");
notifications.addNotification(notification);
sendMail(notification);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
}
}
boolean transcribe = false;
if (transcribeCallback != null) {
transcribe = true;
} else {
attribute = verb.attribute("transcribe");
if (attribute != null) {
final String value = attribute.value();
if (value != null && !value.isEmpty()) {
transcribe = Boolean.parseBoolean(value);
}
}
}
if (transcribe && checkAsrService()) {
final Sid sid = Sid.generate(Sid.Type.TRANSCRIPTION);
final Transcription.Builder otherBuilder = Transcription.builder();
otherBuilder.setSid(sid);
otherBuilder.setAccountSid(accountId);
otherBuilder.setStatus(Transcription.Status.IN_PROGRESS);
otherBuilder.setRecordingSid(recordingSid);
otherBuilder.setTranscriptionText("Transcription Text not available");
otherBuilder.setDuration(duration);
otherBuilder.setPrice(new BigDecimal("0.00"));
buffer = new StringBuilder();
buffer.append("/").append(version).append("/Accounts/").append(accountId.toString());
buffer.append("/Transcriptions/").append(sid.toString());
final URI uri = URI.create(buffer.toString());
otherBuilder.setUri(uri);
final Transcription transcription = otherBuilder.build();
final TranscriptionsDao transcriptions = storage.getTranscriptionsDao();
transcriptions.addTranscription(transcription);
try {
final Map attributes = new HashMap();
attributes.put("callback", transcribeCallback);
attributes.put("transcription", transcription);
getAsrService().tell(new AsrRequest(new File(recordingUri), "en", attributes), source);
outstandingAsrRequests++;
} catch (final Exception exception) {
logger.error(exception.getMessage(), exception);
}
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("AsrService is not enabled");
}
}
}
// If action is present redirect to the action URI.
String action = null;
Attribute attribute = verb.attribute(GatherAttributes.ATTRIBUTE_ACTION);
if (attribute != null) {
action = attribute.value();
if (action != null && !action.isEmpty()) {
URI target = null;
try {
target = URI.create(action);
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 11100, action
+ " 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 = UriUtils.resolve(base, target);
// Parse "method".
String method = "POST";
attribute = verb.attribute(GatherAttributes.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, 13610, method
+ " is not a valid HTTP method for ");
notifications.addNotification(notification);
method = "POST";
}
} else {
method = "POST";
}
}
final List parameters = parameters();
if (recording != null) {
if (amazonS3Enabled) {
//If Amazon S3 is enabled the Recordings DAO uploaded the wav file to S3 and changed the URI
parameters.add(new BasicNameValuePair("RecordingUrl", recording.getFileUri().toURL().toString()));
parameters.add(new BasicNameValuePair("PublicRecordingUrl", recording.getFileUri().toURL().toString()));
} else {
// Redirect to the action url.
String httpRecordingUri = configuration.subset("runtime-settings").getString("recordings-uri");
if (!httpRecordingUri.endsWith("/")) {
httpRecordingUri += "/";
}
String fileExtension = recordingMediaType.equals(MediaAttributes.MediaType.AUDIO_ONLY) ? ".wav" : ".mp4";
httpRecordingUri += recordingSid.toString() + fileExtension;
URI publicRecordingUri = UriUtils.resolve(new URI(httpRecordingUri));
parameters.add(new BasicNameValuePair("RecordingUrl", recordingUri.toString()));
parameters.add(new BasicNameValuePair("PublicRecordingUrl", publicRecordingUri.toString()));
}
}
parameters.add(new BasicNameValuePair("RecordingDuration", Double.toString(duration)));
if (MediaGroupResponse.class.equals(klass)) {
final MediaGroupResponse response = (MediaGroupResponse) message;
Object data = response.get();
if (data instanceof CollectedResult) {
parameters.add(new BasicNameValuePair("Digits", ((CollectedResult)data).getResult()));
} else if(data instanceof String) {
parameters.add(new BasicNameValuePair("Digits", (String)data));
} else {
logger.error("unidentified response recived in MediaGroupResponse: "+response);
}
request = new HttpRequestDescriptor(uri, method, parameters);
if (logger.isInfoEnabled()){
logger.info("About to execute Record action to: "+uri);
}
downloader.tell(request, self());
} else if (CallStateChanged.class.equals(klass)) {
parameters.add(new BasicNameValuePair("Digits", "hangup"));
request = new HttpRequestDescriptor(uri, method, parameters);
if (logger.isInfoEnabled()) {
logger.info("About to execute Record action to: "+uri);
}
downloader.tell(request, self());
}
// final StopInterpreter stop = new StopInterpreter();
// source.tell(stop, source);
}
} else {
//Action is null here
final GetNextVerb next = new GetNextVerb();
parser.tell(next, source);
}
// A little clean up.
recordingSid = null;
recordingUri = null;
recordingMediaType = null;
}
}
final class CreatingSmsSession extends AbstractAction {
public CreatingSmsSession(final ActorRef source) {
super(source);
}
@Override
public void execute(Object message) throws Exception {
// Save verb.
final Class> klass = message.getClass();
if (Tag.class.equals(klass)) {
verb = (Tag) message;
}
// Create a new sms session to handle the verb.
smsService.tell(new CreateSmsSession(callInfo.from(), callInfo.to(), accountId.toString(), false), source);
}
}
final class SendingSms extends AbstractAction {
public SendingSms(final ActorRef source) {
super(source);
}
@SuppressWarnings("unchecked")
@Override
public void execute(final Object message) throws Exception {
final SmsServiceResponse response = (SmsServiceResponse) message;
final ActorRef session = response.get();
final NotificationsDao notifications = storage.getNotificationsDao();
// Parse "from".
String from = callInfo.to();
Attribute attribute = verb.attribute("from");
if (attribute != null) {
from = attribute.value();
if (from != null && !from.isEmpty()) {
from = e164(from);
if (from == null) {
from = verb.attribute("from").value();
final Notification notification = notification(ERROR_NOTIFICATION, 14102, from
+ " is an invalid 'from' phone number.");
notifications.addNotification(notification);
sendMail(notification);
smsService.tell(new DestroySmsSession(session), source);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
}
}
// Parse "to".
String to = callInfo.from();
attribute = verb.attribute("to");
if (attribute != null) {
to = attribute.value();
if (to != null && !to.isEmpty()) {
to = e164(to);
if (to == null) {
to = verb.attribute("to").value();
final Notification notification = notification(ERROR_NOTIFICATION, 14101, to
+ " is an invalid 'to' phone number.");
notifications.addNotification(notification);
sendMail(notification);
smsService.tell(new DestroySmsSession(session), source);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
}
}
// Parse text.
String body = verb.text();
if (body == null || body.isEmpty()) {
final Notification notification = notification(ERROR_NOTIFICATION, 14103, body + " is an invalid SMS body.");
notifications.addNotification(notification);
sendMail(notification);
smsService.tell(new DestroySmsSession(session), source);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
} else {
// Start observing events from the sms session.
session.tell(new Observe(source), source);
// Store the status callback in the sms session.
attribute = verb.attribute("statusCallback");
if (attribute != null) {
String callback = attribute.value();
if (callback != null && !callback.isEmpty()) {
URI target = null;
try {
target = URI.create(callback);
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 14105, callback
+ " is an invalid URI.");
notifications.addNotification(notification);
sendMail(notification);
smsService.tell(new DestroySmsSession(session), source);
final StopInterpreter stop = new StopInterpreter();
source.tell(stop, source);
return;
}
final URI base = request.getUri();
final URI uri = UriUtils.resolve(base, target);
session.tell(new SmsSessionAttribute("callback", uri), source);
}
}
// Create an SMS detail record.
final Sid sid = Sid.generate(Sid.Type.SMS_MESSAGE);
final SmsMessage.Builder builder = SmsMessage.builder();
builder.setSid(sid);
builder.setAccountSid(accountId);
builder.setApiVersion(version);
builder.setRecipient(to);
builder.setSender(from);
builder.setBody(body);
builder.setDirection(Direction.OUTBOUND_REPLY);
builder.setStatus(Status.SENDING);
builder.setPrice(new BigDecimal("0.00"));
final StringBuilder buffer = new StringBuilder();
buffer.append("/").append(version).append("/Accounts/");
buffer.append(accountId.toString()).append("/SMS/Messages/");
buffer.append(sid.toString());
final URI uri = URI.create(buffer.toString());
builder.setUri(uri);
final SmsMessage record = builder.build();
final SmsMessagesDao messages = storage.getSmsMessagesDao();
messages.addSmsMessage(record);
// Store the sms record in the sms session.
session.tell(new SmsSessionAttribute("record", record), source);
// Send the SMS.
final SmsSessionRequest sms = new SmsSessionRequest(from, to, body, null);
session.tell(sms, source);
smsSessions.put(sid, session);
}
// Parses "action".
attribute = verb.attribute("action");
if (attribute != null) {
String action = attribute.value();
if (action != null && !action.isEmpty()) {
URI target = null;
try {
target = URI.create(action);
} catch (final Exception exception) {
final Notification notification = notification(ERROR_NOTIFICATION, 11100, action
+ " 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 = UriUtils.resolve(base, target);
// Parse "method".
String method = "POST";
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, 14104, method
+ " is not a valid HTTP method for ");
notifications.addNotification(notification);
method = "POST";
}
} else {
method = "POST";
}
}
// Redirect to the action url.
final List parameters = parameters();
final String status = Status.SENDING.toString();
parameters.add(new BasicNameValuePair("SmsStatus", status));
request = new HttpRequestDescriptor(uri, method, parameters);
downloader.tell(request, source);
return;
}
}
// Ask the parser for the next action to take.
final GetNextVerb next = new GetNextVerb();
parser.tell(next, source);
}
}
}