package com.rayo.core.xml.providers; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Namespace; import org.dom4j.Node; import org.dom4j.QName; import org.dom4j.Text; import org.joda.time.Duration; import com.rayo.core.validation.Messages; import com.rayo.core.validation.ValidationException; import com.rayo.core.validation.Validator; import com.rayo.core.verb.InputMode; import com.rayo.core.verb.Ssml; import com.rayo.core.verb.VerbCompleteEvent; import com.rayo.core.verb.VerbCompleteEvent.Reason; import com.rayo.core.verb.VerbCompleteReason; import com.rayo.core.xml.XmlProvider; import com.rayo.core.xml.XmlProviderManager; import com.voxeo.utils.Enums; public abstract class BaseProvider implements XmlProvider { protected static final Namespace RAYO_NAMESPACE = new Namespace("", "urn:xmpp:rayo:1"); protected static final Namespace RAYO_COMPONENT_NAMESPACE = new Namespace("", "urn:xmpp:rayo:ext:1"); protected static final Namespace RAYO_COMPLETE_NAMESPACE = new Namespace("", "urn:xmpp:rayo:ext:complete:1"); private Validator validator; private XmlProviderManager manager; private List<String> namespaces; private List<Class<?>> classes; @Override @SuppressWarnings("unchecked") public Object fromXML(Element element) { Object returnValue = null; try { returnValue = processElement(element); if (returnValue == null) { return null; } } catch (ValidationException ve) { throw ve; } catch (Exception e) { throw new IllegalStateException(e); } validator.validate(returnValue); return returnValue; } @Override public Element toXML(Object object) { try { Document document = DocumentHelper.createDocument(); generateDocument(object, document); return document.getRootElement(); } catch (Exception e) { throw new IllegalStateException(e); } } protected abstract Object processElement(Element element) throws Exception; protected abstract void generateDocument(Object object, Document document) throws Exception; protected VerbCompleteEvent toVerbCompleteEvent(Element element) { String reasonValue = element.getName().toUpperCase(); Reason reason = VerbCompleteEvent.Reason.valueOf(reasonValue); VerbCompleteEvent event = new VerbCompleteEvent(reason); if(reason == Reason.ERROR) { event.setErrorText(element.getText()); } return event; } protected Map<String, String> grabHeaders(Element node) { Map<String, String> headers = new HashMap<String, String>(); @SuppressWarnings("unchecked") List<Element> elements = node.elements("header"); for (Element element : elements) { headers.put(element.attributeValue("name"), element.attributeValue("value")); } return headers; } @SuppressWarnings("unchecked") protected Ssml extractSsml(Element node) throws URISyntaxException { StringBuilder builder = new StringBuilder(); List<Node> elements = node.content(); for (Node element : elements) { if (element instanceof Text || element instanceof Element) { String xml = element.asXML(); //TODO: Better namespaces cleanup. See for exmaple GH issue #19. Options are // move everything to DOM or change the rayo spec to use CDATA. But currently // speak elements let people enter arbitrary xml which causes lot of trouble xml = xml.replaceAll(" xmlns=\"[^\"]*\"", ""); xml = xml.replaceAll(" xmlns:xml=\"[^\"]*\"", ""); xml = xml.replaceAll(" xml:xml", " xml"); builder.append(xml); } } Ssml ssml = new Ssml(builder.toString()); ssml.setVoice(node.attributeValue("voice")); return ssml; } protected void addHeaders(Map<String, String> map, Element node) { if (map != null) { for (Map.Entry<String, String> entry : map.entrySet()) { Element header = node.addElement("header"); header.addAttribute("name", entry.getKey()); header.addAttribute("value", entry.getValue()); } } } protected void addSsml(Ssml item, Element root) throws DocumentException { if (item != null) { if (item.getVoice() != null) { root.addAttribute("voice", item.getVoice()); } StringBuilder builder = new StringBuilder("<wrapper>"); builder.append(item.getText()); builder.append("</wrapper>"); Document ssmlDoc = DocumentHelper.parseText(builder.toString()); for (Object element : ssmlDoc.getRootElement().content()) { if (element instanceof Text) { root.addText(((Text) element).asXML()); } else if (element instanceof Element) { root.add(((Element) element).createCopy()); } } } } protected InputMode loadInputMode(Element element) { try { return InputMode.valueOf(element.attributeValue("mode").toUpperCase()); } catch (Exception e) { throw new ValidationException(Messages.INVALID_INPUT_MODE); } } protected URI toURI(String string) { if (string == null || string.trim().equals("")) { return null; } string = string.trim(); try { return new URI(string); } catch (URISyntaxException e) { throw new ValidationException(Messages.INVALID_URI); } } protected Boolean toBoolean(String attribute, Element element) { String string = element.attributeValue(attribute); if (string == null) { throw new ValidationException(String.format(Messages.INVALID_BOOLEAN, attribute)); } string = string.toLowerCase(); if (string.equals("false") || string.equals("true")) { return Boolean.valueOf(string); } throw new ValidationException(String.format(Messages.INVALID_BOOLEAN, attribute)); } protected int toInteger(String attribute, Element element) { String string = element.attributeValue(attribute); if (string == null) { throw new ValidationException(String.format(Messages.INVALID_INTEGER, attribute)); } string = string.toLowerCase(); try { return Integer.parseInt(string); } catch (NumberFormatException nfe) { throw new ValidationException(String.format(Messages.INVALID_INTEGER, attribute)); } } protected long toLong(String attribute, Element element) { String string = element.attributeValue(attribute); if (string == null) { throw new ValidationException(String.format(Messages.INVALID_LONG, attribute)); } string = string.toLowerCase(); try { return Long.parseLong(string); } catch (NumberFormatException nfe) { throw new ValidationException(String.format(Messages.INVALID_LONG, attribute)); } } protected float toFloat(String attribute, Element element) { String string = element.attributeValue(attribute); if (string == null) { throw new ValidationException(String.format(Messages.INVALID_FLOAT, attribute)); } string = string.toLowerCase(); try { return Float.parseFloat(string); } catch (NumberFormatException nfe) { throw new ValidationException(String.format(Messages.INVALID_FLOAT, attribute)); } } protected Duration toDuration(String name, Element element) { try { return new Duration(Long.parseLong(element.attributeValue(name))); } catch (IllegalArgumentException iae) { throw new ValidationException(String.format(Messages.INVALID_DURATION, name)); } } protected Duration toTimeout(String value) { try { return new Duration(Long.parseLong(value)); } catch (IllegalArgumentException iae) { throw new ValidationException(Messages.INVALID_TIMEOUT); } } protected Float toFloatConfidence(String value) { try { return Float.valueOf(value); } catch (NumberFormatException nfe) { throw new ValidationException(Messages.INVALID_CONFIDENCE); } } protected Character toTerminator(String value) { if (value == null || value.length() != 1) { throw new ValidationException(Messages.INVALID_TERMINATOR); } return new Character(value.charAt(0)); } protected <T extends Enum<T>> T toEnum(Class<T> enumClass, String name, Element element) { try { return Enum.valueOf(enumClass, element.attributeValue(name).toUpperCase()); } catch (IllegalArgumentException iae) { throw new ValidationException(String.format(Messages.INVALID_ENUM, name)); } } @SuppressWarnings("unchecked") protected List<Element> children(Element element) { return (List<Element>) element.elements(); } @SuppressWarnings({ "rawtypes", "unchecked" }) protected VerbCompleteEvent fillCompleteEvent(Element completeElement, VerbCompleteEvent event, Class<? extends Enum> reasonClazz) { VerbCompleteReason reason = null; List<Element> children = children(completeElement); if (!children.isEmpty()) { Element reasonElement = children.get(0); String reasonName = reasonElement.getName().toUpperCase(); reason = Enums.valueOf(VerbCompleteEvent.Reason.class, reasonName, null); if (reason == null) { reason = (VerbCompleteReason) Enum.valueOf(reasonClazz, reasonName); } if (reason == VerbCompleteEvent.Reason.ERROR) { event.setErrorText(reasonElement.getText()); } } event.setReason(reason); return event; } protected Element addCompleteElement(Document document, VerbCompleteEvent event, Namespace completeNamespace) { Element reasonElement = null; Element completeElement = document.addElement(new QName("complete", RAYO_COMPONENT_NAMESPACE)); VerbCompleteReason reason = event.getReason(); if (reason instanceof VerbCompleteEvent.Reason) { Reason globalReason = (VerbCompleteEvent.Reason) reason; String reasonValue = globalReason.name().toLowerCase(); reasonElement = completeElement.addElement(new QName(reasonValue, RAYO_COMPLETE_NAMESPACE)); if (globalReason == VerbCompleteEvent.Reason.ERROR) { if (event.getErrorText() != null) { reasonElement.setText(event.getErrorText()); } } } else { Enum<?> reasonEnum = (Enum<?>) reason; reasonElement = completeElement.addElement(new QName(reasonEnum.name().toLowerCase(), completeNamespace)); } return reasonElement; } public void setValidator(Validator validator) { this.validator = validator; } protected Validator getValidator() { return validator; } @Override public boolean handles(Element element) { return handles(element.getNamespace()); } @Override public boolean handles(Namespace ns) { for (String namespace : namespaces) { if (namespace.equals(ns.getURI())) { return true; } } return false; } @Override public XmlProviderManager getManager() { return manager; } @Override public void setManager(XmlProviderManager manager) { this.manager = manager; } public List<String> getNamespaces() { return namespaces; } public void setNamespaces(List<String> namespaces) { this.namespaces = namespaces; } @Override public boolean handles(Class<?> clazz) { return classes.contains(clazz); } public void setClasses(List<Class<?>> classes) { this.classes = classes; } public List<Class<?>> getClasses() { return classes; } }