/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.usergrid.rest.applications.queries; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.usergrid.persistence.index.utils.UUIDUtils; import org.apache.usergrid.rest.test.resource.AbstractRestIT; import org.apache.usergrid.rest.test.resource.model.Collection; import org.apache.usergrid.rest.test.resource.model.Entity; import org.apache.usergrid.rest.test.resource.model.QueryParameters; import org.apache.usergrid.utils.MapUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; /** * // TODO: Document this * * @since 4.0 */ public class GeoPagingTest extends AbstractRestIT { private static final Logger log = LoggerFactory.getLogger(GeoPagingTest.class); /** * Tests the ability to query groups by location * 1. Create several groups * 2. Query the groups from a nearby location, restricting the search * by creation time to a single entity where created[i-1] < created[i] < created[i+1] * 3. Verify that the desired entity i, and only the desired entity, is returned * * @throws IOException */ @Test //("Test uses up to many resources to run reliably") // USERGRID-1403 public void groupQueriesWithGeoPaging() throws IOException { int maxRangeLimit = 2000; long[] index = new long[maxRangeLimit]; Double lat = 37.0; Double lon = -75.0; //Create our base entity template Entity actor = new Entity(); actor.put("displayName", "Erin"); actor.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", lat) .map("longitude", lon)); Entity props = new Entity(); props.put("actor", actor); props.put("verb", "go"); props.put("content", "bragh"); // 1. Create several groups // Modifying the path will cause a new group to be created for (int i = 0; i < 5; i++) { String newPath = String.format("/kero" + i); props.put("path", newPath); props.put("ordinal", i); //save the entity Entity activity = this.app().collection("groups").post(props); //retrieve it again from the database activity = this.app().collection("groups").entity(activity).get(); index[i] = (Long) activity.get("created"); if (log.isDebugEnabled()) { log.debug("Activity {} created at {}", i, index[i]); } } this.waitForQueueDrainAndRefreshIndex(); // 2. Query the groups from a nearby location, restricting the search // by creation time to a single entity where created[i-1] < created[i] < created[i+1] //since this geo location is contained by an actor it needs to be actor.location. String query = "select * where actor.location within 20000 of 37.0,-75.0 " + " and created > " + (index[0]) + " and created < " + (index[2]) + " order by created"; QueryParameters params = new QueryParameters(); params.setQuery(query); Collection collection = this.app().collection("groups").get(params); assertEquals("Query should have returned 1 entity", 1, collection.getResponse().getEntityCount()); Entity entity = collection.next(); // 3. Verify that the desired entity i, and only the desired entity, is returned assertNotNull("Query should have returned 1 entity", entity); assertEquals(index[1], Long.parseLong(entity.get("created").toString())); assertFalse("Query should have returned only 1 entity", collection.hasNext()); try { entity = collection.next(); fail("Query should have returned only 1 entity"); } catch (NoSuchElementException nse) { //We're expecting a NoSuchElementException. This is good news, so no need to do //anything with the exception } } /** * Creates a store then queries to check ability to find different store from up to 40 mil meters away * 1. Create 2 entities * 2. Query from a short distance of the center point to ensure that none are returned * 3. Query within a huge distance of the center point to ensure that both are returned */ @Test public void testFarAwayLocationFromCenter() throws IOException { String collectionType = "testFarAwayLocation" + UUIDUtils.newTimeUUID(); final double lat = 37.776753; final double lon = -122.407846; QueryParameters queryClose = new QueryParameters(); queryClose.setQuery("select * where location within 20000 of " + String.valueOf(lat) + ", " + String.valueOf(lon) + ""); QueryParameters queryFar = new QueryParameters(); queryFar.setQuery("select * where location within " + Integer.MAX_VALUE + " of " + String.valueOf(lat) + ", " + String.valueOf(lon) + ""); // 1. Create 2 entities Entity props = new Entity(); props.put("name", "usergrid"); props.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", -33.746369) .map("longitude", 150.952183)); this.app().collection(collectionType).post(props); Entity props2 = new Entity(); props2.put("name", "usergrid2"); props2.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", -33.889058) .map("longitude", 151.124024)); this.app().collection(collectionType).post(props2); this.waitForQueueDrainAndRefreshIndex(); Collection collection = this.app().collection(collectionType).get(); assertEquals("Should return both entities", 2, collection.getResponse().getEntityCount()); // 2. Query within a short distance of the center point to ensure that none are returned collection = this.app().collection(collectionType).get(queryClose); assertEquals("Results from nearby, should return nothing", 0, collection.getResponse().getEntityCount()); // 3. Query within a huge distance of the center point to ensure that both are returned collection = this.app().collection(collectionType).get(queryFar); assertEquals("Results from center point to ridiculously far", 2, collection.getResponse().getEntityCount()); } /** * Test that geo-query returns co-located entities in expected order. */ @Test // USERGRID-1401 public void groupQueriesWithConsistentResults() throws IOException { int maxRangeLimit = 20; Entity[] cats = new Entity[maxRangeLimit]; // 1. Create several entities for (int i = 0; i < 20; i++) { Entity cat = new Entity(); cat.put("name", "cat" + i); cat.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", 37.0) .map("longitude", -75.0)); cat.put("ordinal", i); cats[i] = cat; this.app().collection("cats").post(cat); } this.waitForQueueDrainAndRefreshIndex(); QueryParameters params = new QueryParameters(); for (int consistent = 0; consistent < 20; consistent++) { // 2. Query a subset of the entities String query = String.format( "select * where location within 100 of 37, -75 and ordinal >= %s and ordinal < %s", cats[7].get("ordinal"), cats[10].get("ordinal")); params.setQuery(query); Collection collection = this.app().collection("cats").get(params); assertEquals(3, collection.getResponse().getEntityCount()); List entities = collection.getResponse().getEntities(); // 3. Test that the entities were returned in the order expected for (int i = 0; i > 3; i++) { // shouldn't start at 10 since you're excluding it above in the query, it should return 9,8,7 Entity entity = (Entity)entities.get(i); Entity savedEntity = cats[10 - i]; assertEquals(savedEntity.get("ordinal"), entity.get("ordinal")); } } } /** * Test to make sure that a geo query with an order by of the geo field returns in the correct order. This is known * as sorting based on distance to the coordinates in the within operand. * @throws IOException */ @Test public void geoQuerywithDistanceSort() throws IOException { int maxRangeLimit = 20; final Entity[] cats = new Entity[maxRangeLimit]; // 1. Create several entities for (int i = 0; i < 20; i++) { Entity cat = new Entity(); cat.put("name", "cat" + i); cat.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", 37.0) .map("longitude", -75.0 - 0.00001*i)); // as we create, make each entity is ascending further away from the later search of 37, -75 cats[i] = cat; this.app().collection("cats").post(cat); } this.waitForQueueDrainAndRefreshIndex(); final QueryParameters params = new QueryParameters(); for (int consistent = 0; consistent < 20; consistent++) { // 2. Query all of the entities final String query = "select * where location within 100 of 37, -75 order by location asc"; params.setQuery(query); params.setLimit(50); Collection collection = this.app().collection("cats").get(params); assertEquals(20, collection.getResponse().getEntityCount()); final List returnedEntities = collection.getResponse().getEntities(); // 3. Test that the entities were returned in the order expected for (int i = 0; i < maxRangeLimit; i++) { // shouldn't start at 10 since you're excluding it above in the query, it should return 9,8,7 final Entity returnedEntity = (Entity)returnedEntities.get(i); final Entity catEntity = cats[i]; assertEquals(catEntity.get("name"), returnedEntity.get("name")); } } } /** * Creates a store right on top of the center store and checks to see if we can find that store, then find both * stores. * 1. Create 2 entities * 2. Query from the center point to ensure that one is returned * 3. Query within a huge distance of the center point to ensure that both are returned */ @Test public void testFarAwayLocationWithOneResultCloser() throws IOException { String collectionType = "testFarAwayLocationWithOneResultCloser" + UUIDUtils.newTimeUUID(); final double lat = -33.746369; final double lon = 150.952183; QueryParameters queryClose = new QueryParameters(); queryClose.setQuery("select * where location within 10000 of " + String.valueOf(lat) + ", " + String.valueOf(lon) + ""); QueryParameters queryFar = new QueryParameters(); queryFar.setQuery("select * where location within " + Integer.MAX_VALUE + " of " + String.valueOf(lat) + ", " + String.valueOf(lon) + ""); // 1. Create 2 entities Entity props = new Entity(); props.put("name", "usergrid"); props.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", -33.746369) .map("longitude", 150.952183)); this.app().collection(collectionType).post(props); this.waitForQueueDrainAndRefreshIndex(); Entity props2 = new Entity(); props2.put("name", "usergrid2"); props2.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", -33.889058) .map("longitude", 151.124024)); this.app().collection(collectionType).post(props2); this.waitForQueueDrainAndRefreshIndex(); // 2. Query from the center point to ensure that one is returned Collection collection = this.app().collection(collectionType).get(queryClose); assertEquals("Results from nearby, should return 1 store", 1, collection.getResponse().getEntityCount()); // 3. Query within a huge distance of the center point to ensure that both are returned collection = this.app().collection(collectionType).get(queryFar); assertEquals("Results from center point to ridiculously far", 2, collection.getResponse().getEntityCount()); } /** * Creates two users, then a matrix of coordinates, then checks to see if any of the coordinates are near our users * 1. Create 2 users * 2. Create a list of geo points * 3. Test each to ensure it is not within 10000 meters of our users * * @throws IOException */ @Test public void createHugeMatrixOfCoordinates() throws IOException { //1. Create 2 users Entity props = new Entity(); props.put("username", "norwest"); props.put("displayName", "norwest"); props.put("email", "norwest@usergrid.com"); props.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", -33.746369) .map("longitude", 150.952183)); this.app().collection("users").post(props); props.put("username", "ashfield"); props.put("displayName", "ashfield"); props.put("email", "ashfield@usergrid.com"); props.put("location", new MapUtils.HashMapBuilder<String, Double>() .map("latitude", -33.746369) .map("longitude", 150.952183)); this.app().collection("users").post(props); this.waitForQueueDrainAndRefreshIndex(); // 2. Create a list of geo points List<double[]> points = new ArrayList<>(); points.add(new double []{33.746369, -89});//Woodland, MS points.add(new double []{33.746369, -91});//Beulah, MS points.add(new double []{-1.000000, 102.000000});//Somewhere in Indonesia points.add(new double []{-90.000000, 90.000000});//Antarctica points.add(new double []{90, 90});//Santa's house // 3. Test each to ensure it is not within 10000 meters of our users Iterator<double[]> pointIterator = points.iterator(); for ( double[] p = pointIterator.next(); pointIterator.hasNext(); p = pointIterator.next()) { String query = "select * where location within 10000 of " + p[0] + ", " + p[1];//locationQuery( 10000 ,center ); QueryParameters params = new QueryParameters(); params.setQuery(query); Collection collection = this.app().collection("users").get(params); assertEquals("Expected 0 results", 0, collection.getResponse().getEntityCount()); } } }