001/* 002 * Copyright (c) 2011-2017 Nexmo Inc 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a copy 005 * of this software and associated documentation files (the "Software"), to deal 006 * in the Software without restriction, including without limitation the rights 007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 008 * copies of the Software, and to permit persons to whom the Software is 009 * furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 020 * THE SOFTWARE. 021 */ 022package com.nexmo.client.sms; 023 024import com.nexmo.client.HttpWrapper; 025import com.nexmo.client.NexmoClientException; 026import com.nexmo.client.NexmoResponseParseException; 027import com.nexmo.client.auth.SignatureAuthMethod; 028import com.nexmo.client.auth.TokenAuthMethod; 029import com.nexmo.client.legacyutils.XmlParser; 030import com.nexmo.client.sms.messages.Message; 031import com.nexmo.client.voice.endpoints.AbstractMethod; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.apache.http.HttpResponse; 035import org.apache.http.client.methods.RequestBuilder; 036import org.apache.http.impl.client.BasicResponseHandler; 037import org.w3c.dom.Document; 038import org.w3c.dom.Node; 039import org.w3c.dom.NodeList; 040 041import java.io.IOException; 042import java.io.UnsupportedEncodingException; 043import java.math.BigDecimal; 044import java.util.ArrayList; 045import java.util.List; 046 047public class SendMessageEndpoint extends AbstractMethod<Message, SmsSubmissionResult[]> { 048 private static final Log log = LogFactory.getLog(SmsClient.class); 049 050 private static final Class[] ALLOWED_AUTH_METHODS = new Class[]{SignatureAuthMethod.class, TokenAuthMethod.class}; 051 052 private static final String DEFAULT_URI = "https://rest.nexmo.com/sms/xml"; 053 054 private XmlParser xmlParser = new XmlParser(); 055 private String uri = DEFAULT_URI; 056 057 public SendMessageEndpoint(HttpWrapper httpWrapper) { 058 super(httpWrapper); 059 } 060 061 @Override 062 protected Class[] getAcceptableAuthMethods() { 063 return ALLOWED_AUTH_METHODS; 064 } 065 066 @Override 067 public RequestBuilder makeRequest(Message message) throws NexmoClientException, UnsupportedEncodingException { 068 RequestBuilder request = RequestBuilder.post(uri); 069 message.addParams(request); 070 return request; 071 } 072 073 @Override 074 public SmsSubmissionResult[] parseResponse(HttpResponse response) throws IOException { 075 return parseResponse(new BasicResponseHandler().handleResponse(response)); 076 } 077 078 protected SmsSubmissionResult[] parseResponse(String response) throws NexmoResponseParseException { 079 // parse the response doc ... 080 081 /* 082 We receive a response from the api that looks like this, parse the document 083 and turn it into an array of SmsSubmissionResult, one object per <message> node 084 085 <mt-submission-response> 086 <messages count='x'> 087 <message> 088 <to>xxx</to> 089 <messageId>xxx</messageId> 090 <status>xx</status> 091 <errorText>ff</errorText> 092 <clientRef>xxx</clientRef> 093 <remainingBalance>##.##</remainingBalance> 094 <messagePrice>##.##</messagePrice> 095 <reachability status='x' description='xxx' /> 096 <network>23410</network> 097 </message> 098 </messages> 099 </mt-submission-response> 100 */ 101 102 List<SmsSubmissionResult> results = new ArrayList<>(); 103 104 Document doc = xmlParser.parseXml(response); 105 106 NodeList replies = doc.getElementsByTagName("mt-submission-response"); 107 for (int i = 0; i < replies.getLength(); i++) { 108 Node reply = replies.item(i); 109 NodeList messageLists = reply.getChildNodes(); 110 for (int i2 = 0; i2 < messageLists.getLength(); i2++) { 111 Node messagesNode = messageLists.item(i2); 112 if (messagesNode.getNodeType() != Node.ELEMENT_NODE) 113 continue; 114 if (messagesNode.getNodeName().equals("messages")) { 115 NodeList messages = messagesNode.getChildNodes(); 116 for (int i3 = 0; i3 < messages.getLength(); i3++) { 117 SmsSubmissionResult message = parseMessageXmlNode(messages.item(i3)); 118 if (message != null) { 119 results.add(message); 120 } 121 } 122 } 123 } 124 } 125 126 return results.toArray(new SmsSubmissionResult[results.size()]); 127 } 128 129 private SmsSubmissionResult parseMessageXmlNode(Node messageNode) 130 throws NexmoResponseParseException { 131 if (messageNode.getNodeType() != Node.ELEMENT_NODE) 132 return null; 133 134 int status = -1; 135 String messageId = null; 136 String destination = null; 137 String errorText = null; 138 String clientReference = null; 139 BigDecimal remainingBalance = null; 140 BigDecimal messagePrice = null; 141 SmsSubmissionReachabilityStatus smsSubmissionReachabilityStatus = null; 142 String network = null; 143 144 NodeList nodes = messageNode.getChildNodes(); 145 for (int i4 = 0; i4 < nodes.getLength(); i4++) { 146 Node node = nodes.item(i4); 147 if (node.getNodeType() != Node.ELEMENT_NODE) 148 continue; 149 if (node.getNodeName().equals("messageId")) { 150 messageId = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 151 } else if (node.getNodeName().equals("to")) { 152 destination = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 153 } else if (node.getNodeName().equals("status")) { 154 String str = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 155 try { 156 status = Integer.parseInt(str); 157 } catch (NumberFormatException e) { 158 log.error("xml parser .. invalid value in <status> node [ " + str + " ] "); 159 status = SmsSubmissionResult.STATUS_INTERNAL_ERROR; 160 } 161 } else if (node.getNodeName().equals("errorText")) { 162 errorText = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 163 } else if (node.getNodeName().equals("clientRef")) { 164 clientReference = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 165 } else if (node.getNodeName().equals("remainingBalance")) { 166 String str = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 167 try { 168 if (str != null) 169 remainingBalance = new BigDecimal(str); 170 } catch (NumberFormatException e) { 171 log.error("xml parser .. invalid value in <remainingBalance> node [ " + str + " ] "); 172 } 173 } else if (node.getNodeName().equals("messagePrice")) { 174 String str = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 175 try { 176 if (str != null) 177 messagePrice = new BigDecimal(str); 178 } catch (NumberFormatException e) { 179 log.error("xml parser .. invalid value in <messagePrice> node [ " + str + " ] "); 180 } 181 } else if (node.getNodeName().equals("network")) { 182 network = node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue(); 183 } else { 184 log.error("xml parser .. unknown node found in status-return, expected [ messageId, to, status, errorText, clientRef, messagePrice, remainingBalance, reachability, network ] -- found [ " + node.getNodeName() + " ] "); 185 } 186 } 187 188 if (status == -1) 189 throw new NexmoResponseParseException("Xml Parser - did not find a <status> node"); 190 191 // Is this a temporary error ? 192 boolean temporaryError = (status == SmsSubmissionResult.STATUS_THROTTLED || 193 status == SmsSubmissionResult.STATUS_INTERNAL_ERROR || 194 status == SmsSubmissionResult.STATUS_TOO_MANY_BINDS); 195 196 return new SmsSubmissionResult(status, 197 destination, 198 messageId, 199 errorText, 200 clientReference, 201 remainingBalance, 202 messagePrice, 203 temporaryError, 204 smsSubmissionReachabilityStatus, 205 network); 206 } 207}