Coverage Report - org.codeforamerica.open311.facade.APIWrapper
 
Classes in this File Line Coverage Branch Coverage Complexity
APIWrapper
70%
96/137
56%
17/30
3.75
 
 1  
 package org.codeforamerica.open311.facade;
 2  
 
 3  
 import java.io.IOException;
 4  
 import java.net.MalformedURLException;
 5  
 import java.net.URL;
 6  
 import java.util.HashMap;
 7  
 import java.util.LinkedList;
 8  
 import java.util.List;
 9  
 import java.util.Map;
 10  
 
 11  
 import org.codeforamerica.open311.facade.data.Attribute;
 12  
 import org.codeforamerica.open311.facade.data.POSTServiceRequestResponse;
 13  
 import org.codeforamerica.open311.facade.data.RelationshipManager;
 14  
 import org.codeforamerica.open311.facade.data.Service;
 15  
 import org.codeforamerica.open311.facade.data.ServiceDefinition;
 16  
 import org.codeforamerica.open311.facade.data.ServiceRequest;
 17  
 import org.codeforamerica.open311.facade.data.ServiceRequestIdResponse;
 18  
 import org.codeforamerica.open311.facade.data.operations.GETServiceRequestsFilter;
 19  
 import org.codeforamerica.open311.facade.data.operations.POSTServiceRequestData;
 20  
 import org.codeforamerica.open311.facade.exceptions.APIWrapperException;
 21  
 import org.codeforamerica.open311.facade.exceptions.APIWrapperException.Error;
 22  
 import org.codeforamerica.open311.facade.exceptions.DataParsingException;
 23  
 import org.codeforamerica.open311.facade.exceptions.GeoReportV2Error;
 24  
 import org.codeforamerica.open311.facade.exceptions.InvalidValueError;
 25  
 import org.codeforamerica.open311.internals.caching.Cache;
 26  
 import org.codeforamerica.open311.internals.logging.LogManager;
 27  
 import org.codeforamerica.open311.internals.network.NetworkManager;
 28  
 import org.codeforamerica.open311.internals.network.URLBuilder;
 29  
 import org.codeforamerica.open311.internals.parsing.DataParser;
 30  
 
 31  
 /**
 32  
  * Base class of the API. This is the entry point to the system. You can build
 33  
  * objects of this class using the {@link APIWrapperFactory} class.
 34  
  * 
 35  
  * @author Santiago MunĂ­n <santimunin@gmail.com>
 36  
  * 
 37  
  */
 38  
 public class APIWrapper {
 39  
 
 40  
         private String endpointUrl;
 41  
         private String apiKey;
 42  
         private String jurisdictionId;
 43  
         private EndpointType type;
 44  
         private DataParser dataParser;
 45  
         private NetworkManager networkManager;
 46  
         private URLBuilder urlBuilder;
 47  
         private Cache cache;
 48  
         /**
 49  
          * Useful to log events.
 50  
          */
 51  10
         private LogManager logManager = LogManager.getInstance();
 52  
 
 53  
         /**
 54  
          * Builds an API wrapper from its components. Note that this constructor
 55  
          * can't be invoked from outside the package. If you are a user the library
 56  
          * please check the {@link APIWrapperFactory} class.
 57  
          * 
 58  
          * @param endpointUrl
 59  
          *            Base url of the endpoint.
 60  
          * @param type
 61  
          *            Type of the server.
 62  
          * @param dataParser
 63  
          *            Instance of
 64  
          * @param networkManager
 65  
          */
 66  
         /* package */APIWrapper(String endpointUrl, Format format,
 67  
                         EndpointType type, DataParser dataParser,
 68  
                         NetworkManager networkManager, Cache cache, String jurisdictionId,
 69  
                         String apiKey) {
 70  10
                 super();
 71  10
                 this.endpointUrl = endpointUrl;
 72  10
                 this.type = type;
 73  10
                 this.dataParser = dataParser;
 74  10
                 this.networkManager = networkManager;
 75  10
                 this.cache = cache;
 76  10
                 this.jurisdictionId = jurisdictionId;
 77  10
                 this.apiKey = apiKey;
 78  10
                 this.urlBuilder = new URLBuilder(endpointUrl, this.jurisdictionId,
 79  
                                 format.toString());
 80  10
         }
 81  
 
 82  
         public String getEndpointUrl() {
 83  9
                 return endpointUrl;
 84  
         }
 85  
 
 86  
         /**
 87  
          * Returns a string with some info.
 88  
          * 
 89  
          * @return endpointUrl - type.
 90  
          */
 91  
         public String getWrapperInfo() {
 92  3
                 return endpointUrl + " - " + type.toString();
 93  
         }
 94  
 
 95  
         /**
 96  
          * Returns the cache used by the wrapper.
 97  
          * 
 98  
          * @return Cache used by the wrapper, useful to delete it.
 99  
          */
 100  
         public Cache getCache() {
 101  0
                 return cache;
 102  
         }
 103  
 
 104  
         /**
 105  
          * Updates the format of the wrapper. A new {@link URLBuilder} will be
 106  
          * instantiated.
 107  
          * 
 108  
          * @param format
 109  
          *            New format.
 110  
          */
 111  
         public void setFormat(Format format) {
 112  0
                 logManager.logInfo(this,
 113  
                                 "Changing wrapper format to " + format.toString());
 114  0
                 urlBuilder = new URLBuilder(endpointUrl, jurisdictionId,
 115  
                                 format.toString());
 116  0
         }
 117  
 
 118  
         /**
 119  
          * Gets a list of services from the endpoint. <a
 120  
          * href="http://wiki.open311.org/GeoReport_v2#GET_Service_List">More
 121  
          * info</a>
 122  
          * 
 123  
          * @return List of fetched services.
 124  
          * @throws APIWrapperException
 125  
          *             If there was any problem (data parsing, I/O...).
 126  
          */
 127  
         public List<Service> getServiceList() throws APIWrapperException {
 128  3
                 logManager.logInfo(this, "GET Service List");
 129  3
                 List<Service> result = cache
 130  
                                 .retrieveCachedServiceList(this.endpointUrl);
 131  3
                 if (result == null) {
 132  3
                         result = askEndpointForTheServiceList();
 133  3
                         cache.saveListOfServices(endpointUrl, result);
 134  
                 }
 135  3
                 if (result != null) {
 136  3
                         RelationshipManager.getInstance().addServiceWrapperRelationship(
 137  
                                         result, this);
 138  
                 }
 139  3
                 return result;
 140  
         }
 141  
 
 142  
         /**
 143  
          * Makes a network operation to ask the endpoint for the service list.
 144  
          * 
 145  
          * @return The list of services of the endpoint.
 146  
          * @throws APIWrapperException
 147  
          *             If there was any problem (data parsing, I/O...).
 148  
          */
 149  
         private List<Service> askEndpointForTheServiceList()
 150  
                         throws APIWrapperException {
 151  3
                 logManager.logInfo(this,
 152  
                                 "GET Service List is not cached, asking endpoint.");
 153  3
                 String rawServiceListData = "";
 154  
                 try {
 155  3
                         URL serviceListUrl = urlBuilder.buildGetServiceListUrl();
 156  3
                         rawServiceListData = networkGet(serviceListUrl);
 157  3
                         return dataParser.parseServiceList(rawServiceListData);
 158  0
                 } catch (DataParsingException e) {
 159  0
                         tryToParseError(rawServiceListData);
 160  0
                         return null;
 161  0
                 } catch (MalformedURLException e) {
 162  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 163  
                                         null);
 164  
                 }
 165  
         }
 166  
 
 167  
         /**
 168  
          * Gets the service definition of a concrete service. <a
 169  
          * href="http://wiki.open311.org/GeoReport_v2#GET_Service_Definition">More
 170  
          * info</a>
 171  
          * 
 172  
          * @param serviceCode
 173  
          *            Code of the service of interest.
 174  
          * @return All the information related to the given code.
 175  
          * @throws APIWrapperException
 176  
          *             If there was any problem (data parsing, I/O...).
 177  
          */
 178  
         public ServiceDefinition getServiceDefinition(String serviceCode)
 179  
                         throws APIWrapperException {
 180  3
                 logManager.logInfo(this, "GET Service Definition (service_code: "
 181  
                                 + serviceCode + ")");
 182  3
                 ServiceDefinition result = cache.retrieveCachedServiceDefinition(
 183  
                                 endpointUrl, serviceCode);
 184  3
                 if (result == null) {
 185  3
                         result = askEndpointForAServiceDefinition(serviceCode);
 186  3
                         cache.saveServiceDefinition(endpointUrl, serviceCode, result);
 187  
                 }
 188  3
                 return result;
 189  
         }
 190  
 
 191  
         /**
 192  
          * Makes a network operation to ask the endpoint for the service definition.
 193  
          * 
 194  
          * @param serviceCode
 195  
          *            Code of the service of interest.
 196  
          * @return All the information related to the given code.
 197  
          * @throws APIWrapperException
 198  
          */
 199  
         private ServiceDefinition askEndpointForAServiceDefinition(
 200  
                         String serviceCode) throws APIWrapperException {
 201  3
                 logManager.logInfo(this, "GET Service Definition (service_code: "
 202  
                                 + serviceCode + ") is not cached, asking endpoint.");
 203  3
                 String rawServiceDefinitionData = "";
 204  
                 try {
 205  3
                         URL serviceDefinitionUrl = urlBuilder
 206  
                                         .buildGetServiceDefinitionUrl(serviceCode);
 207  3
                         rawServiceDefinitionData = networkGet(serviceDefinitionUrl);
 208  3
                         return dataParser.parseServiceDefinition(rawServiceDefinitionData);
 209  0
                 } catch (DataParsingException e) {
 210  0
                         tryToParseError(rawServiceDefinitionData);
 211  0
                         return null;
 212  0
                 } catch (MalformedURLException e) {
 213  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 214  
                                         null);
 215  
                 }
 216  
         }
 217  
 
 218  
         /**
 219  
          * This function is useful when the POST Service Request returns a token.
 220  
          * 
 221  
          * @param token
 222  
          *            Given token.
 223  
          * @return A service id useful to the operation GET Service Request.
 224  
          * @throws APIWrapperException
 225  
          *             If there was any problem.
 226  
          */
 227  
         public ServiceRequestIdResponse getServiceRequestIdFromToken(String token)
 228  
                         throws APIWrapperException {
 229  1
                 logManager.logInfo(this, "GET Service Request Id from token (token: "
 230  
                                 + token + "), asking endpoint.");
 231  1
                 String rawServiceRequestId = "";
 232  
                 try {
 233  1
                         URL serviceDefinitionUrl = urlBuilder
 234  
                                         .buildGetServiceRequestIdFromATokenUrl(token);
 235  1
                         rawServiceRequestId = networkGet(serviceDefinitionUrl);
 236  1
                         return dataParser
 237  
                                         .parseServiceRequestIdFromAToken(rawServiceRequestId);
 238  0
                 } catch (DataParsingException e) {
 239  0
                         tryToParseError(rawServiceRequestId);
 240  0
                         return null;
 241  0
                 } catch (MalformedURLException e) {
 242  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 243  
                                         null);
 244  
                 }
 245  
         }
 246  
 
 247  
         /**
 248  
          * Retrieves all the service requests which accord to the given data.
 249  
          * 
 250  
          * @param operationData
 251  
          *            An object with all the desired optional filtering parameters
 252  
          *            to send.
 253  
          * @return A list of service requests.
 254  
          * @throws APIWrapperException
 255  
          *             If there was any problem.
 256  
          */
 257  
         public List<ServiceRequest> getServiceRequests(
 258  
                         GETServiceRequestsFilter operationData) throws APIWrapperException {
 259  2
                 logManager.logInfo(this, "GET Service Requests");
 260  2
                 operationData = operationData == null ? new GETServiceRequestsFilter()
 261  
                                 : operationData;
 262  2
                 List<ServiceRequest> result = cache.retrieveCachedServiceRequests(
 263  
                                 endpointUrl, operationData);
 264  2
                 if (result == null) {
 265  2
                         result = askEndpointForServiceRequests(operationData);
 266  2
                         cache.saveServiceRequestList(endpointUrl, operationData, result);
 267  
                 }
 268  2
                 return result;
 269  
         }
 270  
 
 271  
         /**
 272  
          * Makes a network operation to ask the endpoint for service requests.
 273  
          * 
 274  
          * @param operationData
 275  
          *            Filter to apply to the search in the endpoint.
 276  
          * @return A list of service requests.
 277  
          * @throws APIWrapperException
 278  
          *             If there was any problem.
 279  
          */
 280  
         private List<ServiceRequest> askEndpointForServiceRequests(
 281  
                         GETServiceRequestsFilter operationData) throws APIWrapperException {
 282  2
                 logManager
 283  
                                 .logInfo(this,
 284  
                                                 "GET Service Requests with the given filter is not cached, asking endpoint.");
 285  2
                 String rawServiceRequests = "";
 286  
                 try {
 287  2
                         URL serviceRequestsUrl = operationData != null ? urlBuilder
 288  
                                         .buildGetServiceRequests(operationData
 289  
                                                         .getOptionalParametersMap()) : urlBuilder
 290  
                                         .buildGetServiceRequests(null);
 291  2
                         rawServiceRequests = networkGet(serviceRequestsUrl);
 292  2
                         return dataParser.parseServiceRequests(rawServiceRequests);
 293  0
                 } catch (DataParsingException e) {
 294  0
                         tryToParseError(rawServiceRequests);
 295  0
                         return null;
 296  0
                 } catch (MalformedURLException e) {
 297  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 298  
                                         null);
 299  
                 }
 300  
         }
 301  
 
 302  
         /**
 303  
          * GET Service Request operation.
 304  
          * 
 305  
          * @param serviceRequestId
 306  
          *            ID of the request to be fetched.
 307  
          * @return The info related to the given ID.
 308  
          * @throws APIWrapperException
 309  
          *             If there was any problem.
 310  
          */
 311  
         public ServiceRequest getServiceRequest(String serviceRequestId)
 312  
                         throws APIWrapperException {
 313  2
                 logManager.logInfo(this, "GET Service Request (service_request_id: "
 314  
                                 + serviceRequestId + ")");
 315  2
                 ServiceRequest result = cache.retrieveCachedServiceRequest(endpointUrl,
 316  
                                 serviceRequestId);
 317  2
                 if (result == null) {
 318  2
                         result = askEndpointForAServiceRequest(serviceRequestId);
 319  2
                         cache.saveSingleServiceRequest(endpointUrl, serviceRequestId,
 320  
                                         result);
 321  
                 }
 322  2
                 return result;
 323  
         }
 324  
 
 325  
         /**
 326  
          * Makes a network operation to ask the endpoint for a service request.
 327  
          * 
 328  
          * @param serviceRequestId
 329  
          *            Id of the request.
 330  
          * @return The info related to the given ID.
 331  
          * @throws APIWrapperException
 332  
          *             If there was any problem.
 333  
          */
 334  
         private ServiceRequest askEndpointForAServiceRequest(String serviceRequestId)
 335  
                         throws APIWrapperException {
 336  2
                 logManager.logInfo(this, "GET Service Request (service_request_id: "
 337  
                                 + serviceRequestId + ") is not cached, asking endpoint.");
 338  2
                 String rawServiceRequests = "";
 339  
                 try {
 340  2
                         URL serviceRequestsUrl = urlBuilder
 341  
                                         .buildGetServiceRequest(serviceRequestId);
 342  2
                         rawServiceRequests = networkGet(serviceRequestsUrl);
 343  2
                         List<ServiceRequest> parsedServiceRequests = dataParser
 344  
                                         .parseServiceRequests(rawServiceRequests);
 345  2
                         return parsedServiceRequests.size() > 0 ? parsedServiceRequests
 346  
                                         .get(0) : null;
 347  0
                 } catch (DataParsingException e) {
 348  0
                         tryToParseError(rawServiceRequests);
 349  0
                         return null;
 350  0
                 } catch (MalformedURLException e) {
 351  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 352  
                                         null);
 353  
                 }
 354  
         }
 355  
 
 356  
         /**
 357  
          * Performs a POST Service Request operation.
 358  
          * 
 359  
          * @param operationData
 360  
          *            An object with all the desired parameters and attributes to be
 361  
          *            sent.
 362  
          * @return The server's response.
 363  
          * @throws APIWrapperException
 364  
          *             If there was any problem.
 365  
          */
 366  
         public POSTServiceRequestResponse postServiceRequest(
 367  
                         POSTServiceRequestData operationData) throws APIWrapperException {
 368  5
                 logManager.logInfo(this, "POST Service Request");
 369  5
                 if (operationData == null) {
 370  0
                         throw new InvalidValueError("The given parameter is null");
 371  
                 }
 372  5
                 Map<String, String> optionalArguments = operationData
 373  
                                 .getBodyRequestParameters() != null ? operationData
 374  
                                 .getBodyRequestParameters() : new HashMap<String, String>();
 375  5
                 List<Attribute> attributes = operationData.getAttributes() != null ? operationData
 376  
                                 .getAttributes() : new LinkedList<Attribute>();
 377  
                 try {
 378  5
                         URL url = urlBuilder.buildPostServiceRequestUrl();
 379  5
                         return postServiceRequestInternal(url, optionalArguments,
 380  
                                         attributes);
 381  0
                 } catch (MalformedURLException e) {
 382  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 383  
                                         null);
 384  
                 }
 385  
         }
 386  
 
 387  
         /**
 388  
          * Performs the POST service request with already built URL and body.
 389  
          * 
 390  
          * @param url
 391  
          *            Target.
 392  
          * @param arguments
 393  
          *            Pairs (key,value) of optional arguments.
 394  
          * @param attributes
 395  
          *            List of attributes (check the GeoReport v2 wiki).
 396  
          * @return The server's response.
 397  
          * @throws APIWrapperException
 398  
          *             If there was any problem.
 399  
          * @throws MalformedURLException
 400  
          *             If any attribute is not valid.
 401  
          */
 402  
         private POSTServiceRequestResponse postServiceRequestInternal(URL url,
 403  
                         Map<String, String> arguments, List<Attribute> attributes)
 404  
                         throws APIWrapperException, MalformedURLException {
 405  5
                 if (apiKey.length() > 0) {
 406  1
                         arguments.put("api_key", apiKey);
 407  
                 }
 408  5
                 if (jurisdictionId.length() > 0) {
 409  0
                         arguments.put("jurisdiction_id", jurisdictionId);
 410  
                 }
 411  5
                 Map<String, String> postArguments = urlBuilder
 412  
                                 .buildPostServiceRequestBody(arguments, attributes);
 413  5
                 String rawPostServiceRequestResponse = networkPost(url, postArguments);
 414  
                 try {
 415  4
                         return dataParser
 416  
                                         .parsePostServiceRequestResponse(rawPostServiceRequestResponse);
 417  1
                 } catch (DataParsingException e) {
 418  1
                         tryToParseError(rawPostServiceRequestResponse);
 419  0
                         return null;
 420  
                 }
 421  
         }
 422  
 
 423  
         /**
 424  
          * This function has to be used after a DataParsingException. It means that
 425  
          * the response is not the expected, but it still could be an API response
 426  
          * (error).
 427  
          * 
 428  
          * @param rawData
 429  
          *            Obtained data.
 430  
          * @throws APIWrapperException
 431  
          *             Always throws it, it depends on the content of the given
 432  
          *             <code>rawData</code> the exact {@link Error}.
 433  
          */
 434  
         private void tryToParseError(String rawData) throws APIWrapperException {
 435  1
                 logManager.logError(this,
 436  
                                 "There was an error, trying checking if it was an API error");
 437  
                 try {
 438  1
                         GeoReportV2Error error = dataParser.parseGeoReportV2Errors(rawData);
 439  1
                         throw new APIWrapperException("GeoReport_v2 error",
 440  
                                         Error.GEO_REPORT_V2, error);
 441  0
                 } catch (DataParsingException ex) {
 442  0
                         logManager.logError(this,
 443  
                                         "The error couldn't be parsed (it is not an API error).");
 444  0
                         throw new APIWrapperException(ex.getMessage(), Error.DATA_PARSING,
 445  
                                         null);
 446  
                 }
 447  
         }
 448  
 
 449  
         /**
 450  
          * Tries to perform an HTTP GET operation and returns the result.
 451  
          * 
 452  
          * @param url
 453  
          *            Target.
 454  
          * @return Server response.
 455  
          * @throws APIWrapperException
 456  
          *             If there was any problem with the request.
 457  
          */
 458  
         protected String networkGet(URL url) throws APIWrapperException {
 459  11
                 logManager.logInfo(this, "HTTP GET " + url.toString());
 460  
                 try {
 461  11
                         String response = networkManager.doGet(url);
 462  11
                         String cutResponse = response.length() > 50 ? response.substring(0,
 463  
                                         50) + "..." : response;
 464  11
                         logManager.logInfo(this,
 465  
                                         "HTTP GET response (50 or less first characters)"
 466  
                                                         + cutResponse);
 467  11
                         return response;
 468  0
                 } catch (IOException e) {
 469  0
                         logManager.logError(this, "HTTP GET error: " + e.getMessage());
 470  0
                         throw new APIWrapperException(e.getMessage(),
 471  
                                         Error.NETWORK_MANAGER, null);
 472  
                 }
 473  
         }
 474  
 
 475  
         /**
 476  
          * Tries to perform an HTTP POST operation and returns the result.
 477  
          * 
 478  
          * @param url
 479  
          *            Target.
 480  
          * @param parameters
 481  
          *            Parameters of the request.
 482  
          * @return Server response.
 483  
          * @throws APIWrapperException
 484  
          *             If there was any problem with the request.
 485  
          */
 486  
         protected String networkPost(URL url, Map<String, String> parameters)
 487  
                         throws APIWrapperException {
 488  5
                 logManager.logInfo(this, "HTTP POST " + url.toString());
 489  
                 try {
 490  5
                         String response = networkManager.doPost(url, parameters);
 491  4
                         String cutResponse = response.length() > 50 ? response.substring(0,
 492  
                                         50) + "..." : response;
 493  4
                         logManager.logInfo(this,
 494  
                                         "HTTP POST response (50 or less first characters)"
 495  
                                                         + cutResponse);
 496  4
                         return response;
 497  1
                 } catch (IOException e) {
 498  1
                         logManager.logError(this, "HTTP POST error: " + e.getMessage());
 499  1
                         throw new APIWrapperException(e.getMessage(),
 500  
                                         Error.NETWORK_MANAGER, null);
 501  
                 }
 502  
         }
 503  
 
 504  
         public String toString() {
 505  0
                 return this.getWrapperInfo();
 506  
         }
 507  
 }