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.verify.endpoints; 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.legacyutils.XmlUtil; 031import com.nexmo.client.verify.BaseResult; 032import com.nexmo.client.verify.SearchRequest; 033import com.nexmo.client.verify.SearchResult; 034import com.nexmo.client.verify.VerifyResult; 035import com.nexmo.client.voice.endpoints.AbstractMethod; 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.apache.http.HttpResponse; 039import org.apache.http.client.methods.RequestBuilder; 040import org.apache.http.impl.client.BasicResponseHandler; 041import org.w3c.dom.Document; 042import org.w3c.dom.Element; 043import org.w3c.dom.Node; 044import org.w3c.dom.NodeList; 045 046import java.io.IOException; 047import java.io.UnsupportedEncodingException; 048import java.text.ParseException; 049import java.text.SimpleDateFormat; 050import java.util.ArrayList; 051import java.util.Date; 052import java.util.List; 053 054public class SearchEndpoint extends AbstractMethod<SearchRequest, SearchResult[]> { 055 private static final Log log = LogFactory.getLog(SearchEndpoint.class); 056 057 private static final Class[] ALLOWED_AUTH_METHODS = new Class[]{SignatureAuthMethod.class, TokenAuthMethod.class}; 058 059 private static final String DEFAULT_URI = "https://api.nexmo.com/verify/search/xml"; 060 061 private XmlParser xmlParser = new XmlParser(); 062 063 private String uri = DEFAULT_URI; 064 065 private static final ThreadLocal<SimpleDateFormat> sDateTimePattern = new ThreadLocal<SimpleDateFormat>() { 066 @Override 067 protected SimpleDateFormat initialValue() { 068 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 069 sdf.setLenient(false); 070 071 return sdf; 072 } 073 }; 074 075 /** 076 * Create a new SearchEndpoint. 077 * <p> 078 * This client is used for calling the verify API's search endpoint. 079 */ 080 public SearchEndpoint(HttpWrapper httpWrapper) { 081 super(httpWrapper); 082 } 083 084 @Override 085 protected Class[] getAcceptableAuthMethods() { 086 return ALLOWED_AUTH_METHODS; 087 } 088 089 @Override 090 public RequestBuilder makeRequest(SearchRequest request) throws NexmoClientException, UnsupportedEncodingException { 091 RequestBuilder result = RequestBuilder.post(this.uri); 092 if (request.getRequestIds().length == 1) { 093 result.addParameter("request_id", request.getRequestIds()[0]); 094 } else { 095 for (String requestId : request.getRequestIds()) 096 result.addParameter("request_ids", requestId); 097 } 098 return result; 099 } 100 101 @Override 102 public SearchResult[] parseResponse(HttpResponse response) throws IOException { 103 String json = new BasicResponseHandler().handleResponse(response); 104 return parseSearchResponse(json); 105 } 106 107 public SearchResult search(String requestId) throws IOException, NexmoClientException { 108 SearchResult[] result = search(new String[]{requestId}); 109 return result != null && result.length > 0 ? result[0] : null; 110 } 111 112 public SearchResult[] search(String... requestIds) throws IOException, NexmoClientException { 113 return this.execute(new SearchRequest(requestIds)); 114 } 115 116 protected SearchResult[] parseSearchResponse(String response) throws NexmoResponseParseException { 117 Document doc = xmlParser.parseXml(response); 118 119 Element root = doc.getDocumentElement(); 120 if ("verify_response".equals(root.getNodeName())) { 121 // error response 122 VerifyResult result = SharedParsers.parseVerifyResponseXmlNode(root); 123 return new SearchResult[]{ 124 new SearchResult(result.getStatus(), 125 result.getRequestId(), 126 null, 127 null, 128 null, 129 0, null, 130 null, 131 null, null, 132 null, null, 133 null, 134 result.getErrorText(), 135 result.isTemporaryError()) 136 }; 137 } else if (("verify_request").equals(root.getNodeName())) { 138 return new SearchResult[]{parseVerifyRequestXmlNode(root)}; 139 } else if ("verification_requests".equals(root.getNodeName())) { 140 List<SearchResult> results = new ArrayList<>(); 141 142 NodeList fields = root.getChildNodes(); 143 for (int i = 0; i < fields.getLength(); i++) { 144 Node node = fields.item(i); 145 if (node.getNodeType() != Node.ELEMENT_NODE) 146 continue; 147 148 if ("verify_request".equals(node.getNodeName())) 149 results.add(parseVerifyRequestXmlNode((Element) node)); 150 } 151 152 return results.toArray(new SearchResult[results.size()]); 153 } else { 154 throw new NexmoResponseParseException("No valid response found [ " + response + "] "); 155 } 156 } 157 158 protected static SearchResult parseVerifyRequestXmlNode(Element root) throws NexmoResponseParseException { 159 String requestId = null; 160 String accountId = null; 161 String number = null; 162 String senderId = null; 163 Date dateSubmitted = null; 164 Date dateFinalized = null; 165 Date firstEventDate = null; 166 Date lastEventDate = null; 167 float price = -1; 168 String currency = null; 169 SearchResult.VerificationStatus status = null; 170 List<SearchResult.VerifyCheck> checks = new ArrayList<>(); 171 String errorText = null; 172 173 NodeList fields = root.getChildNodes(); 174 for (int i = 0; i < fields.getLength(); i++) { 175 Node node = fields.item(i); 176 if (node.getNodeType() != Node.ELEMENT_NODE) 177 continue; 178 179 String name = node.getNodeName(); 180 if ("request_id".equals(name)) { 181 requestId = XmlUtil.stringValue(node); 182 } else if ("account_id".equals(name)) { 183 accountId = XmlUtil.stringValue(node); 184 } else if ("status".equals(name)) { 185 String str = XmlUtil.stringValue(node); 186 if (str != null) { 187 try { 188 status = SearchResult.VerificationStatus.valueOf(str.replace(' ', '_')); 189 } catch (IllegalArgumentException e) { 190 log.error("xml parser .. invalid value in <status> node [ " + str + " ] "); 191 } 192 } 193 } else if ("number".equals(name)) { 194 number = XmlUtil.stringValue(node); 195 } else if ("price".equals(name)) { 196 String str = XmlUtil.stringValue(node); 197 try { 198 if (str != null) 199 price = Float.parseFloat(str); 200 } catch (NumberFormatException e) { 201 log.error("xml parser .. invalid value in <price> node [ " + str + " ] "); 202 } 203 } else if ("currency".equals(name)) { 204 currency = XmlUtil.stringValue(node); 205 } else if ("sender_id".equals(name)) { 206 senderId = XmlUtil.stringValue(node); 207 } else if ("date_submitted".equals(name)) { 208 String str = XmlUtil.stringValue(node); 209 if (str != null) { 210 try { 211 dateSubmitted = parseDateTime(str); 212 } catch (ParseException e) { 213 log.error("xml parser .. invalid value in <date_submitted> node [ " + str + " ] "); 214 } 215 } 216 } else if ("date_finalized".equals(name)) { 217 String str = XmlUtil.stringValue(node); 218 if (str != null) { 219 try { 220 dateFinalized = parseDateTime(str); 221 } catch (ParseException e) { 222 log.error("xml parser .. invalid value in <date_finalized> node [ " + str + " ] "); 223 } 224 } 225 } else if ("first_event_date".equals(name)) { 226 String str = XmlUtil.stringValue(node); 227 if (str != null) { 228 try { 229 firstEventDate = parseDateTime(str); 230 } catch (ParseException e) { 231 log.error("xml parser .. invalid value in <first_event_date> node [ " + str + " ] "); 232 } 233 } 234 } else if ("last_event_date".equals(name)) { 235 String str = XmlUtil.stringValue(node); 236 if (str != null) { 237 try { 238 lastEventDate = parseDateTime(str); 239 } catch (ParseException e) { 240 log.error("xml parser .. invalid value in <last_event_date> node [ " + str + " ] "); 241 } 242 } 243 } else if ("checks".equals(name)) { 244 NodeList checkNodes = node.getChildNodes(); 245 for (int j = 0; j < checkNodes.getLength(); j++) { 246 Node checkNode = checkNodes.item(j); 247 if (checkNode.getNodeType() != Node.ELEMENT_NODE) 248 continue; 249 250 if ("check".equals(checkNode.getNodeName())) 251 checks.add(parseCheckXmlNode((Element) checkNode)); 252 } 253 } else if ("error_text".equals(name)) { 254 errorText = XmlUtil.stringValue(node); 255 } 256 } 257 258 if (status == null) 259 throw new NexmoResponseParseException("Xml Parser - did not find a <status> node"); 260 261 return new SearchResult(BaseResult.STATUS_OK, 262 requestId, 263 accountId, 264 status, 265 number, 266 price, currency, 267 senderId, 268 dateSubmitted, dateFinalized, 269 firstEventDate, lastEventDate, 270 checks, 271 errorText, false); 272 } 273 274 protected static SearchResult.VerifyCheck parseCheckXmlNode(Element root) throws NexmoResponseParseException { 275 String code = null; 276 SearchResult.VerifyCheck.Status status = null; 277 Date dateReceived = null; 278 String ipAddress = null; 279 280 NodeList fields = root.getChildNodes(); 281 for (int i = 0; i < fields.getLength(); i++) { 282 Node node = fields.item(i); 283 if (node.getNodeType() != Node.ELEMENT_NODE) 284 continue; 285 286 String name = node.getNodeName(); 287 if ("code".equals(name)) { 288 code = XmlUtil.stringValue(node); 289 } else if ("status".equals(name)) { 290 String str = XmlUtil.stringValue(node); 291 if (str != null) { 292 try { 293 status = SearchResult.VerifyCheck.Status.valueOf(str); 294 } catch (IllegalArgumentException e) { 295 log.error("xml parser .. invalid value in <status> node [ " + str + " ] "); 296 } 297 } 298 } else if ("ip_address".equals(name)) { 299 ipAddress = XmlUtil.stringValue(node); 300 } else if ("date_received".equals(name)) { 301 String str = XmlUtil.stringValue(node); 302 if (str != null) { 303 try { 304 dateReceived = parseDateTime(str); 305 } catch (ParseException e) { 306 log.error("xml parser .. invalid value in <date_received> node [ " + str + " ] "); 307 } 308 } 309 } 310 } 311 312 if (status == null) 313 throw new NexmoResponseParseException("Xml Parser - did not find a <status> node"); 314 315 return new SearchResult.VerifyCheck(dateReceived, code, status, ipAddress); 316 } 317 318 private static Date parseDateTime(String str) throws ParseException { 319 return sDateTimePattern.get().parse(str); 320 } 321 322}