All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opengis.cite.ogcapitiles10.util.JsonUtils Maven / Gradle / Ivy

There is a newer version: 1.1
Show newest version
package org.opengis.cite.ogcapitiles10.util;

import static io.restassured.RestAssured.given;
import static io.restassured.http.Method.GET;
import static org.opengis.cite.ogcapitiles10.OgcApiTiles10.GEOJSON_MIME_TYPE;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;

/**
 * @author Lyn Goltz 
 */
public class JsonUtils {

	private JsonUtils() {
	}

	/**
	 * Parses the id of the first feature from the passed json.
	 * @param collectionItemJson the json document containing the features, never
	 * null
	 * @return the parsed id, may be null if no feature could be found
	 */
	public static String parseFeatureId(JsonPath collectionItemJson) {
		List> features = collectionItemJson.get("features");
		if (features == null)
			return null;
		for (Map feature : features) {
			if (feature.containsKey("id"))
				return feature.get("id").toString();
		}
		return null;
	}

	/**
	 * Parses the temporal extent from the passed collection.
	 * @param collection the collection containing the extent to parse, never
	 * null
	 * @return the parsed temporal extent, null if no extent exists
	 * @throws IllegalArgumentException if the number of items in the extent invalid
	 *
	 */
	public static TemporalExtent parseTemporalExtent(Map collection) {
		Object extent = collection.get("extent");
		if (extent == null || !(extent instanceof Map))
			return null;
		Object spatial = ((Map) extent).get("temporal");
		if (spatial == null || !(spatial instanceof List))
			return null;
		List coords = (List) spatial;
		if (coords.size() == 2) {
			ZonedDateTime begin = parseAsDate((String) coords.get(0));
			ZonedDateTime end = parseAsDate((String) coords.get(1));
			return new TemporalExtent(begin, end);
		}
		throw new IllegalArgumentException("Temporal extent with " + coords.size() + " items is invalid");
	}

	/**
	 * Parses the passed string as ISO 8601 date.
	 * @param dateTime the dateTime to parse, never null
	 * @return the parsed date, never null
	 */
	public static ZonedDateTime parseAsDate(String dateTime) {
		return ZonedDateTime.parse(dateTime);
	}

	/**
	 * Formats the passed string as ISO 8601 date. Example: "2018-02-12T23:20:50Z"
	 * @param dateTime the dateTime to format, never null
	 * @return the formatted date, never null
	 */
	public static String formatDate(ZonedDateTime dateTime) {
		return DateTimeFormatter.ISO_INSTANT.format(dateTime);
	}

	/**
	 * Formats the passed string as ISO 8601 date. Example: "2018-02-12"
	 * @param date the dateTime to format, never null
	 * @return the formatted date, never null
	 */
	public static String formatDate(LocalDate date) {
		return DateTimeFormatter.ISO_DATE.format(date);
	}

	/**
	 * Formats the passed string as a period using a start and end time. Example:
	 * "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"
	 * @param beginDateTime the begin dateTime to format, never null
	 * @param endDateTime the end dateTime to format, never null
	 * @return the formatted date, never null
	 */
	public static String formatDateRange(ZonedDateTime beginDateTime, ZonedDateTime endDateTime) {
		return formatDate(beginDateTime) + "/" + formatDate(endDateTime);
	}

	/**
	 * Formats the passed string as a period using start time and a duration. Example:
	 * "2018-02-12T00:00:00Z/P1M6DT12H31M12S"
	 * @param beginDate the begin date to format, never null
	 * @param endDate the end date to format, never null
	 * @return the formatted date, never null
	 */
	public static String formatDateRangeWithDuration(LocalDate beginDate, LocalDate endDate) {
		Period betweenDate = Period.between(beginDate, endDate);
		return formatDate(beginDate) + "/" + betweenDate;
	}

	/**
	 * Parses the spatial extent from the passed collection.
	 * @param collection the collection containing the extent to parse, never
	 * null
	 * @return the parsed bbox, null if no extent exists
	 * @throws IllegalArgumentException if the number of items in the extent invalid
	 *
	 */
	public static BBox parseSpatialExtent(Map collection) {
		Object extent = collection.get("extent");
		if (extent == null || !(extent instanceof Map))
			return null;
		Object spatial = ((Map) extent).get("spatial");
		if (spatial == null || !(spatial instanceof List))
			return null;
		List coords = (List) spatial;
		if (coords.size() == 4) {
			double minX = parseValueAsDouble(coords.get(0));
			double minY = parseValueAsDouble(coords.get(1));
			double maxX = parseValueAsDouble(coords.get(2));
			double maxY = parseValueAsDouble(coords.get(3));
			return new BBox(minX, minY, maxX, maxY);
		}
		else if (coords.size() == 6) {
			throw new IllegalArgumentException(
					"BBox with " + coords.size() + " coordinates is currently not supported");
		}
		throw new IllegalArgumentException("BBox with " + coords.size() + " coordinates is invalid");
	}

	/**
	 * Parses all links with 'type' of one of the passed mediaTypes and the 'rel' property
	 * with the passed value.
	 * @param links list of all links, never null
	 * @param mediaTypesToSupport a list of media types the links searched for should
	 * support, may be empty but never null
	 * @param expectedRel the expected value of the property 'rel', never
	 * null
	 * @return a list of links supporting one of the media types and with the expected
	 * 'rel' property, may be empty but never null
	 */
	public static List> findLinksWithSupportedMediaTypeByRel(List> links,
			List mediaTypesToSupport, String expectedRel) {
		List> alternateLinks = new ArrayList<>();
		for (Map link : links) {
			Object type = link.get("type");
			Object rel = link.get("rel");
			if (expectedRel.equals(rel) && isSupportedMediaType(type, mediaTypesToSupport))
				alternateLinks.add(link);
		}
		return alternateLinks;
	}

	/**
	 * Parsing the media types which does not have a link woth property 'type' for.
	 * @param links list of links to search in, never null
	 * @param mediaTypesToSuppport a list of media types which should be supported, never
	 * null
	 * @return the media types which does not have a link for.
	 */
	public static List findUnsupportedTypes(List> links,
			List mediaTypesToSuppport) {
		List unsupportedType = new ArrayList<>();
		for (String contentMediaType : mediaTypesToSuppport) {
			boolean hasLinkForContentType = hasLinkForContentType(links, contentMediaType);
			if (!hasLinkForContentType)
				unsupportedType.add(contentMediaType);
		}
		return unsupportedType;
	}

	/**
	 * Parses the links without 'rel' or 'type' property.
	 * @param links list of links to search in, never null
	 * @param rels rels null
	 * @return the links without 'rel' or 'type' property
	 */
	public static List findLinksWithoutRelOrType(List> links, Set rels) {
		List linksWithoutRelOrType = new ArrayList<>();
		for (Map link : links) {
			if (rels.contains(link.get("rel")) && !linkIncludesRelAndType(link))
				linksWithoutRelOrType.add((String) link.get("href"));
		}
		return linksWithoutRelOrType;
	}

	/**
	 * Parses the link with 'rel=self'.
	 * @param links list of links to search in, never null
	 * @param expectedRel the expected value of the property 'rel', never
	 * null
	 * @return the link to itself or null if no such link exists
	 */
	public static Map findLinkByRel(List> links, String expectedRel) {
		if (links == null)
			return null;
		for (Map link : links) {
			Object rel = link.get("rel");
			if (expectedRel.equals(rel))
				return link;
		}
		return null;
	}

	/**
	 * Checks if the passed link contains 'rel' and 'type' properties.
	 * @param link to check, never null
	 * @return true if the link contains 'rel' and 'type' properties,
	 * false otherwise
	 */
	public static boolean linkIncludesRelAndType(Map link) {
		Object rel = link.get("rel");
		Object type = link.get("type");
		if (rel != null && type != null)
			return true;
		return false;
	}

	/**
	 * Checks if a property with the passed name exists in the jsonPath.
	 * @param propertyName name of the property to check, never null
	 * @param jsonPath to check, never null
	 * @return true if the property exists, false otherwise
	 */
	public static boolean hasProperty(String propertyName, JsonPath jsonPath) {
		return jsonPath.get(propertyName) != null;
	}

	/**
	 * Collects the number of all returned features by iterating over all 'next' links and
	 * summarizing the size of features in 'features' array property.
	 * @param jsonPath the initial collection, never null
	 * @param maximumLimit the limit parameter value to use, if <= 0 the parameter is
	 * omitted
	 * @return the number of all returned features
	 * @throws URISyntaxException if the creation of a uri fails
	 */
	public static int collectNumberOfAllReturnedFeatures(JsonPath jsonPath, int maximumLimit)
			throws URISyntaxException {
		int numberOfAllReturnedFeatures = jsonPath.getList("features").size();
		Map nextLink = findLinkByRel(jsonPath.getList("links"), "next");
		while (nextLink != null) {
			String nextUrl = (String) nextLink.get("href");
			URI uri = new URI(nextUrl);

			RequestSpecification accept = given().log().all().baseUri(nextUrl).accept(GEOJSON_MIME_TYPE);
			String[] pairs = uri.getQuery().split("&");
			String limitParamFromUri = null;
			for (String pair : pairs) {
				int idx = pair.indexOf("=");
				String key = pair.substring(0, idx);
				String value = pair.substring(idx + 1);
				if ("limit".equals(key)) {
					limitParamFromUri = value;
				}
				else {
					accept.param(key, value);
				}
			}
			if (maximumLimit > 0) {
				accept.param("limit", maximumLimit);
			}
			else if (limitParamFromUri != null) {
				accept.param("limit", limitParamFromUri);
			}

			Response response = accept.when().request(GET);
			response.then().statusCode(200);

			JsonPath nextJsonPath = response.jsonPath();
			int features = nextJsonPath.getList("features").size();
			if (features > 0) {
				numberOfAllReturnedFeatures += features;
				nextLink = findLinkByRel(nextJsonPath.getList("links"), "next");
			}
			else {
				nextLink = null;
			}
		}
		return numberOfAllReturnedFeatures;
	}

	/**
	 * Retrieves the property values as list.
	 * @param propertyName name of the property, never null
	 * @param jsonPath the json document to retrieve properties from, never
	 * null
	 * @return the property values as list, may be empty but never null
	 */
	public static List> parseAsListOfMaps(String propertyName, JsonPath jsonPath) {
		Object value = jsonPath.get(propertyName);
		if (value == null)
			return Collections.emptyList();
		return jsonPath.getList(propertyName);
	}

	private static boolean isSameMediaType(String mediaType1, String mediaType2) {
		if (mediaType1.contains(";") || mediaType2.contains(";")) {
			// media types are not case sensitive
			String[] components1 = mediaType1.toLowerCase().split(";");
			String[] components2 = mediaType2.toLowerCase().split(";");
			// type and subtype must match
			if (!components1[0].trim().equals(components2[0].trim()))
				return false;
			Set parameters1 = new HashSet<>();
			Set parameters2 = new HashSet<>();
			// normalize parameter values and compare them
			for (int i = 1; i < components1.length; i++) {
				String parameter = components1[i].trim().replace("\"", "");
				if (!parameter.isEmpty())
					parameters1.add(parameter);
			}
			for (int i = 1; i < components2.length; i++) {
				String parameter = components2[i].trim().replace("\"", "");
				if (!parameter.isEmpty())
					parameters2.add(parameter);
			}
			if (parameters1.size() != parameters2.size())
				return false;
			if (!parameters1.containsAll(parameters2))
				return false;
		}
		else if (!mediaType1.trim().equalsIgnoreCase(mediaType2.trim()))
			return false;

		return true;
	}

	private static boolean hasLinkForContentType(List> alternateLinks, String mediaType) {
		for (Map alternateLink : alternateLinks) {
			Object type = alternateLink.get("type");
			if (type instanceof String && isSameMediaType(mediaType, (String) type))
				return true;
		}
		return false;
	}

	private static boolean isSupportedMediaType(Object type, List mediaTypes) {
		for (String mediaType : mediaTypes) {
			if (type instanceof String && isSameMediaType(mediaType, (String) type))
				return true;
		}
		return false;
	}

	private static double parseValueAsDouble(Object cords) {
		if (cords instanceof Integer) {
			return ((Integer) cords).doubleValue();
		}
		else if (cords instanceof Float) {
			return ((Float) cords).doubleValue();
		}
		else if (cords instanceof Double) {
			return (Double) cords;
		}
		else {
			return Double.parseDouble(cords.toString());
		}
	}

}