Coverage Report - org.codeforamerica.open311.facade.APIWrapperFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
APIWrapperFactory
57%
58/100
43%
14/32
2.556
 
 1  
 package org.codeforamerica.open311.facade;
 2  
 
 3  
 import java.io.IOException;
 4  
 import java.net.MalformedURLException;
 5  
 import java.net.URL;
 6  
 
 7  
 import org.codeforamerica.open311.facade.data.Endpoint;
 8  
 import org.codeforamerica.open311.facade.data.ServiceDiscoveryInfo;
 9  
 import org.codeforamerica.open311.facade.exceptions.APIWrapperException;
 10  
 import org.codeforamerica.open311.facade.exceptions.APIWrapperException.Error;
 11  
 import org.codeforamerica.open311.facade.exceptions.DataParsingException;
 12  
 import org.codeforamerica.open311.internals.caching.Cache;
 13  
 import org.codeforamerica.open311.internals.logging.LogManager;
 14  
 import org.codeforamerica.open311.internals.network.HTTPNetworkManager;
 15  
 import org.codeforamerica.open311.internals.network.NetworkManager;
 16  
 import org.codeforamerica.open311.internals.parsing.DataParser;
 17  
 import org.codeforamerica.open311.internals.parsing.DataParserFactory;
 18  
 import org.codeforamerica.open311.internals.platform.PlatformManager;
 19  
 
 20  
 /**
 21  
  * Builds {@link APIWrapper} instances from different aspects specified by the
 22  
  * user. To set the optional parameters, use the setter methods and, then, call
 23  
  * {@link #build()}.
 24  
  * 
 25  
  * @author Santiago Munin <santimunin@gmail.com>
 26  
  * 
 27  
  */
 28  
 public class APIWrapperFactory {
 29  
         ;
 30  
         /**
 31  
          * Used for the first way of building the {@link APIWrapper} (
 32  
          * {@link #APIWrapperFactory(String, String, Format)}).
 33  
          * 
 34  
          */
 35  7
         private String endpointUrl = null;
 36  
         /**
 37  
          * Used for the first way of building the {@link APIWrapper} (
 38  
          * {@link #APIWrapperFactory(String, String, Format)}).
 39  
          * 
 40  
          */
 41  7
         private String jurisdictionId = null;
 42  
         /**
 43  
          * Used for the second way of building the {@link APIWrapper} (
 44  
          * {@link APIWrapperFactory#APIWrapperFactory(City, EndpointType)}).
 45  
          * 
 46  
          */
 47  7
         private City city = null;
 48  
         /**
 49  
          * Used for the second way of building the {@link APIWrapper} (
 50  
          * {@link APIWrapperFactory#APIWrapperFactory(City, EndpointType)}).
 51  
          */
 52  7
         private EndpointType endpointType = null;
 53  
         /**
 54  
          * Used for the second way of building the {@link APIWrapper} (
 55  
          * {@link APIWrapperFactory#APIWrapperFactory(City, EndpointType)}).
 56  
          */
 57  7
         private Format format = null;
 58  7
         private String apiKey = "";
 59  7
         private NetworkManager networkManager = new HTTPNetworkManager(Format.XML);
 60  
         /**
 61  
          * Suitable cache (depends on the execution environment).
 62  
          */
 63  7
         private Cache cache = PlatformManager.getInstance().buildCache();
 64  
         /**
 65  
          * <code>true</code> if the built instance should be logged.
 66  
          */
 67  7
         private boolean log = false;
 68  
         /**
 69  
          * Reference to the {@link LogManager}.
 70  
          */
 71  7
         private LogManager logManager = LogManager.getInstance();
 72  
 
 73  
         /**
 74  
          * Builds an instance from the endpoint url.
 75  
          * 
 76  
          * @param endpointUrl
 77  
          *            Url of the endpoint.
 78  
          */
 79  1
         public APIWrapperFactory(String endpointUrl) {
 80  1
                 this.endpointUrl = endpointUrl;
 81  1
                 this.jurisdictionId = "";
 82  1
                 this.format = Format.XML;
 83  1
         }
 84  
 
 85  
         /**
 86  
          * Builds an instance from the endpoint url and the jurisdiction_id.
 87  
          * 
 88  
          * @param endpointUrl
 89  
          *            Url of the endpoint.
 90  
          * @param jurisdictionId
 91  
          *            Desired jurisdiction_id (can be <code>null</code>).
 92  
          */
 93  
         public APIWrapperFactory(String endpointUrl, String jurisdictionId) {
 94  1
                 this(endpointUrl);
 95  1
                 this.jurisdictionId = jurisdictionId == null ? "" : jurisdictionId;
 96  1
         }
 97  
 
 98  
         /**
 99  
          * Builds an instance from the endpoint url and the data interchange format.
 100  
          * 
 101  
          * @param endpointUrl
 102  
          *            Url of the endpoint.
 103  
          * @param format
 104  
          *            Data format. It is your responsibility to check if the given
 105  
          *            format is allowed.
 106  
          */
 107  
         public APIWrapperFactory(String endpointUrl, Format format) {
 108  0
                 this(endpointUrl);
 109  0
                 this.format = format;
 110  0
         }
 111  
 
 112  
         /**
 113  
          * Builds an instance from the endpoint url and the jurisdiction_id.
 114  
          * 
 115  
          * @param endpointUrl
 116  
          *            Url of the endpoint.
 117  
          * @param jurisdictionId
 118  
          *            Desired jurisdiction_id (can be <code>null</code>).
 119  
          * @param format
 120  
          *            Data format. It is your responsibility to check if the given
 121  
          *            format is allowed.
 122  
          */
 123  
         public APIWrapperFactory(String endpointUrl, String jurisdictionId,
 124  
                         Format format) {
 125  0
                 this(endpointUrl, jurisdictionId);
 126  0
                 this.format = format;
 127  0
         }
 128  
 
 129  
         /**
 130  
          * Builds an instance from the desired city.
 131  
          * 
 132  
          * The type of the endpoint will be {@link EndpointType#PRODUCTION}) and the
 133  
          * format will be {@link Format#XML}.
 134  
          * 
 135  
          * @param city
 136  
          *            Desired city.
 137  
          */
 138  6
         public APIWrapperFactory(City city) {
 139  6
                 this.city = city;
 140  6
                 this.endpointType = EndpointType.PRODUCTION;
 141  6
                 this.format = Format.XML;
 142  6
         }
 143  
 
 144  
         /**
 145  
          * Builds an instance from the desired city and endpoint type. The format
 146  
          * will be {@link Format#XML}.
 147  
          * 
 148  
          * @param city
 149  
          *            Desired city.
 150  
          * @param endpointType
 151  
          *            Desired endpoint type.
 152  
          */
 153  
         public APIWrapperFactory(City city, EndpointType endpointType) {
 154  1
                 this(city);
 155  1
                 this.endpointType = endpointType;
 156  1
         }
 157  
 
 158  
         /**
 159  
          * Selects the desired data exchange format.
 160  
          * 
 161  
          * @param format
 162  
          *            Desired format.
 163  
          * @return The same instance with the new selected format.
 164  
          */
 165  
         public APIWrapperFactory setFormat(Format format) {
 166  0
                 this.format = format;
 167  0
                 return this;
 168  
         }
 169  
 
 170  
         /**
 171  
          * Sets the api key. <code>""</code> by default.
 172  
          * 
 173  
          * @param apiKey
 174  
          *            Api key for the endpoint.
 175  
          * @return The same instance with the new specified api key.
 176  
          */
 177  
         public APIWrapperFactory setApiKey(String apiKey) {
 178  0
                 this.apiKey = apiKey;
 179  0
                 return this;
 180  
         }
 181  
 
 182  
         /**
 183  
          * Sets a custom {@link NetworkManager}, useful if you need to use mocks or
 184  
          * a platform-dependent network client which you can build implementing the
 185  
          * {@link NetworkManager} interface.
 186  
          * 
 187  
          * @param networkManager
 188  
          *            A implementation of the {@link NetworkManager} interface.
 189  
          * @return The same instance with the new specified {@link NetworkManager}.
 190  
          */
 191  
         public APIWrapperFactory setNetworkManager(NetworkManager networkManager) {
 192  7
                 this.networkManager = networkManager;
 193  7
                 this.networkManager.setFormat(Format.XML);
 194  7
                 return this;
 195  
         }
 196  
 
 197  
         /**
 198  
          * Sets a desired cache system.
 199  
          * 
 200  
          * @param cache
 201  
          *            Implementation of the {@link Cache} interface.
 202  
          * @return The same instance with the new specified {@link Cache}.
 203  
          */
 204  
         public APIWrapperFactory setCache(Cache cache) {
 205  5
                 if (cache != null) {
 206  5
                         this.cache = cache;
 207  
                 }
 208  5
                 return this;
 209  
         }
 210  
 
 211  
         /**
 212  
          * The built instance will be logged.
 213  
          * 
 214  
          * @return The same instance.
 215  
          */
 216  
         public APIWrapperFactory withLogs() {
 217  0
                 this.log = true;
 218  0
                 LogManager.getInstance().activate(this);
 219  0
                 return this;
 220  
         }
 221  
 
 222  
         /**
 223  
          * Builds an {@link APIWrapper}. <b>WARNING</b>: This operation could
 224  
          * require some time to be done (it could involve network operations).
 225  
          * 
 226  
          * @return An instance built from the given parameters to this object.
 227  
          * @throws APIWrapperException
 228  
          *             If there was any problem.
 229  
          */
 230  
         public APIWrapper build() throws APIWrapperException {
 231  7
                 if (city != null) {
 232  6
                         return buildWrapperFromCity(city, endpointType, apiKey,
 233  
                                         networkManager);
 234  
                 }
 235  1
                 if (endpointUrl != null) {
 236  1
                         return buildWrapperFromEndpointUrl(endpointUrl, jurisdictionId,
 237  
                                         format);
 238  
                 }
 239  0
                 return null;
 240  
         }
 241  
 
 242  
         /**
 243  
          * Builds an {@link APIWrapper} ignoring the {@link APIWrapperFactory#city}
 244  
          * and {@link APIWrapperFactory#endpointType} parameters.
 245  
          * 
 246  
          * @param endpointUrl
 247  
          *            Endpoint's url.
 248  
          * @param jurisdictionId
 249  
          *            Jurisdiction id of the city. <b>WARNING</b>: This operation
 250  
          *            does not check if the given format is allowed by the endpoint.
 251  
          * @param format
 252  
          *            Desired format to use.
 253  
          * @return An instance built from the given parameters to this object.
 254  
          */
 255  
         private APIWrapper buildWrapperFromEndpointUrl(String endpointUrl,
 256  
                         String jurisdictionId, Format format) {
 257  1
                 logManager.logInfo(this,
 258  
                                 "Building a wrapper from the given endpoint url: "
 259  
                                                 + endpointUrl + ", jurisdiction_id: \""
 260  
                                                 + jurisdictionId + "\", format: " + format);
 261  1
                 return activateLoginIfRequested(createMostSuitableWrapper(endpointUrl,
 262  
                                 format, EndpointType.UNKNOWN, DataParserFactory.getInstance()
 263  
                                                 .buildDataParser(format), networkManager, cache,
 264  
                                 jurisdictionId, apiKey));
 265  
         }
 266  
 
 267  
         /**
 268  
          * Builds an {@link APIWrapper} ignoring the {@link #endpointUrl} and
 269  
          * {@link #jurisdictionId} parameters. <b>NOTE</b>: This operation could
 270  
          * require some time to be done (it involves network operations).
 271  
          * 
 272  
          * @param city
 273  
          *            Desired city to work with.
 274  
          * @param endpointType
 275  
          *            Type of the desired endpoint (useful if you need, for example,
 276  
          *            a dev one to test your application).
 277  
          * @param apiKey
 278  
          *            Api key which allows to perform some operations. If you need
 279  
          *            one, go to the <a
 280  
          *            href="http://wiki.open311.org/GeoReport_v2/Servers">list of
 281  
          *            servers</a> and request it.
 282  
          * @param networkManager
 283  
          *            A {@link NetworkManager} implementation.
 284  
          * @return An instance suited to the given parameters.
 285  
          * 
 286  
          * @throws APIWrapperException
 287  
          *             If there was any problem.
 288  
          */
 289  
         private APIWrapper buildWrapperFromCity(City city,
 290  
                         EndpointType endpointType, String apiKey,
 291  
                         NetworkManager networkManager) throws APIWrapperException {
 292  
                 try {
 293  6
                         logManager.logInfo(this, "Getting the service discovery file.");
 294  6
                         DataParser dataParser = DataParserFactory.getInstance()
 295  
                                         .buildDataParser(Format.XML);
 296  6
                         ServiceDiscoveryInfo serviceDiscoveryInfo = cache
 297  
                                         .retrieveCachedServiceDiscoveryInfo(city);
 298  6
                         if (serviceDiscoveryInfo == null) {
 299  5
                                 logManager
 300  
                                                 .logInfo(this,
 301  
                                                                 "Service discovery file was not cached, downloading it.");
 302  5
                                 serviceDiscoveryInfo = dataParser
 303  
                                                 .parseServiceDiscovery(networkManager.doGet(new URL(
 304  
                                                                 city.getDiscoveryUrl())));
 305  5
                                 cache.saveServiceDiscovery(city, serviceDiscoveryInfo);
 306  
                         }
 307  6
                         Endpoint endpoint = serviceDiscoveryInfo
 308  
                                         .getMoreSuitableEndpoint(endpointType);
 309  6
                         if (endpoint == null) {
 310  0
                                 logManager.logError(this, "No suitable endpoint was found.");
 311  0
                                 throw new APIWrapperException(
 312  
                                                 "No suitable endpoint was found.",
 313  
                                                 Error.NOT_SUITABLE_ENDPOINT_FOUND, null);
 314  
                         }
 315  6
                         logManager.logInfo(this, "Selected the most suitable endpoint.");
 316  6
                         Format format = selectFormat(this.format, endpoint);
 317  6
                         if (format != Format.XML) {
 318  0
                                 logManager
 319  
                                                 .logInfo(
 320  
                                                                 this,
 321  
                                                                 "The selected endpoint allows to use the "
 322  
                                                                                 + format
 323  
                                                                                 + " format which is a best option, selecting it.");
 324  0
                                 networkManager.setFormat(format);
 325  0
                                 dataParser = DataParserFactory.getInstance().buildDataParser(
 326  
                                                 format);
 327  
                         }
 328  
 
 329  6
                         return activateLoginIfRequested(createMostSuitableWrapper(
 330  
                                         endpoint.getUrl(), format, endpointType, dataParser,
 331  
                                         networkManager, cache, city.getJurisdictionId(), apiKey));
 332  0
                 } catch (MalformedURLException e) {
 333  0
                         logManager.logError(this, "Problem building the url");
 334  0
                         throw new APIWrapperException(e.getMessage(), Error.URL_BUILDER,
 335  
                                         null);
 336  0
                 } catch (DataParsingException e) {
 337  0
                         logManager.logError(this,
 338  
                                         "Problem parsing reveived data: " + e.getMessage());
 339  0
                         throw new APIWrapperException(e.getMessage(), Error.DATA_PARSING,
 340  
                                         null);
 341  0
                 } catch (IOException e) {
 342  0
                         logManager.logError(this,
 343  
                                         "Problem with the network request: " + e.getMessage());
 344  0
                         throw new APIWrapperException(e.getMessage(),
 345  
                                         Error.NETWORK_MANAGER, null);
 346  
                 }
 347  
         }
 348  
 
 349  
         /**
 350  
          * Selects the given {@link Format} if it is allowed by the {@link Endpoint}
 351  
          * .
 352  
          * 
 353  
          * @param format
 354  
          *            Desired format.
 355  
          * @param endpoint
 356  
          *            Selected endpoint.
 357  
          * @return The given format if it is allowed by the endpoint, the more
 358  
          *         suitable otherwise.
 359  
          */
 360  
         private Format selectFormat(Format format, Endpoint endpoint) {
 361  6
                 if (format != null && endpoint.isCompatibleWithFormat(format)) {
 362  6
                         return format;
 363  
                 } else {
 364  0
                         return endpoint.getBestFormat();
 365  
                 }
 366  
         }
 367  
 
 368  
         /**
 369  
          * Activates a wrapper in the {@link LogManager} if it was requested.
 370  
          * 
 371  
          * @param wrapperInstance
 372  
          *            Instance of the wrapper which could be logged.
 373  
          * @return the given instance.
 374  
          */
 375  
         private APIWrapper activateLoginIfRequested(APIWrapper wrapperInstance) {
 376  7
                 if (log) {
 377  0
                         LogManager.getInstance().activate(wrapperInstance);
 378  
                 }
 379  7
                 return wrapperInstance;
 380  
         }
 381  
 
 382  
         public String toString() {
 383  0
                 StringBuilder builder = new StringBuilder("APIWrapperFactory");
 384  0
                 if (city != null) {
 385  0
                         builder.append(" - building from a City object " + "type: "
 386  
                                         + endpointType);
 387  
                 }
 388  0
                 if (endpointUrl != null) {
 389  0
                         builder.append(" - building from an endpoint url");
 390  
                 }
 391  0
                 return builder.toString();
 392  
         }
 393  
 
 394  
         private APIWrapper createMostSuitableWrapper(String endpointUrl,
 395  
                         Format format, EndpointType endpointType, DataParser dataParser,
 396  
                         NetworkManager networkManager, Cache cache, String jurisdictionId,
 397  
                         String apiKey) {
 398  7
                 if (city == City.BLOOMINGTON && format == Format.XML) {
 399  0
                         logManager
 400  
                                         .logInfo(
 401  
                                                         this,
 402  
                                                         "The selected endpoint's responses are not compatible with XML so it is necessary to skip invalid characters, using an specialized kind of APIWrapper.");
 403  0
                         return new InvalidXMLWrapper(endpointUrl, format, endpointType,
 404  
                                         dataParser, networkManager, cache, jurisdictionId, apiKey);
 405  
                 }
 406  7
                 if (endpointUrl.contains("seeclickfix") && format == Format.JSON) {
 407  0
                         logManager
 408  
                                         .logInfo(
 409  
                                                         this,
 410  
                                                         "The selected endpoint's JSON responses are not compatible with the standard, selecting the XML format.");
 411  0
                         format = Format.XML;
 412  0
                         dataParser = DataParserFactory.getInstance()
 413  
                                         .buildDataParser(format);
 414  0
                         networkManager.setFormat(format);
 415  
                 }
 416  
                 
 417  7
                 return new APIWrapper(endpointUrl, format, endpointType, dataParser,
 418  
                                 networkManager, cache, jurisdictionId, apiKey);
 419  
 
 420  
         }
 421  
 }