Coverage Report - org.codeforamerica.open311.internals.parsing.JSONParser
 
Classes in this File Line Coverage Branch Coverage Complexity
JSONParser
91%
120/131
69%
18/26
3.625
 
 1  
 package org.codeforamerica.open311.internals.parsing;
 2  
 
 3  
 import java.net.MalformedURLException;
 4  
 import java.net.URL;
 5  
 import java.util.Date;
 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.AttributeInfo;
 12  
 import org.codeforamerica.open311.facade.data.AttributeInfo.Datatype;
 13  
 import org.codeforamerica.open311.facade.data.POSTServiceRequestResponse;
 14  
 import org.codeforamerica.open311.facade.data.Service;
 15  
 import org.codeforamerica.open311.facade.data.ServiceDefinition;
 16  
 import org.codeforamerica.open311.facade.data.ServiceDiscoveryInfo;
 17  
 import org.codeforamerica.open311.facade.data.ServiceRequest;
 18  
 import org.codeforamerica.open311.facade.data.ServiceRequest.Status;
 19  
 import org.codeforamerica.open311.facade.data.ServiceRequestIdResponse;
 20  
 import org.codeforamerica.open311.facade.exceptions.DataParsingException;
 21  
 import org.codeforamerica.open311.facade.exceptions.GeoReportV2Error;
 22  
 import org.json.JSONArray;
 23  
 import org.json.JSONException;
 24  
 import org.json.JSONObject;
 25  
 
 26  
 /**
 27  
  * Implementation of a {@link DataParser} which takes JSON data as input.
 28  
  * 
 29  
  * @author Santiago MunĂ­n <santimunin@gmail.com>
 30  
  * 
 31  
  */
 32  1
 public class JSONParser extends AbstractParser {
 33  
         private static final String NULL_STRING_JSON = "null";
 34  1
         private DateParser dateParser = new DateParser();
 35  
 
 36  
         @Override
 37  
         public List<Service> parseServiceList(String rawData)
 38  
                         throws DataParsingException {
 39  2
                 List<Service> result = new LinkedList<Service>();
 40  
                 try {
 41  2
                         JSONArray serviceList = new JSONArray(rawData);
 42  3
                         for (int i = 0; i < serviceList.length(); i++) {
 43  2
                                 JSONObject service = serviceList.getJSONObject(i);
 44  2
                                 result.add(getService(service));
 45  
                         }
 46  1
                 } catch (Exception e) {
 47  1
                         throw new DataParsingException(e.getMessage());
 48  1
                 }
 49  1
                 return result;
 50  
         }
 51  
 
 52  
         /**
 53  
          * Builds a {@link Service} object from a {@link JSONObject}.
 54  
          * 
 55  
          * @param service
 56  
          *            JSONObject which represents a service.
 57  
          * @return An object with its state given by the JSONObject
 58  
          * @throws JSONException
 59  
          *             If the given service isn't correct.
 60  
          * @throws DataParsingException
 61  
          *             If the received parameters are not valid.
 62  
          */
 63  
         private Service getService(JSONObject service) throws JSONException,
 64  
                         DataParsingException {
 65  2
                 String code = getString(service, SERVICE_CODE_TAG);
 66  2
                 String name = getString(service, SERVICE_NAME_TAG);
 67  2
                 String description = getString(service, DESCRIPTION_TAG);
 68  2
                 Boolean metadata = getBoolean(service, METADATA_TAG);
 69  2
                 String group = getString(service, SERVICE_GROUP_TAG);
 70  2
                 String[] keywords = getKeywords(getString(service, KEYWORDS_TAG));
 71  2
                 Service.Type type = Service.Type.getFromString(getString(service,
 72  
                                 TYPE_TAG));
 73  2
                 checkParameters(code);
 74  2
                 return new Service(code, name, description, metadata, type, keywords,
 75  
                                 group);
 76  
         }
 77  
 
 78  
         @Override
 79  
         public ServiceDefinition parseServiceDefinition(String rawData)
 80  
                         throws DataParsingException {
 81  
                 try {
 82  2
                         JSONObject serviceDefinition = new JSONObject(rawData);
 83  1
                         if (serviceDefinition.has(SERVICE_DEFINITION_TAG)) {
 84  0
                                 serviceDefinition = serviceDefinition
 85  
                                                 .getJSONObject(SERVICE_DEFINITION_TAG);
 86  
                         }
 87  1
                         String serviceCode = getString(serviceDefinition, SERVICE_CODE_TAG);
 88  1
                         JSONArray attributesArray = serviceDefinition
 89  
                                         .getJSONArray(ATTRIBUTES_TAG);
 90  1
                         checkParameters(serviceCode);
 91  1
                         return new ServiceDefinition(serviceCode,
 92  
                                         parseAttributeList(attributesArray));
 93  1
                 } catch (JSONException e) {
 94  1
                         throw new DataParsingException(e.getMessage());
 95  
                 }
 96  
         }
 97  
 
 98  
         /**
 99  
          * Parses the list of attributes attached to a service definition.
 100  
          * 
 101  
          * @param attributesArray
 102  
          *            The JSONArray object.
 103  
          * @return List of attributes.
 104  
          * @throws JSONException
 105  
          *             If the given object is not correct.
 106  
          * @throws DataParsingException
 107  
          *             If the code parameter is missing.
 108  
          */
 109  
         private List<AttributeInfo> parseAttributeList(JSONArray attributesArray)
 110  
                         throws JSONException, DataParsingException {
 111  1
                 List<AttributeInfo> attributes = new LinkedList<AttributeInfo>();
 112  2
                 for (int i = 0; i < attributesArray.length(); i++) {
 113  1
                         JSONObject attribute = attributesArray.getJSONObject(i);
 114  1
                         Boolean variable = getBoolean(attribute, VARIABLE_TAG);
 115  1
                         String code = getString(attribute, CODE_TAG);
 116  1
                         Datatype datatype = Datatype.getFromString(getString(attribute,
 117  
                                         DATATYPE_TAG));
 118  1
                         Boolean required = getBoolean(attribute, REQUIRED_TAG);
 119  1
                         String datatypeDescription = getString(attribute,
 120  
                                         DATATYPE_DESCRIPTION_TAG);
 121  1
                         Integer order = getInteger(attribute, ORDER_TAG);
 122  1
                         String description = getString(attribute, DESCRIPTION_TAG);
 123  1
                         Map<String, String> values = null;
 124  1
                         if (attribute.has(VALUES_TAG)) {
 125  1
                                 JSONArray valuesArray = attribute.getJSONArray(VALUES_TAG);
 126  1
                                 values = new HashMap<String, String>();
 127  3
                                 for (int j = 0; j < valuesArray.length(); j++) {
 128  2
                                         JSONObject valueObject = valuesArray.getJSONObject(j);
 129  2
                                         String key = getString(valueObject, KEY_TAG);
 130  2
                                         String name = getString(valueObject, NAME_TAG);
 131  2
                                         values.put(key, name);
 132  
                                 }
 133  
                         }
 134  1
                         checkParameters(code);
 135  1
                         attributes.add(new AttributeInfo(variable, code, datatype,
 136  
                                         required, datatypeDescription, order, description, values));
 137  
                 }
 138  1
                 return attributes;
 139  
         }
 140  
 
 141  
         @Override
 142  
         public ServiceRequestIdResponse parseServiceRequestIdFromAToken(
 143  
                         String rawData) throws DataParsingException {
 144  
                 try {
 145  2
                         JSONArray responsesArray = new JSONArray(rawData);
 146  1
                         if (responsesArray.length() == 0) {
 147  0
                                 return null;
 148  
                         }
 149  1
                         JSONObject response = responsesArray.getJSONObject(0);
 150  1
                         String token = getString(response, TOKEN_TAG);
 151  1
                         String serviceRequestId = getString(response,
 152  
                                         SERVICE_REQUEST_ID_TAG);
 153  1
                         checkParameters(token, serviceRequestId);
 154  1
                         return new ServiceRequestIdResponse(serviceRequestId, token);
 155  1
                 } catch (Exception e) {
 156  1
                         throw new DataParsingException(e.getMessage());
 157  
                 }
 158  
         }
 159  
 
 160  
         @Override
 161  
         public List<ServiceRequest> parseServiceRequests(String rawData)
 162  
                         throws DataParsingException {
 163  2
                 List<ServiceRequest> result = new LinkedList<ServiceRequest>();
 164  
                 try {
 165  
                         JSONArray serviceRequestsArray;
 166  2
                         if (rawData.contains("{\"service_requests\"")) {
 167  0
                                 serviceRequestsArray = new JSONObject(rawData)
 168  
                                                 .getJSONArray(SERVICE_REQUESTS_TAG);
 169  
                         } else {
 170  2
                                 serviceRequestsArray = new JSONArray(rawData);
 171  
                         }
 172  3
                         for (int i = 0; i < serviceRequestsArray.length(); i++) {
 173  2
                                 JSONObject serviceRequest = serviceRequestsArray
 174  
                                                 .getJSONObject(i);
 175  2
                                 result.add(parseServiceRequest(serviceRequest));
 176  
                         }
 177  1
                         return result;
 178  1
                 } catch (Exception e) {
 179  1
                         throw new DataParsingException(e.getMessage());
 180  
                 }
 181  
         }
 182  
 
 183  
         /**
 184  
          * Builds a service request object from a JSONObject.
 185  
          * 
 186  
          * @param serviceRequest
 187  
          *            JSONObject representing the service definition data.
 188  
          * @return A service request object.
 189  
          * @throws MalformedURLException
 190  
          *             If the media URL isn't correct.
 191  
          * @throws DataParsingException
 192  
          *             If the received parameters are not valid.
 193  
          * @throws JSONException
 194  
          *             If there was any problem parsing any parameter.
 195  
          */
 196  
         private ServiceRequest parseServiceRequest(JSONObject serviceRequest)
 197  
                         throws MalformedURLException, DataParsingException, JSONException {
 198  2
                 String serviceRequestId = getString(serviceRequest,
 199  
                                 SERVICE_REQUEST_ID_TAG);
 200  2
                 Status status = Status.getFromString(getString(serviceRequest,
 201  
                                 STATUS_TAG));
 202  2
                 String statusNotes = getString(serviceRequest, STATUS_NOTES_TAG);
 203  2
                 String serviceName = getString(serviceRequest, SERVICE_NAME_TAG);
 204  2
                 String serviceCode = getString(serviceRequest, SERVICE_CODE_TAG);
 205  2
                 String description = getString(serviceRequest, DESCRIPTION_TAG);
 206  2
                 String agencyResponsible = getString(serviceRequest,
 207  
                                 AGENCY_RESPONSIBLE_TAG);
 208  2
                 String serviceNotice = getString(serviceRequest, SERVICE_NOTICE_TAG);
 209  2
                 Date requestedDatetime = dateParser.parseDate(getString(serviceRequest,
 210  
                                 REQUESTED_DATETIME_TAG));
 211  2
                 Date updatedDatetime = dateParser.parseDate(getString(serviceRequest,
 212  
                                 UPDATED_DATETIME_TAG));
 213  2
                 Date expectedDatetime = dateParser.parseDate(getString(serviceRequest,
 214  
                                 EXPECTED_DATETIME_TAG));
 215  2
                 String address = getString(serviceRequest, ADDRESS_TAG);
 216  2
                 Long addressId = getLong(serviceRequest, ADDRESS_ID_TAG);
 217  2
                 Integer zipCode = getInteger(serviceRequest, ZIPCODE_TAG);
 218  2
                 Float latitude = getFloat(serviceRequest, LATITUDE_TAG);
 219  2
                 Float longitude = getFloat(serviceRequest, LONGITUDE_TAG);
 220  2
                 String rawMediaUrl = getString(serviceRequest, MEDIA_URL_TAG).trim();
 221  2
                 URL mediaUrl = buildUrl(rawMediaUrl);
 222  2
                 checkParameters(serviceCode);
 223  2
                 return new ServiceRequest(serviceRequestId, status, statusNotes,
 224  
                                 serviceName, serviceCode, description, agencyResponsible,
 225  
                                 serviceNotice, requestedDatetime, updatedDatetime,
 226  
                                 expectedDatetime, address, addressId, zipCode, latitude,
 227  
                                 longitude, mediaUrl);
 228  
         }
 229  
 
 230  
         @Override
 231  
         public POSTServiceRequestResponse parsePostServiceRequestResponse(
 232  
                         String rawData) throws DataParsingException {
 233  
                 JSONArray postServiceRequestResponseArray;
 234  
                 try {
 235  2
                         postServiceRequestResponseArray = new JSONArray(rawData);
 236  1
                         if (postServiceRequestResponseArray.length() > 0) {
 237  1
                                 JSONObject postServiceRequestResponse = postServiceRequestResponseArray
 238  
                                                 .getJSONObject(0);
 239  1
                                 String token = getString(postServiceRequestResponse, TOKEN_TAG);
 240  1
                                 String serviceRequestId = getString(postServiceRequestResponse,
 241  
                                                 SERVICE_REQUEST_ID_TAG);
 242  1
                                 String serviceNotice = getString(postServiceRequestResponse,
 243  
                                                 SERVICE_NOTICE_TAG);
 244  1
                                 String accountId = getString(postServiceRequestResponse,
 245  
                                                 ACCOUNT_ID_TAG);
 246  1
                                 checkParameters(serviceRequestId, token);
 247  1
                                 return new POSTServiceRequestResponse(serviceRequestId, token,
 248  
                                                 serviceNotice, accountId);
 249  
                         }
 250  0
                         throw new DataParsingException(
 251  
                                         "The obtained response couldn't be parsed, it may be an error.");
 252  1
                 } catch (JSONException e) {
 253  1
                         throw new DataParsingException(e.getMessage());
 254  
                 }
 255  
         }
 256  
 
 257  
         @Override
 258  
         public GeoReportV2Error parseGeoReportV2Errors(String rawData)
 259  
                         throws DataParsingException {
 260  
                 try {
 261  2
                         JSONArray errorsArray = new JSONArray(rawData);
 262  1
                         if (errorsArray.length() > 0) {
 263  1
                                 JSONObject errorObject = errorsArray.getJSONObject(0);
 264  1
                                 String code = getString(errorObject, CODE_TAG);
 265  1
                                 String description = getString(errorObject, DESCRIPTION_TAG);
 266  1
                                 checkParameters(code, description);
 267  1
                                 return new GeoReportV2Error(code, description);
 268  
                         } else {
 269  0
                                 throw new DataParsingException(
 270  
                                                 "The obtained response is not an error object");
 271  
                         }
 272  1
                 } catch (JSONException e) {
 273  1
                         throw new DataParsingException(e.getMessage());
 274  
                 }
 275  
         }
 276  
 
 277  
         @Override
 278  
         public ServiceDiscoveryInfo parseServiceDiscovery(String rawData)
 279  
                         throws DataParsingException {
 280  1
                 throw new UnsupportedOperationException(
 281  
                                 "This operation is expected to be done by an XML parser.");
 282  
         }
 283  
 
 284  
         /**
 285  
          * Searches the value of a given tag in a {@link JSONObject}.
 286  
          * 
 287  
          * @param object
 288  
          *            Object to inspect.
 289  
          * @param tag
 290  
          *            Tag to search.
 291  
          * @return The string value of the tag, <code>null</code> if it wasn't
 292  
          *         found.
 293  
          */
 294  
         private String getString(JSONObject object, String tag) {
 295  
                 String result;
 296  
                 try {
 297  55
                         result = object.getString(tag);
 298  54
                         return result.equals(NULL_STRING_JSON) ? "" : result;
 299  1
                 } catch (JSONException e) {
 300  1
                         return "";
 301  
                 }
 302  
         }
 303  
 
 304  
         /**
 305  
          * Searches the value of a given tag in a {@link JSONObject}.
 306  
          * 
 307  
          * @param object
 308  
          *            Object to inspect.
 309  
          * @param tag
 310  
          *            Tag to search.
 311  
          * @return The integer value of the tag, <code>null</code> if it wasn't
 312  
          *         found.
 313  
          * @throws JSONException
 314  
          *             If there was any problem with the parsing.
 315  
          */
 316  
         private Integer getInteger(JSONObject object, String tag)
 317  
                         throws JSONException {
 318  3
                 Long result = getLong(object, tag);
 319  3
                 return result != null ? result.intValue() : null;
 320  
 
 321  
         }
 322  
 
 323  
         /**
 324  
          * Searches the value of a given tag in a {@link JSONObject}.
 325  
          * 
 326  
          * @param object
 327  
          *            Object to inspect.
 328  
          * @param tag
 329  
          *            Tag to search.
 330  
          * @return The long value of the tag, <code>null</code> if it wasn't found.
 331  
          * @throws JSONException
 332  
          *             If there was any problem with the parsing.
 333  
          */
 334  
         private Long getLong(JSONObject object, String tag) throws JSONException {
 335  
                 try {
 336  5
                         return object.getLong(tag);
 337  0
                 } catch (Exception e) {
 338  0
                         return null;
 339  
                 }
 340  
         }
 341  
 
 342  
         /**
 343  
          * Searches the value of a given tag in a {@link JSONObject}.
 344  
          * 
 345  
          * @param object
 346  
          *            Object to inspect.
 347  
          * @param tag
 348  
          *            Tag to search.
 349  
          * @return The float value of the tag, <code>null</code> if it wasn't found.
 350  
          * @throws JSONException
 351  
          *             If there was any problem with the parsing.
 352  
          */
 353  
         private Float getFloat(JSONObject object, String tag) throws JSONException {
 354  4
                 Double result = getDouble(object, tag);
 355  4
                 return result != null ? result.floatValue() : null;
 356  
         }
 357  
 
 358  
         /**
 359  
          * Searches the value of a given tag in a {@link JSONObject}.
 360  
          * 
 361  
          * @param object
 362  
          *            Object to inspect.
 363  
          * @param tag
 364  
          *            Tag to search.
 365  
          * @return The double value of the tag, <code>null</code> if it wasn't
 366  
          *         found.
 367  
          * @throws JSONException
 368  
          *             If there was any problem with the parsing.
 369  
          */
 370  
         private Double getDouble(JSONObject object, String tag)
 371  
                         throws JSONException {
 372  
                 try {
 373  4
                         return object.getDouble(tag);
 374  0
                 } catch (Exception e) {
 375  0
                         return null;
 376  
                 }
 377  
         }
 378  
 
 379  
         /**
 380  
          * Parses a boolean.
 381  
          * 
 382  
          * @param object
 383  
          *            Object to inspect.
 384  
          * @param tag
 385  
          *            Tag to search.
 386  
          * @return <code>null</code> if it wasn't found.
 387  
          * @throws JSONException
 388  
          *             If there was any problem.
 389  
          */
 390  
         private Boolean getBoolean(JSONObject object, String tag)
 391  
                         throws JSONException {
 392  
                 try {
 393  4
                         return new Boolean(object.getBoolean(tag));
 394  0
                 } catch (Exception e) {
 395  0
                         return Boolean.FALSE;
 396  
                 }
 397  
         }
 398  
 }