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