package com.voxeo.moho.remote.impl; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.media.mscontrol.mediagroup.MediaGroup; import org.apache.log4j.Logger; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.joda.time.Duration; import com.rayo.core.verb.Choices; import com.rayo.core.verb.Record; import com.rayo.core.verb.Ssml; import com.rayo.core.verb.VerbRef; import com.voxeo.moho.MediaException; import com.voxeo.moho.MediaService; import com.voxeo.moho.event.EventSource; import com.voxeo.moho.media.Input; import com.voxeo.moho.media.Output; import com.voxeo.moho.media.Prompt; import com.voxeo.moho.media.Recording; import com.voxeo.moho.media.input.Grammar; import com.voxeo.moho.media.input.InputCommand; import com.voxeo.moho.media.input.SimpleGrammar; import com.voxeo.moho.media.output.AudibleResource; import com.voxeo.moho.media.output.AudioURIResource; import com.voxeo.moho.media.output.OutputCommand; import com.voxeo.moho.media.output.TextToSpeechResource; import com.voxeo.moho.media.record.RecordCommand; import com.voxeo.moho.remote.impl.media.InputImpl; import com.voxeo.moho.remote.impl.media.OutputImpl; import com.voxeo.moho.remote.impl.media.PromptImpl; import com.voxeo.moho.remote.impl.media.RecordingImpl; import com.voxeo.rayo.client.XmppException; import com.voxeo.rayo.client.xmpp.stanza.IQ; import com.voxeo.rayo.client.xmpp.stanza.Presence; public abstract class MediaServiceSupport<T extends EventSource> extends ParticipantImpl implements MediaService<T> { protected static final Logger LOG = Logger.getLogger(MediaServiceSupport.class); protected MohoRemoteImpl _mohoRemote; protected Map<String, JointImpl> _joints = new ConcurrentHashMap<String, JointImpl>(); protected Map<String, UnJointImpl> _unjoints = new ConcurrentHashMap<String, UnJointImpl>(); protected Map<String, RayoListener> _componentListeners = new ConcurrentHashMap<String, RayoListener>(); protected Lock _componentstLock = new ReentrantLock(); public MediaServiceSupport(MohoRemoteImpl mohoRemote) { _mohoRemote = mohoRemote; _dispatcher.setExecutor(_mohoRemote.getExecutor(), true); } public MohoRemoteImpl getMohoRemote() { return _mohoRemote; } public void removeComponentListener(String id) { getComponentstLock().lock(); try { _componentListeners.remove(id); } finally { getComponentstLock().unlock(); } } public RayoListener getComponentListener(final String id) { getComponentstLock().lock(); try { return _componentListeners.get(id); } finally { getComponentstLock().unlock(); } } public void addComponentListener(String id, RayoListener listener) { _componentListeners.put(id, listener); } @Override public Output<T> output(String text) throws MediaException { OutputImpl<T> output = null; getComponentstLock().lock(); try { VerbRef verbRef = _mohoRemote.getRayoClient().output(text, this.getId()); output = new OutputImpl<T>(verbRef, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return output; } @Override public Output<T> output(URI media) throws MediaException { OutputImpl<T> output = null; getComponentstLock().lock(); try { VerbRef verbRef = _mohoRemote.getRayoClient().output(getSsmlFromURI(media).getText(), this.getId()); output = new OutputImpl<T>(verbRef, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return output; } @Override public Output<T> output(OutputCommand output) throws MediaException { return internalOutput(output, 0); } @Override public Prompt<T> prompt(String text, String grammar, int repeat) throws MediaException { final OutputCommand output = text == null ? null : new OutputCommand(new TextToSpeechResource(text)); final InputCommand input = grammar == null ? null : new InputCommand(new SimpleGrammar(grammar)); return prompt(output, input, repeat); } @Override public Prompt<T> prompt(URI media, String grammar, int repeat) throws MediaException { final OutputCommand output = media == null ? null : new OutputCommand(new AudioURIResource(media)); final InputCommand input = grammar == null ? null : new InputCommand(new SimpleGrammar(grammar)); return prompt(output, input, repeat); } @Override public Prompt<T> prompt(OutputCommand output, InputCommand input, int repeat) throws MediaException { PromptImpl<T> prompt = new PromptImpl<T>(_mohoRemote.getExecutor()); if (output != null) { prompt.setOutput(internalOutput(output, repeat)); } if (input != null) { prompt.setInput(input(input)); } return prompt; } private Output<T> internalOutput(OutputCommand output, int repeat) throws MediaException { OutputImpl<T> outputFuture = null; getComponentstLock().lock(); try { com.rayo.core.verb.Output rayoOutput = new com.rayo.core.verb.Output(); if (output.getStartingOffset() > 0) { rayoOutput.setStartOffset(Duration.standardSeconds(output.getStartingOffset() / 1000)); } if (output.isStartInPausedMode()) { rayoOutput.setStartPaused(true); } if (output.getRepeatInterval() > 0) { rayoOutput.setRepeatInterval(Duration.standardSeconds(output.getRepeatInterval() / 1000)); } if (output.getRepeatTimes() > 0) { rayoOutput.setRepeatTimes(output.getRepeatTimes()); } if (repeat > 0) { rayoOutput.setRepeatTimes(repeat); } if (output.getMaxtime() > 0) { rayoOutput.setMaxTime(Duration.standardSeconds(output.getMaxtime() / 1000)); } if (output.getVoiceName() != null) { rayoOutput.setVoice(output.getVoiceName()); } VerbRef verbRef = null; OutputCommand next = null; if (output.getAudibleResources() != null && output.getAudibleResources().length > 0) { AudibleResource ar = output.getAudibleResources()[0]; if (ar instanceof TextToSpeechResource) { rayoOutput.setPrompt(new Ssml(((TextToSpeechResource) ar).getText())); } else { rayoOutput.setPrompt(getSsmlFromURI(ar.toURI())); } if (output.getAudibleResources().length > 1) { next = (OutputCommand) output.clone(); next.setAudibleResource(Arrays.copyOfRange(output.getAudibleResources(), 1, output.getAudibleResources().length)); } } else { throw new IllegalArgumentException("no AudibleResources."); } verbRef = _mohoRemote.getRayoClient().output(rayoOutput, this.getId()); outputFuture = new OutputImpl<T>(verbRef, next, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return outputFuture; } @Override public Input<T> input(String grammar) throws MediaException { InputImpl<T> input = null; getComponentstLock().lock(); try { Choices choice = new Choices(); choice.setContent(grammar); choice.setContentType(Choices.VOXEO_GRAMMAR); List<Choices> list = new ArrayList<Choices>(1); list.add(choice); com.rayo.core.verb.Input command = new com.rayo.core.verb.Input(); command.setCallId(this.getId()); command.setGrammars(list); VerbRef verbRef = _mohoRemote.getRayoClient().input(command, this.getId()); input = new InputImpl<T>(verbRef, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return input; } @Override public Input<T> input(InputCommand inputCommand) throws MediaException { InputImpl<T> input = null; getComponentstLock().lock(); try { Grammar[] grammars = inputCommand.getGrammars(); List<Choices> list = new ArrayList<Choices>(grammars.length); for (Grammar grammar : grammars) { Choices choice = new Choices(); if (grammar.getText() != null) { choice.setContent(grammar.getText()); choice.setContentType(grammar.getContentType()); } else { choice.setUri(grammar.getUri()); } list.add(choice); } com.rayo.core.verb.Input command = new com.rayo.core.verb.Input(); command.setCallId(this.getId()); command.setGrammars(list); if (inputCommand.getInitialTimeout() > 0) { command.setInitialTimeout(Duration.standardSeconds(inputCommand.getInitialTimeout() / 1000)); } if (inputCommand.getInterDigitsTimeout() > 0) { command.setInterDigitTimeout(Duration.standardSeconds(inputCommand.getInterDigitsTimeout())); } if (inputCommand.getTerminator() != null) { command.setTerminator(inputCommand.getTerminator()); } if (inputCommand.getMinConfidence() > 0) { command.setMinConfidence(inputCommand.getMinConfidence()); } if (inputCommand.getRecognizer() != null) { command.setRecognizer(inputCommand.getRecognizer()); } if (inputCommand.getSpeechCompleteTimeout() > 0) { // TODO } if (inputCommand.getSpeechIncompleteTimeout() > 0) { command.setMaxSilence(Duration.standardSeconds(inputCommand.getSpeechIncompleteTimeout())); } if (inputCommand.getInputMode() != null) { if (inputCommand.getInputMode() == com.voxeo.moho.media.InputMode.DTMF) { command.setMode(com.rayo.core.verb.InputMode.DTMF); } else if (inputCommand.getInputMode() == com.voxeo.moho.media.InputMode.SPEECH) { command.setMode(com.rayo.core.verb.InputMode.VOICE); } else { command.setMode(com.rayo.core.verb.InputMode.ANY); } } if (inputCommand.getSensitivity() > 0) { command.setSensitivity(inputCommand.getSensitivity()); } VerbRef verbRef = _mohoRemote.getRayoClient().input(command, this.getId()); input = new InputImpl<T>(verbRef, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return input; } @Override public Recording<T> record(URI recordURI) throws MediaException { Recording<T> recording = null; getComponentstLock().lock(); try { Record record = new Record(); record.setTo(recordURI); record.setCallId(this.getId()); VerbRef verbRef = _mohoRemote.getRayoClient().record(record, this.getId()); recording = new RecordingImpl<T>(verbRef, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return recording; } @Override public Recording<T> record(RecordCommand command) throws MediaException { Recording<T> recording = null; getComponentstLock().lock(); try { Record record = new Record(); record.setTo(command.getRecordURI()); record.setCallId(this.getId()); if (command.getFinalTimeout() > 0) { record.setFinalTimeout(Duration.standardSeconds(command.getFinalTimeout() / 1000)); } if (command.getFileFormat() != null) { record.setFormat(command.getFileFormat().toString()); } if (command.getMaxDuration() > 0) { record.setMaxDuration(Duration.standardSeconds(command.getMaxDuration() / 1000)); } if (command.isStartBeep()) { record.setStartBeep(true); } if (command.isStartInPausedMode()) { record.setStartPaused(true); } if (command.getInitialTimeout() > 0) { record.setInitialTimeout(Duration.standardSeconds(command.getInitialTimeout() / 1000)); } VerbRef verbRef = _mohoRemote.getRayoClient().record(record, this.getId()); recording = new RecordingImpl<T>(verbRef, this, (T) this); } catch (XmppException e) { LOG.error("", e); throw new MediaException(e); } finally { getComponentstLock().unlock(); } return recording; } @Override public void onRayoEvent(JID from, Presence presence) { RayoListener listener = getComponentListener(from.getResource()); if (listener != null) { listener.onRayoEvent(from, presence); } else { LOG.error("Can't find corresponding component, Can't process presence:" + presence); } } @Override public void onRayoCommandResult(JID from, IQ iq) { if (from.getResource() != null) { RayoListener listener = _componentListeners.get(from.getResource()); if (listener != null) { listener.onRayoCommandResult(from, iq); } else { LOG.warn("Unprocessed IQ:" + iq); } } else { LOG.warn("Unprocessed IQ:" + iq); } } @Override public MediaGroup getMediaGroup() { throw new UnsupportedOperationException(Constants.unsupported_operation); } @Override public MediaGroup getMediaGroup(boolean create) { throw new UnsupportedOperationException(Constants.unsupported_operation); } private Ssml getSsmlFromURI(URI uri) { Ssml ssml = null; String uriString = uri.toString(); if (uriString.startsWith("data:")) { String decoded = null; try { decoded = URLDecoder.decode(uriString, "UTF-8"); } catch (UnsupportedEncodingException e1) { throw new IllegalArgumentException("Wrong URI:" + uri); } int xmlIndex = decoded.indexOf(","); if (xmlIndex < 0) { throw new IllegalArgumentException("Wrong URI:" + uri); } String xml = decoded.substring(xmlIndex + 1); try { Element element = DocumentHelper.parseText(xml).getRootElement(); ssml = new Ssml(element.asXML()); } catch (DocumentException e) { throw new IllegalArgumentException("Wrong URI:" + uri); } } else { ssml = new Ssml(String.format("<audio src=\"%s\"/>", uri.toString())); } return ssml; } public Lock getComponentstLock() { return _componentstLock; } }