src/main/java/org/codeforamerica/open311/internals/network/HTTPNetworkManager.java
public class HTTPNetworkManager implements NetworkManager {
	private HttpClient httpClient;
	private Format format;
	private static final int TIMEOUT = 5000;
	private static final String ACCEPT_HEADER = "Accept";
	private static final String CONTENT_TYPE_HEADER = "Content-Type";

	public HTTPNetworkManager(Format format) {
		this.format = format;
		this.httpClient = getNewHttpClient();
	}

	@Override
	public String doGet(URL url) throws IOException {
		try {
			HttpGet httpGet = new HttpGet(url.toURI());
			httpGet.setHeader(ACCEPT_HEADER, format.getHTTPContentType());
			httpGet.setHeader(CONTENT_TYPE_HEADER, format.getHTTPContentType());
			HttpResponse response = httpClient.execute(httpGet);
			return EntityUtils.toString(response.getEntity(), CHARSET);
		} catch (Exception e) {
			throw new IOException(e.getMessage());
		}
	}

	@Override
	public String doPost(URL url, Map<String, String> parameters)
			throws IOException {
		try {
			HttpPost httpPost = new HttpPost(url.toURI());
			httpPost.setHeader(ACCEPT_HEADER, format.getHTTPContentType());
			httpPost.setHeader("Content-Type", POST_CONTENT_TYPE);
			httpPost.setEntity(generateHttpEntityFromParameters(parameters));
			HttpResponse response = httpClient.execute(httpPost);
			return EntityUtils.toString(response.getEntity(), CHARSET);
		} catch (Exception e) {
			throw new IOException(e);
		}
	}

	@Override
	public void setFormat(Format format) {
		this.format = format;
	}

	/**
	 * Builds an {@link HttpEntity} with all the given parameters.
	 * 
	 * @param parameters
	 *            A list of parameters of a POST request.
	 * @return An {@link UrlEncodedFormEntity} with the given parameters.
	 * @throws UnsupportedEncodingException
	 *             if the default encoding isn't supported.
	 */
	private HttpEntity generateHttpEntityFromParameters(
			Map<String, String> parameters) throws UnsupportedEncodingException {
		List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
		if (parameters != null) {
			for (Entry<String, String> parameterEntry : parameters.entrySet()) {
				nameValuePairs.add(new BasicNameValuePair(parameterEntry
						.getKey(), parameterEntry.getValue()));
			}
		}
		return new UrlEncodedFormEntity(nameValuePairs);
	}

	/**
	 * Builds a {@link HttpClient} which allows non trusted SSL certificates.
	 * 
	 * @return {@link HttpClient} which allows non trusted SSL certificates.
	 */
	private HttpClient getNewHttpClient() {
		try {
			KeyStore trustStore = KeyStore.getInstance(KeyStore
					.getDefaultType());
			trustStore.load(null, null);

			SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
			sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

			HttpParams params = new BasicHttpParams();
			HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
			HttpProtocolParams.setContentCharset(params, CHARSET);
			HttpConnectionParams.setConnectionTimeout(params, TIMEOUT);

			SchemeRegistry registry = new SchemeRegistry();
			registry.register(new Scheme("http", PlainSocketFactory
					.getSocketFactory(), 80));
			registry.register(new Scheme("https", sf, 443));

			ClientConnectionManager ccm = new ThreadSafeClientConnManager(
					params, registry);
			return new DefaultHttpClient(ccm, params);
		} catch (Exception e) {
			return new DefaultHttpClient();
		}
	}

	/**
	 * A SSLSocketFactory which allows non trusted SSL certificates.
	 * 
	 * @author Santiago Munín <santimunin@gmail.com>
	 * 
	 */
	private class MySSLSocketFactory extends SSLSocketFactory {
		SSLContext sslContext = SSLContext.getInstance("TLS");

		public MySSLSocketFactory(KeyStore truststore)
				throws NoSuchAlgorithmException, KeyManagementException,
				KeyStoreException, UnrecoverableKeyException {
			super(truststore);

			TrustManager tm = new X509TrustManager() {
				public void checkClientTrusted(X509Certificate[] chain,
						String authType) {
				}

				public void checkServerTrusted(X509Certificate[] chain,
						String authType) throws CertificateException {
				}

				public X509Certificate[] getAcceptedIssuers() {
					return null;
				}
			};

			sslContext.init(null, new TrustManager[] { tm }, null);
		}

		@Override
		public Socket createSocket(Socket socket, String host, int port,
				boolean autoClose) throws IOException, UnknownHostException {
			return sslContext.getSocketFactory().createSocket(socket, host,
					port, autoClose);
		}

		@Override
		public Socket createSocket() throws IOException {
			return sslContext.getSocketFactory().createSocket();
		}
	}
}
src/main/java/org/codeforamerica/open311/facade/exceptions/APIWrapperException.java
public static enum Error {
	DATA_PARSING("Problem while parsing text data."), NETWORK_MANAGER(
			"Network problem."), URL_BUILDER(
			"Problem while building a valid URL."), GEO_REPORT_V2(
			"GeoReport v2 problem."), NOT_SUITABLE_ENDPOINT_FOUND(
			"Problem finding a suitable endpoint."), NOT_CREATED_BY_A_WRAPPER(
			"The given object wasn't created by any wrapper");
	private String description;

	private Error(String description) {
		this.description = description;
	}

	public String toString() {
		return this.description;
	}
}
src/main/java/org/codeforamerica/open311/internals/logging/LogManager.java
public class LogManager {
	/**
	 * Unique instance of the logger.
	 */
	private Logger logger = PlatformManager.getInstance().buildLogger();
	/**
	 * Unique instance of the class.
	 */
	private static LogManager instance = new LogManager();
	/**
	 * Contains a wrapper if it has to be logged.
	 */
	private static Set<Object> loggedObjects = new HashSet<Object>();

	/**
	 * Prevents other classes to instantiate this class.
	 */
	private LogManager() {
	}

	/**
	 * Returns the unique instance of the class.
	 * 
	 * @return unique instance of the class.
	 */
	public static LogManager getInstance() {
		return instance;
	}

	/**
	 * Activates the logging for a concrete wrapper.
	 * 
	 * @param objectToBeLogged
	 *            Object to be logged.
	 */
	public void activate(Object objectToBeLogged) {
		loggedObjects.add(objectToBeLogged);
	}

	/**
	 * Disables the logging for a concrete wrapper.
	 * 
	 * @param loggedObject
	 *            Object which logging will be disabled.
	 */
	public void disable(Object loggedObject) {
		loggedObjects.remove(loggedObject);
	}

	/**
	 * Logs a non-critical event.
	 * 
	 * @param messageCreator
	 *            origin of the message.
	 * @param message
	 *            Event message.
	 */
	public void logInfo(Object messageCreator, String message) {
		if (loggedObjects.contains(messageCreator)) {
			logger.logInfo(buildMessage(messageCreator, message));
		}
	}

	/**
	 * Logs any problem.
	 * 
	 * @param messageCreator
	 *            origin of the message.
	 * @param message
	 *            Event message.
	 */
	public void logError(Object messageCreator, String message) {
		if (loggedObjects.contains(messageCreator)) {
			logger.logError(buildMessage(messageCreator, message));
		}
	}

	/**
	 * Builds a log message containing the wrapper information.
	 * 
	 * @param messageCreator
	 *            origin of the message.
	 * 
	 * @param message
	 *            Event message.
	 * @return
	 */
	private static String buildMessage(Object messageCreator, String message) {
		return "(" + messageCreator + ") --> " + message;
	}
}
src/main/java/org/codeforamerica/open311/internals/parsing/DataParserFactory.java
public class DataParserFactory {

	private static DataParserFactory instance = new DataParserFactory();

	private DataParserFactory() {
	}

	public static DataParserFactory getInstance() {
		return instance;
	}

	/**
	 * Builds a data parser taking care of the desired format.
	 * 
	 * @param format
	 *            Desired format.
	 * @return an instance of {@link DataParser} of <code>null</code> if the
	 *         given format is not supported.
	 */
	public DataParser buildDataParser(Format format) {
		if (format == Format.XML) {
			return new XMLParser();
		}
		if (format == Format.JSON) {
			return new JSONParser();
		}
		return null;
	}
}
src/main/java/org/codeforamerica/open311/internals/platform/PlatformManager.java
public class PlatformManager {
	/**
	 * Unique instance.
	 */
	private static PlatformManager instance = new PlatformManager();
	private boolean androidPlatform;

	/**
	 * Prevents other objects to instantiate instances of this class.
	 */
	private PlatformManager() {
		androidPlatform = System.getProperty("java.vm.name").equalsIgnoreCase(
				"Dalvik");
	}

	/**
	 * Returns the unique instance of the class.
	 * 
	 * @return The unique instance of the class.
	 */
	public static PlatformManager getInstance() {
		return instance;
	}

	/**
	 * Builds a cache instance taking care of the execution environment.
	 * 
	 * @return {@link NoCache} under Android, {@link RegularJavaCache}
	 *         otherwise. {@link AndroidCache} has to be built explicitly.
	 */
	public Cache buildCache() {
		return androidPlatform ? new NoCache() : RegularJavaCache.getInstance();
	}

	/**
	 * Builds a logger instance taking care of the execution environment.
	 * 
	 * @return {@link AndroidLogger} under Android. {@link RegularJavaLogger}
	 *         otherwise.
	 */
	public Logger buildLogger() {
		return androidPlatform ? new AndroidLogger() : new RegularJavaLogger();
	}

}
src/main/java/org/codeforamerica/open311/internals/network/NetworkManager.java
public interface NetworkManager {

	public static final String POST_CONTENT_TYPE = "application/x-www-form-urlencoded";
	public static final String CHARSET = HTTP.UTF_8;

	/**
	 * Sends a GET HTTP request.
	 * 
	 * @param url
	 *            Target.
	 * @return Server response.
	 * @throws IOException
	 *             If there was any problem with the connection.
	 */
	public String doGet(URL url) throws IOException;

	/**
	 * Sends a POST HTTP request.
	 * 
	 * @param url
	 *            Target.
	 * @param parameters
	 *            Parameters of the POST operation.
	 * @return Server response.
	 * @throws IOException
	 *             If there was any problem with the connection.
	 */
	public String doPost(URL url, Map<String, String> parameters)
			throws IOException;

	/**
	 * Sets the desired format of the requests.
	 * 
	 * @param format
	 *            A serialization format (XML or JSON).
	 */
	public void setFormat(Format format);
}
src/main/java/org/codeforamerica/open311/facade/exceptions/APIWrapperException.java
public class APIWrapperException extends Exception {

	private static final long serialVersionUID = 1L;
	private Error error;
	private GeoReportV2Error geoReportError;

	public APIWrapperException(String message, Error error,
			GeoReportV2Error geoReportError) {
		super(message);
		this.error = error;
		this.geoReportError = geoReportError;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	public Error getError() {
		return error;
	}

	public GeoReportV2Error getGeoReportError() {
		return geoReportError;
	}

	public String toString() {
		String result = this.getMessage() + " (" + this.error;
		if (geoReportError != null) {
			result += " -- " + geoReportError.toString();
		}
		result += ")";
		return result;
	}

	/**
	 * Different types of errors.
	 */
	public static enum Error {
		DATA_PARSING("Problem while parsing text data."), NETWORK_MANAGER(
				"Network problem."), URL_BUILDER(
				"Problem while building a valid URL."), GEO_REPORT_V2(
				"GeoReport v2 problem."), NOT_SUITABLE_ENDPOINT_FOUND(
				"Problem finding a suitable endpoint."), NOT_CREATED_BY_A_WRAPPER(
				"The given object wasn't created by any wrapper");
		private String description;

		private Error(String description) {
			this.description = description;
		}

		public String toString() {
			return this.description;
		}
	}

}
src/main/java/org/codeforamerica/open311/facade/data/Endpoint.java
public class Endpoint implements Serializable {
	private static final long serialVersionUID = -5512681512770606630L;
	private String specificationUrl;
	private String url;
	private Date changeset;
	private EndpointType type;
	private List<Format> formats;

	public Endpoint(String specificationUrl, String url, Date changeset,
			EndpointType type, List<Format> formats) {
		super();
		this.specificationUrl = specificationUrl;
		this.url = url;
		this.changeset = changeset;
		this.type = type;
		this.formats = formats;
	}

	public String getSpecificationUrl() {
		return specificationUrl;
	}

	public String getUrl() {
		return url;
	}

	public Date getChangeset() {
		return changeset;
	}

	public EndpointType getType() {
		return type;
	}

	/**
	 * Returns the more suitable format. Tries JSON before XML.
	 * 
	 * @return A format allowed by the endpoint.
	 */
	public Format getBestFormat() {
		if (formats.contains(Format.JSON)) {
			return Format.JSON;
		}
		if (formats.contains(Format.XML)) {
			return Format.XML;
		}
		return null;
	}

	/**
	 * Returns whether it allows the given format or not.
	 * 
	 * @param format
	 *            Desired format.
	 * @return <code>true</code> if it allows the given format, else
	 *         <code>false</code>.
	 */
	public boolean isCompatibleWithFormat(Format format) {
		return formats.contains(format);
	}
}
src/main/java/org/codeforamerica/open311/internals/caching/Cache.java
public interface Cache {
	/**
	 * Saves the endpoints-service discovery relationships.
	 * 
	 * @param data
	 *            A JSON list.
	 */
	public void saveCitiesInfo(String data);

	/**
	 * Looks for the endpoints-service discovery relationships.
	 * 
	 * @return A JSON list or <code>null</code> if it wasn't found.
	 */
	public String retrieveCitiesInfo();

	/**
	 * Saves a {@link ServiceDiscoveryInfo object} related to a city.
	 * 
	 * @param city
	 *            City related to the requested service discovery.
	 * @param serviceDiscovery
	 *            The obtained service discovery.
	 */
	public void saveServiceDiscovery(City city,
			ServiceDiscoveryInfo serviceDiscovery);

	/**
	 * Looks for the service discovery of a given city in the local cache.
	 * 
	 * @param city
	 *            City of interest.
	 * @return The service discovery of the given city of <code>null</code> if
	 *         it isn't cached.
	 */
	public ServiceDiscoveryInfo retrieveCachedServiceDiscoveryInfo(City city);

	/**
	 * Saves a list of {@link Service} objects related to an endpoint.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param services
	 *            Obtained services.
	 */
	public void saveListOfServices(String endpointUrl, List<Service> services);

	/**
	 * Looks for a cached list of services.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @return The list of services or <code>null</code> if they aren't cached.
	 */
	public List<Service> retrieveCachedServiceList(String endpointUrl);

	/**
	 * Saves a service definition.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param serviceCode
	 *            Code of the service.
	 * @param serviceDefinition
	 *            Obtained definition from the server.
	 */
	public void saveServiceDefinition(String endpointUrl, String serviceCode,
			ServiceDefinition serviceDefinition);

	/**
	 * Looks for a cached service definition.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param serviceCode
	 *            Service code of the desired service.
	 * @return A service definition or <code>null</code> if it isn't cached.
	 */
	public ServiceDefinition retrieveCachedServiceDefinition(
			String endpointUrl, String serviceCode);

	/**
	 * Saves a list of service requests.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param filter
	 *            Filter sent to the endpoint.
	 * @param requests
	 *            Obtained list of requests.
	 */
	public void saveServiceRequestList(String endpointUrl,
			GETServiceRequestsFilter filter, List<ServiceRequest> requests);

	/**
	 * Looks for a cached GET service requests response.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param filter
	 *            The desired filter.
	 * @return A list of ServiceRequest or <code>null</code> if they aren't
	 *         cached.
	 */
	public List<ServiceRequest> retrieveCachedServiceRequests(
			String endpointUrl, GETServiceRequestsFilter filter);

	/**
	 * Saves a service request.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param serviceRequestId
	 *            Id of the requested service request.
	 * @param request
	 *            Obtained service request.
	 */
	public void saveSingleServiceRequest(String endpointUrl,
			String serviceRequestId, ServiceRequest request);

	/**
	 * Looks for a cached GET service request response.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param serviceRequestId
	 *            The service request's id.
	 * @return A ServiceRequest or <code>null</code> if it wasn't cached.
	 */
	public ServiceRequest retrieveCachedServiceRequest(String endpointUrl,
			String serviceRequestId);

	/**
	 * Deletes the cache.
	 */
	public void deleteCache();

	/**
	 * Set a custom time to live to a given operation.
	 * 
	 * @param operation
	 *            Operation which time to live will be changed.
	 * @param timeToLiveInHours
	 *            New time to live (in hours).
	 */
	public void setCustomTimeToLive(CacheableOperation operation,
			int timeToLiveInHours);

	/**
	 * Set of operations which will be cached.
	 * 
	 * @author Santiago Munín <santimunin@gmail.com>
	 * 
	 */
	public static enum CacheableOperation {
		GET_SERVICE_DISCOVERY, GET_SERVICE_LIST, GET_SERVICE_DEFINITION, GET_SERVICE_REQUEST_LIST, GET_SINGLE_SERVICE_REQUEST, GET_CITIES_SERVICE_DISCOVERY_URLS;
	}
}
src/main/java/org/codeforamerica/open311/facade/exceptions/DataParsingException.java
public class DataParsingException extends Exception {

	private static final long serialVersionUID = 1L;

	public DataParsingException(String message) {
		super(message);
	}

}
src/main/java/org/codeforamerica/open311/internals/parsing/DataParser.java
public interface DataParser {
	public static final String TEXT_FORMAT = "utf-8";
	public static final String SERVICE_TAG = "service";
	public static final String SERVICE_CODE_TAG = "service_code";
	public static final String SERVICE_NAME_TAG = "service_name";
	public static final String DESCRIPTION_TAG = "description";
	public static final String METADATA_TAG = "metadata";
	public static final String TYPE_TAG = "type";
	public static final String KEYWORDS_TAG = "keywords";
	public static final String KEYWORDS_SEPARATOR = ",";
	public static final String SERVICE_GROUP_TAG = "group";
	public static final String SERVICE_DEFINITION_TAG = "service_definition";
	public static final String ATTRIBUTE_TAG = "attribute";
	public static final String ATTRIBUTES_TAG = "attributes";
	public static final String VARIABLE_TAG = "variable";
	public static final String CODE_TAG = "code";
	public static final String DATATYPE_TAG = "datatype";
	public static final String REQUIRED_TAG = "required";
	public static final String DATATYPE_DESCRIPTION_TAG = "datatype_description";
	public static final String ORDER_TAG = "order";
	public static final String VALUE_TAG = "value";
	public static final String VALUES_TAG = "values";
	public static final String KEY_TAG = "key";
	public static final String NAME_TAG = "name";
	public static final String SERVICE_REQUEST_TAG = "request";
	public static final String SERVICE_REQUESTS_TAG = "service_requests";
	public static final String TOKEN_TAG = "token";
	public static final String SERVICE_REQUEST_ID_TAG = "service_request_id";
	public static final String STATUS_TAG = "status";
	public static final String STATUS_NOTES_TAG = "status_notes";
	public static final String AGENCY_RESPONSIBLE_TAG = "agency_responsible";
	public static final String SERVICE_NOTICE_TAG = "service_notice";
	public static final String REQUESTED_DATETIME_TAG = "requested_datetime";
	public static final String UPDATED_DATETIME_TAG = "updated_datetime";
	public static final String EXPECTED_DATETIME_TAG = "expected_datetime";
	public static final String ADDRESS_TAG = "address";
	public static final String ADDRESS_ID_TAG = "address_id";
	public static final String ZIPCODE_TAG = "zipcode";
	public static final String LATITUDE_TAG = "lat";
	public static final String LONGITUDE_TAG = "long";
	public static final String MEDIA_URL_TAG = "media_url";
	public static final String ACCOUNT_ID_TAG = "account_id";
	public static final String ERROR_TAG = "error";
	public static final String DISCOVERY_TAG = "discovery";
	public static final String CHANGESET_TAG = "changeset";
	public static final String CONTACT_TAG = "contact";
	public static final String KEY_SERVICE_TAG = "key_service";
	public static final String SPECIFICATION_TAG = "specification";
	public static final String URL_TAG = "url";
	public static final String FORMAT_TAG = "format";
	public static final String ENDPOINT_TAG = "endpoint";
	public static final String START_DATE_TAG = "start_date";
	public static final String END_DATE_TAG = "end_date";
	public static final String EMAIL_TAG = "email";
	public static final String DEVICE_ID_TAG = "device_id";
	public static final String FIRST_NAME_TAG = "first_name";
	public static final String LAST_NAME_TAG = "last_name";
	public static final String PHONE_TAG = "phone";

	/**
	 * Parses the response to the GET service list operation.
	 * 
	 * @param rawData
	 *            Text data.
	 * @return A list of {@link Service} objects.
	 */
	public List<Service> parseServiceList(String rawData)
			throws DataParsingException;

	/**
	 * Parses a service definition.
	 * 
	 * @param rawData
	 *            Text data.
	 * @throws DataParsingException
	 *             If there was any problem parsing the data.
	 * 
	 * @return A service definition object.
	 */
	public ServiceDefinition parseServiceDefinition(String rawData)
			throws DataParsingException;

	/**
	 * Parses the response to the GET service request id from a token.
	 * 
	 * @param rawData
	 *            Text data.
	 * @return the given token and the service request id.
	 * @throws DataParsingException
	 *             If there was any problem parsing the data.
	 */
	public ServiceRequestIdResponse parseServiceRequestIdFromAToken(
			String rawData) throws DataParsingException;

	/**
	 * Parses a list of service requests.
	 * 
	 * @param rawData
	 *            Text data.
	 * @return A list of ServiceRequest objects.
	 * @throws DataParsingException
	 *             If there was any problem parsing the data.
	 */
	public List<ServiceRequest> parseServiceRequests(String rawData)
			throws DataParsingException;

	/**
	 * Parses the response of a POST Service Request operation.
	 * 
	 * @param rawData
	 *            Text data.
	 * @return an object containing the response information.
	 * @throws DataParsingException
	 *             If there was any problem parsing the data.
	 */
	public POSTServiceRequestResponse parsePostServiceRequestResponse(
			String rawData) throws DataParsingException;

	/**
	 * Parses an error and returns an object with its information.
	 * 
	 * @param rawData
	 *            Text data.
	 * @return Error information.
	 * @throws DataParsingException
	 *             If there was any problem parsing the data.
	 */
	public GeoReportV2Error parseGeoReportV2Errors(String rawData)
			throws DataParsingException;

	/**
	 * Parses a service discovery and returns an object with its information.
	 * 
	 * @param rawData
	 *            Text data.
	 * @return Service discovery information (endpoints and their formats).
	 * @throws DataParsingException
	 *             If there was any problem parsing the data.
	 */
	public ServiceDiscoveryInfo parseServiceDiscovery(String rawData)
			throws DataParsingException;
}
src/main/java/org/codeforamerica/open311/facade/data/ServiceDiscoveryInfo.java
public class ServiceDiscoveryInfo implements Serializable {

	private static final long serialVersionUID = 5804902138488110319L;
	private final static String GEO_REPORT_V2_SPECIFICATION_URL = "http://wiki.open311.org/GeoReport_v2";
	private Date changeset;
	private String contact;
	private String keyService;
	List<Endpoint> endpoints;

	public ServiceDiscoveryInfo(Date changeset, String contact,
			String keyService, List<Endpoint> endpoints) {
		super();
		this.changeset = changeset;
		this.contact = contact;
		this.keyService = keyService;
		this.endpoints = endpoints;
	}

	public Date getChangeset() {
		return changeset;
	}

	public String getContact() {
		return contact;
	}

	public String getKeyService() {
		return keyService;
	}

	public List<Endpoint> getEndpoints() {
		return endpoints;
	}

	/**
	 * Searches through the list of endpoints and tries to find a valid endpoint
	 * of the given type. Tries to get the GeoReport latest version (currently
	 * v2).
	 * 
	 * @param endpointType
	 *            Type of the desired endpoint.
	 * @return <code>null</code> if a suitable endpoint couldn't be found.
	 */
	public Endpoint getMoreSuitableEndpoint(EndpointType endpointType) {
		List<Endpoint> typeFilteredEndpoints = new LinkedList<Endpoint>();
		for (Endpoint endpoint : endpoints) {
			if (endpoint.getType() == endpointType) {
				typeFilteredEndpoints.add(endpoint);
			}
		}
		Endpoint candidate = null;
		for (Endpoint endpoint : typeFilteredEndpoints) {
			// Some endpoints don't follows the specification so it does this
			// "double check", which has been proved working.
			if (endpoint.getSpecificationUrl().equals(GEO_REPORT_V2_SPECIFICATION_URL)) {
				if (endpoint.getUrl().contains("v2")) {
					return endpoint;
				}
				candidate = endpoint;
			}
		}
		return candidate;
	}
}


Question: package org.codeforamerica.open311.facade;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.codeforamerica.open311.facade.data.Endpoint;
import org.codeforamerica.open311.facade.data.ServiceDiscoveryInfo;
import org.codeforamerica.open311.facade.exceptions.APIWrapperException;
import org.codeforamerica.open311.facade.exceptions.APIWrapperException.Error;
import org.codeforamerica.open311.facade.exceptions.DataParsingException;
import org.codeforamerica.open311.internals.caching.Cache;
import org.codeforamerica.open311.internals.logging.LogManager;
import org.codeforamerica.open311.internals.network.HTTPNetworkManager;
import org.codeforamerica.open311.internals.network.NetworkManager;
import org.codeforamerica.open311.internals.parsing.DataParser;
import org.codeforamerica.open311.internals.parsing.DataParserFactory;
import org.codeforamerica.open311.internals.platform.PlatformManager;



/**
 * Builds {@link APIWrapper} instances from different aspects specified by the
 * user. To set the optional parameters, use the setter methods and, then, call
 * {@link #build()}.
 * 
 * @author Santiago Munin <santimunin@gmail.com>
 * 
 */
public class APIWrapperFactory {
	;
	/**
	 * Used for the first way of building the {@link APIWrapper} (
	 * {@link #APIWrapperFactory(String, String, Format)}).
	 * 
	 */
	private String endpointUrl = null;
	/**
	 * Used for the first way of building the {@link APIWrapper} (
	 * {@link #APIWrapperFactory(String, String, Format)}).
	 * 
	 */
	private String jurisdictionId = null;
	/**
	 * Used for the second way of building the {@link APIWrapper} (
	 * {@link APIWrapperFactory#APIWrapperFactory(City, EndpointType)}).
	 * 
	 */
	private City city = null;
	/**
	 * Used for the second way of building the {@link APIWrapper} (
	 * {@link APIWrapperFactory#APIWrapperFactory(City, EndpointType)}).
	 */
	private EndpointType endpointType = null;
	/**
	 * Used for the second way of building the {@link APIWrapper} (
	 * {@link APIWrapperFactory#APIWrapperFactory(City, EndpointType)}).
	 */
	private Format format = null;
	private String apiKey = "";
	private NetworkManager networkManager = new HTTPNetworkManager(Format.XML);
	/**
	 * Suitable cache (depends on the execution environment).
	 */
	private Cache cache = PlatformManager.getInstance().buildCache();
	/**
	 * <code>true</code> if the built instance should be logged.
	 */
	private boolean log = false;
	/**
	 * Reference to the {@link LogManager}.
	 */
	private LogManager logManager = LogManager.getInstance();

	/**
	 * Builds an instance from the endpoint url.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 */
	public APIWrapperFactory(String endpointUrl) {
		this.endpointUrl = endpointUrl;
		this.jurisdictionId = "";
		this.format = Format.XML;
	}

	/**
	 * Builds an instance from the endpoint url and the jurisdiction_id.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param jurisdictionId
	 *            Desired jurisdiction_id (can be <code>null</code>).
	 */
	public APIWrapperFactory(String endpointUrl, String jurisdictionId) {
		this(endpointUrl);
		this.jurisdictionId = jurisdictionId == null ? "" : jurisdictionId;
	}

	/**
	 * Builds an instance from the endpoint url and the data interchange format.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param format
	 *            Data format. It is your responsibility to check if the given
	 *            format is allowed.
	 */
	public APIWrapperFactory(String endpointUrl, Format format) {
		this(endpointUrl);
		this.format = format;
	}

	/**
	 * Builds an instance from the endpoint url and the jurisdiction_id.
	 * 
	 * @param endpointUrl
	 *            Url of the endpoint.
	 * @param jurisdictionId
	 *            Desired jurisdiction_id (can be <code>null</code>).
	 * @param format
	 *            Data format. It is your responsibility to check if the given
	 *            format is allowed.
	 */
	public APIWrapperFactory(String endpointUrl, String jurisdictionId,
			Format format) {
		this(endpointUrl, jurisdictionId);
		this.format = format;
	}

	/**
	 * Builds an instance from the desired city.
	 * 
	 * The type of the endpoint will be {@link EndpointType#PRODUCTION}) and the
	 * format will be {@link Format#XML}.
	 * 
	 * @param city
	 *            Desired city.
	 */
	public APIWrapperFactory(City city) {
		this.city = city;
		this.endpointType = EndpointType.PRODUCTION;
		this.format = Format.XML;
	}

	/**
	 * Builds an instance from the desired city and endpoint type. The format
	 * will be {@link Format#XML}.
	 * 
	 * @param city
	 *            Desired city.
	 * @param endpointType
	 *            Desired endpoint type.
	 */
	public APIWrapperFactory(City city, EndpointType endpointType) {
		this(city);
		this.endpointType = endpointType;
	}

	/**
	 * Selects the desired data exchange format.
	 * 
	 * @param format
	 *            Desired format.
	 * @return The same instance with the new selected format.
	 */
	public APIWrapperFactory setFormat(Format format) {
		this.format = format;
		return this;
	}

	/**
	 * Sets the api key. <code>""</code> by default.
	 * 
	 * @param apiKey
	 *            Api key for the endpoint.
	 * @return The same instance with the new specified api key.
	 */
	public APIWrapperFactory setApiKey(String apiKey) {
		this.apiKey = apiKey;
		return this;
	}

	/**
	 * Sets a custom {@link NetworkManager}, useful if you need to use mocks or
	 * a platform-dependent network client which you can build implementing the
	 * {@link NetworkManager} interface.
	 * 
	 * @param networkManager
	 *            A implementation of the {@link NetworkManager} interface.
	 * @return The same instance with the new specified {@link NetworkManager}.
	 */
	public APIWrapperFactory setNetworkManager(NetworkManager networkManager) {
		this.networkManager = networkManager;
		this.networkManager.setFormat(Format.XML);
		return this;
	}

	/**
	 * Sets a desired cache system.
	 * 
	 * @param cache
	 *            Implementation of the {@link Cache} interface.
	 * @return The same instance with the new specified {@link Cache}.
	 */
	public APIWrapperFactory setCache(Cache cache) {
		if (cache != null) {
			this.cache = cache;
		}
		return this;
	}

	/**
	 * The built instance will be logged.
	 * 
	 * @return The same instance.
	 */
	public APIWrapperFactory withLogs() {
		this.log = true;
		LogManager.getInstance().activate(this);
		return this;
	}

	/**
	 * Builds an {@link APIWrapper}. <b>WARNING</b>: This operation could
	 * require some time to be done (it could involve network operations).
	 * 
	 * @return An instance built from the given parameters to this object.
	 * @throws APIWrapperException
	 *             If there was any problem.
	 */
	public APIWrapper build() throws APIWrapperException {
		if (city != null) {
			return buildWrapperFromCity(city, endpointType, apiKey,
					networkManager);
		}
		if (endpointUrl != null) {
			return buildWrapperFromEndpointUrl(endpointUrl, jurisdictionId,
					format);
		}
		return null;
	}

	/**
	 * Builds an {@link APIWrapper} ignoring the {@link APIWrapperFactory#city}
	 * and {@link APIWrapperFactory#endpointType} parameters.
	 * 
	 * @param endpointUrl
	 *            Endpoint's url.
	 * @param jurisdictionId
	 *            Jurisdiction id of the city. <b>WARNING</b>: This operation
	 *            does not check if the given format is allowed by the endpoint.
	 * @param format
	 *            Desired format to use.
	 * @return An instance built from the given parameters to this object.
	 */
	private APIWrapper buildWrapperFromEndpointUrl(String endpointUrl,
			String jurisdictionId, Format format) {
		logManager.logInfo(this,
				"Building a wrapper from the given endpoint url: "
						+ endpointUrl + ", jurisdiction_id: \""
						+ jurisdictionId + "\", format: " + format);
		return activateLoginIfRequested(createMostSuitableWrapper(endpointUrl,
				format, EndpointType.UNKNOWN, DataParserFactory.getInstance()
						.buildDataParser(format), networkManager, cache,
				jurisdictionId, apiKey));
	}

	/**
	 * Builds an {@link APIWrapper} ignoring the {@link #endpointUrl} and
	 * {@link #jurisdictionId} parameters. <b>NOTE</b>: This operation could
	 * require some time to be done (it involves network operations).
	 * 
	 * @param city
	 *            Desired city to work with.
	 * @param endpointType
	 *            Type of the desired endpoint (useful if you need, for example,
	 *            a dev one to test your application).
	 * @param apiKey
	 *            Api key which allows to perform some operations. If you need
	 *            one, go to the <a
	 *            href="http://wiki.open311.org/GeoReport_v2/Servers">list of
	 *            servers</a> and request it.
	 * @param networkManager
	 *            A {@link NetworkManager} implementation.
	 * @return An instance suited to the given parameters.
	 * 
	 * @throws APIWrapperException
	 *             If there was any problem.
	 */
	private APIWrapper buildWrapperFromCity(City city,
			EndpointType endpointType, String apiKey,
			NetworkManager networkManager) throws APIWrapperException {
		try {
			logManager.logInfo(this, "Getting the service discovery file.");
			DataParser dataParser = DataParserFactory.getInstance()
					.buildDataParser(Format.XML);
			ServiceDiscoveryInfo serviceDiscoveryInfo = cache
					.retrieveCachedServiceDiscoveryInfo(city);
			if (serviceDiscoveryInfo == null) {
				logManager
						.logInfo(this,
								"Service discovery file was not cached, downloading it.");
				serviceDiscoveryInfo = dataParser
						.parseServiceDiscovery(networkManager.doGet(new URL(
								city.getDiscoveryUrl())));
				cache.saveServiceDiscovery(city, serviceDiscoveryInfo);
			}
Answer: 			Endpoint endpoint = serviceDiscoveryInfo