/** * Copyright (C) 2013 - 2015 the enviroCar community * * This file is part of the enviroCar app. * * The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.remote.util; import android.util.Log; import com.google.common.base.Preconditions; import org.envirocar.core.entity.Measurement; import org.envirocar.core.entity.Track; import org.envirocar.core.exception.NoMeasurementsException; import org.envirocar.core.exception.NotConnectedException; import org.envirocar.core.exception.ResourceConflictException; import org.envirocar.core.exception.UnauthorizedException; import org.envirocar.core.logging.Logger; import org.envirocar.core.util.Util; import java.util.ArrayList; import java.util.List; import java.util.Map; import retrofit.Response; /** * @author dewall */ public class EnvirocarServiceUtils { private static final Logger LOG = Logger.getLogger(EnvirocarServiceUtils.class); public static final int HTTP_MULTIPLE_CHOICES = 300; public static final int HTTP_BAD_REQUEST = 400; public static final int HTTP_UNAUTHORIZED = 401; public static final int HTTP_FORBIDDEN = 403; public static final int HTTP_NOT_FOUND = 404; public static final int HTTP_CONFLICT = 409; /** * Searches a given link string for the 'rel=last' value. A link string can look like this: * <p> * <https://envirocar.org/api/stable/sensors/?limit=100&page=3>;rel=last;type=application/json, * <https://envirocar.org/api/stable/sensors/?limit=100&page=2>;rel=next;type=application/json * * @param linkString the link string to search for the specific value. * @return the number of pages, which was encoded in the link string. */ public static final Integer getLastRelValueOfLink(String linkString) { if (linkString == null || linkString.isEmpty() || linkString.equals("")) { Log.w("EnvirocarServiceUtils", "Input string was null or empty"); return 0; } // Split the input string at the komma String[] split = linkString.split(","); // iterate through the array and try to find 'rel=last' for (String line : split) { if (line.contains("rel=last")) { String[] params = line.split(";"); if (params != null && params.length > 0) { // When found, then resolve the page value. return resolvePageValue(params[0]); } } } // Not found Log.w("EnvirocarServiceUtils", "rel=last not found in the input string. Therefore, return 0"); return 0; } /** * Extracts the page value contained in the url of the link header. * * @param sourceUrl the url to scan for the page value. * @return the page value of the url; */ private static final Integer resolvePageValue(String sourceUrl) { String url; // if the string starts with < and ends with >, then cut these chars. if (sourceUrl.startsWith("<")) { url = sourceUrl.substring(1, sourceUrl.length() - 1); } else { url = sourceUrl; } if (url.contains("?")) { int index = url.indexOf("?") + 1; if (index != url.length()) { String params = url.substring(index, url.length()); for (String kvp : params.split("&")) { if (kvp.startsWith("page")) { return Integer.parseInt(kvp.substring(kvp.indexOf("page") + 5)); } } } } return null; } public static final void assertStatusCode(int httpStatusCode, String error) throws UnauthorizedException, NotConnectedException, ResourceConflictException { if (httpStatusCode >= HTTP_MULTIPLE_CHOICES) { if (httpStatusCode == HTTP_UNAUTHORIZED || httpStatusCode == HTTP_FORBIDDEN) { throw new UnauthorizedException("Authentication failed: " + httpStatusCode + "; " + error); } else if (httpStatusCode == HTTP_CONFLICT) { throw new ResourceConflictException(error); } else { throw new NotConnectedException("Unsupported Server response: " + httpStatusCode + "; " + error); } } } /** * Checks whether the response's corresponding page has a next page or not. * * @param response the response of a request. * @return true if the response does not correspond to that of the last page. */ public static final boolean hasNextPage(Response<?> response) { Preconditions.checkNotNull(response, "Response input cannot be null!"); // Get the header als multimap. Map<String, List<String>> headerListMap = response.headers().toMultimap(); if (headerListMap.containsKey("Link")) { // Iterate over all Link entries in the link header and if there exist one containing a // "rel=last", then return true; for (String header : headerListMap.get("Link")) { if (header.contains("rel=last")) { return true; } } } // Otherwise, return false. return false; } /** * Resolves the number of pages encoded in the link header. * * @param response the response of a call. * @return the number of pages. */ public static final int resolvePageCount(Response<?> response) { Preconditions.checkNotNull(response, "Input response cannot be null!"); // Get the header als multimap. Map<String, List<String>> headerListMap = response.headers().toMultimap(); if (headerListMap.containsKey("Link")) { for (String header : headerListMap.get("Link")) { if (header.contains("rel=last")) { String[] params = header.split(";"); if (params != null && params.length > 0) { String sourceUrl = params[0]; // if the string starts with < and ends with >, then cut these chars. if (sourceUrl.startsWith("<")) { sourceUrl = sourceUrl.substring(1, sourceUrl.length() - 1); } if (sourceUrl.contains("?")) { int index = sourceUrl.indexOf("?") + 1; if (index != sourceUrl.length()) { String parames = sourceUrl.substring(index, sourceUrl.length()); // find the "page=..." substring for (String kvp : parames.split("&")) { if (kvp.startsWith("page")) { // Parse the value as integer. return Integer.parseInt(kvp.substring( kvp.indexOf("page") + 5)); } } } } } } } } // If the body is null, then return 0. Otherwise, return 1. // TODO return response.body() != null ? 0 : 0; } /** * Resolves the remote location of an successful upload response. * * @param response the successful response type. * @return the remote location of the uploaded entity. */ public static String resolveRemoteLocation(Response<?> response) { Preconditions.checkNotNull(response, "Response type can not be null."); Preconditions.checkState(response.isSuccess(), "Response has to be succesful to be able " + "to resolve the uploaded location"); // Get all headers in order to find out the location of the uploaded car. Map<String, List<String>> headerListMap = response.headers().toMultimap(); // Get the header of the location String location = ""; if (headerListMap.containsKey("Location")) { for (String locationHeader : headerListMap.get("Location")) { location += locationHeader; } } // Returns the location return location; } public static String resolveRemtoteID(String remoteLocation) { String[] split = remoteLocation.split("/"); String remoteID = split[split.length - 1]; if (remoteID == "" || remoteID.isEmpty()) { remoteID = split[split.length-2]; } return remoteID; } /** * resolve all not obfuscated measurements of a track. * <p> * This returns all measurements, if obfuscation is disabled. Otherwise * measurements within the first and last minute and those within the start/end * radius of 250 m are ignored (only if they are in the beginning/end of the track). * * @param track * @return */ public static Track getNonObfuscatedMeasurements(Track track, boolean obfuscate) { List<Measurement> measurements = track.getMeasurements(); if (obfuscate) { boolean wasAtLeastOneTimeNotObfuscated = false; ArrayList<Measurement> privateCandidates = new ArrayList<Measurement>(); ArrayList<Measurement> nonPrivateMeasurements = new ArrayList<Measurement>(); for (Measurement measurement : measurements) { try { /* * ignore early and late */ if (isTemporalObfuscationCandidate(measurement, track)) { continue; } /* * ignore distance */ if (isSpatialObfuscationCandidate(measurement, track)) { if (wasAtLeastOneTimeNotObfuscated) { privateCandidates.add(measurement); nonPrivateMeasurements.add(measurement); } continue; } /* * we may have found obfuscation candidates in the middle of the track * (may cross start or end point) in a PRIOR iteration * of this loop. these candidates can be removed now as we are again * out of obfuscation scope */ if (wasAtLeastOneTimeNotObfuscated) { privateCandidates.clear(); } else { wasAtLeastOneTimeNotObfuscated = true; } nonPrivateMeasurements.add(measurement); } catch (NoMeasurementsException e) { LOG.warn(e.getMessage(), e); } } /* * the private candidates which have made it until here * shall be ignored */ nonPrivateMeasurements.removeAll(privateCandidates); track.setMeasurements(privateCandidates); } return track; } /** * TODO a circular criteria could lead to * * @param measurement * @param track * @return */ private static boolean isSpatialObfuscationCandidate(Measurement measurement, Track track) throws NoMeasurementsException { return (Util.getDistance(track.getFirstMeasurement(), measurement) <= 0.25) || (Util.getDistance(track.getLastMeasurement(), measurement) <= 0.25); } private static boolean isTemporalObfuscationCandidate(Measurement measurement, Track track) throws NoMeasurementsException { return (measurement.getTime() - track.getStartTime() <= 60000 || track.getEndTime() - measurement.getTime() <= 60000); } }