package com.rayo.core.xml.providers; import static com.voxeo.utils.Strings.isEmpty; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Namespace; import org.dom4j.QName; import com.rayo.core.validation.Messages; import com.rayo.core.validation.ValidationException; import com.rayo.core.verb.Choices; import com.rayo.core.verb.CpaData; import com.rayo.core.verb.Input; import com.rayo.core.verb.InputCompleteEvent; import com.rayo.core.verb.SignalEvent; import com.rayo.core.verb.InputCompleteEvent.Reason; import com.rayo.core.verb.InputMode; public class InputProvider extends BaseProvider { // XML -> Object // ================================================================================ private static final Namespace NAMESPACE = new Namespace("", "urn:xmpp:rayo:input:1"); private static final Namespace COMPLETE_NAMESPACE = new Namespace("", "urn:xmpp:rayo:input:complete:1"); private static final String CPA_DTMF_URI = "urn:xmpp:rayo:cpa:dtmf:1"; private static final String CPA_MODEM_URI = "urn:xmpp:rayo:cpa:modem:1"; private static final String CPA_FAX_URI = "urn:xmpp:rayo:cpa:fax:1"; private static final String CPA_FAX_CNG_URI = "urn:xmpp:rayo:cpa:fax-cng:1"; private static final String CPA_BEEP_URI = "urn:xmpp:rayo:cpa:beep:1"; private static final String CPA_RING_URI = "urn:xmpp:rayo:cpa:ring:1"; private static final String CPA_SIT_URI = "urn:xmpp:rayo:cpa:sit:1"; private static final String CPA_OFFHOOK_URI = "urn:xmpp:rayo:cpa:offhook:1"; private static final String CPA_SPEECH_URI = "urn:xmpp:rayo:cpa:speech:1"; // used just for testing purposes private static final String CPA_FOO_URI = "urn:xmpp:rayo:cpa:foo:1"; @Override protected Object processElement(Element element) throws Exception { if (element.getName().equals("input")) { return buildInput(element); } else if (element.getNamespace().equals(RAYO_COMPONENT_NAMESPACE)) { return buildCompleteCommand(element); } else if (element.getName().equals("signal")) { return buildSignalEvent(element); } return null; } private Object buildSignalEvent(Element element) { SignalEvent event = new SignalEvent(); if (element.attributeValue("duration") != null) { event.setDuration(toLong("duration", element)); } if (element.attributeValue("type") != null) { event.setType(element.attributeValue("type")); } if (element.attributeValue("tone") != null) { event.setTone(element.attributeValue("tone")); } if (element.attributeValue("source") != null) { event.setSource(element.attributeValue("source")); } return event; } private Object buildCompleteCommand(Element element) { InputCompleteEvent event = new InputCompleteEvent(); Element reasonElement = (Element)element.elements().get(0); if (reasonElement.getName().equals("signal")) { event.setReason(Reason.MATCH); event.setSignalEvent((SignalEvent)buildSignalEvent(reasonElement)); } else { String reasonValue = reasonElement.getName().toUpperCase(); Reason reason = Reason.valueOf(reasonValue); event.setReason(reason); if (reasonElement.attributeValue("confidence") != null) { event.setConfidence(toFloatConfidence(reasonElement.attributeValue("confidence"))); } if (reasonElement.attributeValue("mode") != null) { String modeValue = reasonElement.attributeValue("mode").toUpperCase(); InputMode mode = InputMode.valueOf(modeValue); event.setMode(mode); } if (reasonElement.element("interpretation") != null) { event.setInterpretation(reasonElement.element("interpretation").getText()); } if (reasonElement.element("utterance") != null) { event.setUtterance(reasonElement.element("utterance").getText()); } if (reasonElement.element("concept") != null) { event.setConcept(reasonElement.element("concept").getText()); } if (reasonElement.element("tag") != null) { event.setTag(reasonElement.element("tag").getText()); } if (reasonElement.element("result") != null) { event.setNlsml(reasonElement.element("result").asXML()); } } return event; } private Object buildInput(Element element) throws URISyntaxException { Input input = new Input(); if (element.attribute("min-confidence") != null) { input.setMinConfidence(toFloatConfidence(element.attributeValue("min-confidence"))); } if (element.attribute("initial-timeout") != null) { input.setInitialTimeout(toDuration("initial-timeout",element)); } if (element.attribute("mode") != null) { input.setMode(loadInputMode(element)); } if (element.attribute("inter-digit-timeout") != null) { input.setInterDigitTimeout(toDuration("inter-digit-timeout", element)); } if (element.attribute("recognizer") != null) { input.setRecognizer(element.attributeValue("recognizer")); } if (element.attribute("sensitivity") != null) { input.setSensitivity(toFloat("sensitivity", element)); } if (element.attribute("max-silence") != null) { input.setMaxSilence(toDuration("max-silence", element)); } if (element.attribute("terminator") != null) { input.setTerminator(toTerminator(element.attributeValue("terminator"))); } processGrammars(element, input); return input; } @SuppressWarnings("unchecked") private void processGrammars(Element element, Input input) { List<Choices> grammars = new ArrayList<Choices>(); List<Element> grammarsElements = element.elements("grammar"); for (Element choiceElement : grammarsElements) { if (isCpaGrammar(choiceElement)) { processCpaData(input, choiceElement); } else { Choices choice = new Choices(); String content = isEmpty(choiceElement.getText(), (String) null); String contentType = choiceElement.attributeValue("content-type"); choice.setContentType(contentType); if (choiceElement.attributeValue("url") != null) { choice.setUri(toURI(choiceElement.attributeValue("url"))); } else { if (content != null && content.startsWith("<![CDATA[")) { content = content.substring(9, content.length()-3); } choice.setContent(content); } grammars.add(choice); } } input.setGrammars(grammars); } private boolean isCpaGrammar(Element choiceElement) { return (choiceElement.element("ruleref") != null && choiceElement.element("ruleref").attributeValue("uri").contains("urn:xmpp:rayo:cpa")); } @SuppressWarnings("unchecked") private void processCpaData(Input input, Element choiceElement) { // CPA grammar CpaData data = new CpaData(); List<Element> metas = choiceElement.elements("meta"); for(Element meta: metas) { String name = meta.attributeValue("name"); if (name.equals("maxTime")) { data.setMaxTime(Long.parseLong(meta.attributeValue("content"))); } if (name.equals("minSpeechDuration")) { data.setMinSpeechDuration(Long.parseLong(meta.attributeValue("content"))); } if (name.equals("minVolume")) { data.setMinVolume(Long.parseLong(meta.attributeValue("content"))); } if (name.equals("finalSilence")) { data.setFinalSilence(Long.parseLong(meta.attributeValue("content"))); } if (name.equals("terminate")) { data.setTerminate(Boolean.parseBoolean(meta.attributeValue("content"))); } } List<Element> signalElements = choiceElement.elements("ruleref"); List<String> signals = new ArrayList<String>(); for(Element signalElement: signalElements) { String uri = signalElement.attributeValue("uri"); if (uri != null) { String signal = getSignal(uri); if (signal != null) { signals.add(signal); } } } data.setSignals(signals.toArray(new String[]{})); input.setCpaData(data); } private String getSignal(String uri) { if (uri.equals(CPA_BEEP_URI)) { return "beep"; } else if (uri.equals(CPA_MODEM_URI)) { return "dtmf"; } else if (uri.equals(CPA_FAX_URI)) { return "fax"; } else if (uri.equals(CPA_FAX_CNG_URI)) { return "fax-cng"; } else if (uri.equals(CPA_DTMF_URI)) { return "dtmf"; } else if (uri.equals(CPA_RING_URI)) { return "ring"; } else if (uri.equals(CPA_SIT_URI)) { return "sit"; } else if (uri.equals(CPA_OFFHOOK_URI)) { return "offhook"; } else if (uri.equals(CPA_SPEECH_URI)) { return "speech"; } else if (uri.equals(CPA_FOO_URI)) { return "foo"; } return null; } // Object -> XML // ================================================================================ @Override protected void generateDocument(Object object, Document document) throws Exception { if (object instanceof Input) { createInput((Input) object, document); } else if (object instanceof InputCompleteEvent) { createInputCompleteEvent((InputCompleteEvent) object, document); } else if (object instanceof SignalEvent) { createSignalEvent((SignalEvent) object, document); } } private void createInput(Input input, Document document) throws Exception { Element root = document.addElement(new QName("input", NAMESPACE)); if (input.getMinConfidence() != null ) { root.addAttribute("min-confidence", String.valueOf(input.getMinConfidence())); } if (input.getInitialTimeout() != null ) { root.addAttribute("initial-timeout", Long.toString(input.getInitialTimeout().getMillis())); } if (input.getMode() != null ) { root.addAttribute("mode", input.getMode().toString()); } if (input.getInterDigitTimeout() != null ) { root.addAttribute("inter-digit-timeout", Long.toString(input.getInterDigitTimeout().getMillis())); } if (input.getRecognizer() != null ) { root.addAttribute("recognizer", input.getRecognizer()); } if (input.getSensitivity() != null ) { root.addAttribute("sensitivity", String.valueOf(input.getSensitivity())); } if (input.getMaxSilence() != null ) { root.addAttribute("max-silence", Long.toString(input.getMaxSilence().getMillis())); } if (input.getTerminator() != null ) { root.addAttribute("terminator", String.valueOf(input.getTerminator())); } if (input.getGrammars() != null) { for (Choices choice : input.getGrammars()) { Element elementGrammar = root.addElement("grammar"); if (choice.getContentType() != null) { elementGrammar.addAttribute("content-type", choice.getContentType()); } if (choice.getUri() != null) { elementGrammar.addAttribute("url", choice.getUri().toString()); } if (choice.getContent() != null) { //elementGrammar.setText(choice.getContent()); elementGrammar.addCDATA(choice.getContent()); } } } if (input.getCpaData() != null) { Element elementGrammar = root.addElement("grammar"); elementGrammar.addAttribute("content-type", "application/srgs+xml"); addMeta(elementGrammar, input.getCpaData().getMaxTime(), "maxTime"); addMeta(elementGrammar, input.getCpaData().getMinSpeechDuration(), "minSpeechDuration"); addMeta(elementGrammar, input.getCpaData().getMinVolume(), "minVolume"); addMeta(elementGrammar, input.getCpaData().getFinalSilence(), "finalSilence"); addMeta(elementGrammar, input.getCpaData().isTerminate(), "terminate"); for (String signal: input.getCpaData().getSignals()) { Element rule = elementGrammar.addElement("ruleref"); String uri = getUri(signal); if (uri == null) { throw new ValidationException(String.format(Messages.INVALID_SIGNAL,signal)); } rule.addAttribute("uri", uri); } } } private void addMeta(Element grammar, Object object, String name) { if (object != null) { Element meta = grammar.addElement("meta"); meta.addAttribute("name", name); meta.addAttribute("content", object.toString()); } } private String getUri(String signal) { if (signal.equals("beep")) { return CPA_BEEP_URI; } else if (signal.equals("modem")) { return CPA_MODEM_URI; } else if (signal.equals("fax")) { return CPA_FAX_URI; } else if (signal.equals("fax-cng")) { return CPA_FAX_CNG_URI; } else if (signal.equals("dtmf")) { return CPA_DTMF_URI; } else if (signal.equals("ring")) { return CPA_RING_URI; } else if (signal.equals("sit")) { return CPA_SIT_URI; } else if (signal.equals("offhook")) { return CPA_OFFHOOK_URI; } else if (signal.equals("speech")) { return CPA_SPEECH_URI; } else if (signal.equals("foo")) { return CPA_FOO_URI; } return null; } private void createInputCompleteEvent(InputCompleteEvent event, Document document) throws Exception { if(event.getReason() instanceof Reason) { Reason reason = (Reason)event.getReason(); if(reason == Reason.MATCH && event.getSignalEvent() != null) { Element completeElement = document.addElement(new QName("complete", RAYO_COMPONENT_NAMESPACE)); createSignalEvent(event.getSignalEvent(), completeElement); } else { Element completeElement = addCompleteElement(document, event, COMPLETE_NAMESPACE); completeElement.addAttribute("confidence", String.valueOf(event.getConfidence())); if (event.getNlsml() != null) { completeElement.add(buildNsmlElement(event.getNlsml())); } if(event.getMode()!= null) { completeElement.addAttribute("mode", event.getMode().name().toLowerCase()); } if (event.getInterpretation() != null) { completeElement.addElement("interpretation").setText(event.getInterpretation()); } if (event.getUtterance() != null) { completeElement.addElement("utterance").setText(event.getUtterance()); } if (event.getTag() != null) { completeElement.addElement("tag").setText(event.getTag()); } if (event.getConcept() != null) { completeElement.addElement("concept").setText(event.getConcept()); } } } else { addCompleteElement(document, event, COMPLETE_NAMESPACE); } } private void createSignalEvent(SignalEvent event, Document document) throws Exception { Element signalElement = document.addElement(new QName("signal", NAMESPACE)); fillSignalEvent(event, signalElement); } private void createSignalEvent(SignalEvent event, Element element) throws Exception { Element signalElement = element.addElement(new QName("signal", NAMESPACE)); fillSignalEvent(event, signalElement); } private void fillSignalEvent(SignalEvent event, Element signalElement) { if (event.getType() != null) { signalElement.addAttribute("type", event.getType()); } if (event.getDuration() != null && event.getDuration() != -1) { signalElement.addAttribute("duration", String.valueOf(event.getDuration())); } if (event.getTone() != null) { signalElement.addAttribute("tone", String.valueOf(event.getTone())); } if (event.getSource() != null) { signalElement.addAttribute("source", String.valueOf(event.getSource())); } } private Element buildNsmlElement(String nlsml) throws Exception { //FIXME: We can't set the namespace after parsing sinc that would only update the namespace for the root element nlsml = nlsml.replace("<result", "<result xmlns=\"http://www.w3c.org/2000/11/nlsml\" "); Element element = (Element)DocumentHelper.parseText(nlsml).getRootElement(); return element; } }