/* * * * 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.persistence.index.impl; import java.util.*; import org.apache.usergrid.persistence.model.field.*; import org.junit.Test; import org.apache.usergrid.persistence.core.scope.ApplicationScope; import org.apache.usergrid.persistence.core.scope.ApplicationScopeImpl; import org.apache.usergrid.persistence.index.IndexEdge; import org.apache.usergrid.persistence.index.SearchEdge; import org.apache.usergrid.persistence.model.entity.Entity; import org.apache.usergrid.persistence.model.field.value.EntityObject; import org.apache.usergrid.persistence.model.field.value.Location; import org.apache.usergrid.persistence.model.util.EntityUtils; import org.apache.usergrid.persistence.model.util.UUIDGenerator; import rx.functions.Action2; import static org.apache.usergrid.persistence.core.util.IdGenerator.createId; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests our entity conversion */ public class EntityToMapConverterTest { @Test public void testBaseFields() { Entity entity = new Entity( "test" ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( entity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, entity ); //verify our root fields final String applicationId = entityMap.get( IndexingUtils.APPLICATION_ID_FIELDNAME ).toString(); assertEquals( IndexingUtils.applicationId( scope.getApplication() ), applicationId ); final String entityIdString = entityMap.get( IndexingUtils.ENTITY_ID_FIELDNAME ).toString(); assertEquals( IndexingUtils.entityId( entity.getId() ), entityIdString ); final String versionString = entityMap.get( IndexingUtils.ENTITY_VERSION_FIELDNAME ).toString(); assertEquals( versionString, version.toString() ); final String entityTypeString = entityMap.get( IndexingUtils.ENTITY_TYPE_FIELDNAME ).toString(); assertEquals( IndexingUtils.getType( scope, entity.getId() ), entityTypeString ); final String nodeIdString = entityMap.get( IndexingUtils.EDGE_NODE_ID_FIELDNAME ).toString(); assertEquals( IndexingUtils.nodeId( indexEdge.getNodeId() ), nodeIdString ); final String edgeName = entityMap.get( IndexingUtils.EDGE_NAME_FIELDNAME ).toString(); assertEquals( indexEdge.getEdgeName(), edgeName ); final String nodeType = entityMap.get( IndexingUtils.EDGE_NODE_TYPE_FIELDNAME ).toString(); assertEquals( indexEdge.getNodeType().toString(), nodeType ); final long edgeTimestamp = ( long ) entityMap.get( IndexingUtils.EDGE_TIMESTAMP_FIELDNAME ); assertEquals( indexEdge.getTimestamp(), edgeTimestamp ); final String edgeSearch = entityMap.get( IndexingUtils.EDGE_SEARCH_FIELDNAME ).toString(); assertEquals( IndexingUtils.createContextName( scope, indexEdge ), edgeSearch ); final Set<EntityField> fieldsSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); assertEquals( 0, fieldsSet.size() ); } @Test public void testStringField() { testSingleField( new StringField( "Name", "value" ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_STRING ) ) ); } @Test public void testBooleanField() { testSingleField( new BooleanField( "Name", true ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_BOOLEAN ) ) ); } @Test public void testIntegerField() { testSingleField( new IntegerField( "Name", 100 ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_LONG ) ) ); } @Test public void testLongField() { testSingleField( new LongField( "Name", 100l ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_LONG ) ) ); } @Test public void testFloadField() { testSingleField( new FloatField( "Name", 1.10f ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_DOUBLE ) ) ); } /** * Test converting a UUID to a string */ @Test public void testUUIDField() { testSingleField( new UUIDField( "Name", UUIDGenerator.newTimeUUID() ), ( field, entityField ) -> assertEquals( field.getValue().toString(), entityField.get( IndexingUtils.FIELD_STRING ) ) ); } @Test public void testDoubleField() { testSingleField( new DoubleField( "Name", 2.20d ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_DOUBLE ) ) ); } @Test public void testNullField() { testSingleField( new NullField( "NameNullField" ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_NULL) ) ); } @Test public void testLocationField() { testSingleField( new LocationField( "Name", new Location( 10, 20 ) ), ( field, entityField ) -> { final Map<String, Double> latLong = ( Map<String, Double> ) entityField.get( IndexingUtils.FIELD_LOCATION ); assertEquals( Double.valueOf( 10 ), latLong.get( "lat" ) ); assertEquals( Double.valueOf( 20 ), latLong.get( "lon" ) ); } ); } /** * Test the single field in our root level */ public <T> void testSingleField( final Field<T> field, Action2<Field, EntityField> assertFunction ) { Entity entity = new Entity( "test" ); entity.setField( field ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( entity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, entity ); final Set<EntityField> fieldSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); final List<EntityField> fields = new ArrayList<>(); fields.addAll(fieldSet); assertEquals( 1, fields.size() ); final EntityField esField = fields.get( 0 ); //always test the lower case arrays final String fieldName = field.getName().toLowerCase(); assertEquals( fieldName, esField.get( IndexingUtils.FIELD_NAME ) ); assertFunction.call( field, esField ); } @Test public void testStringArray() { final ArrayField<String> array = new ArrayField<>( "strings" ); array.add( "one" ); array.add( "two" ); array.add( "three" ); // all unique values, we expect 3 values testPrimitiveArray( array, IndexingUtils.FIELD_STRING, 3 ); } @Test public void testBooleanArray() { final ArrayField<Boolean> array = new ArrayField<>( "bools" ); array.add( true ); array.add( true ); array.add( false ); array.add( true ); // 3 are duplicates, we expect only 2 values testPrimitiveArray( array, IndexingUtils.FIELD_BOOLEAN, 2 ); } @Test public void testIntArray() { final ArrayField<Integer> array = new ArrayField<>( "ints" ); array.add( 1 ); array.add( 2 ); array.add( 3 ); // all unique values, we expect 3 values testPrimitiveArray( array, IndexingUtils.FIELD_LONG, 3 ); } @Test public void testLongArray() { final ArrayField<Long> array = new ArrayField<>( "longs" ); array.add( 1l ); array.add( 2l ); array.add( 3l ); // all unique values, we expect 3 values testPrimitiveArray( array, IndexingUtils.FIELD_LONG, 3 ); } @Test public void testFloatArray() { final ArrayField<Float> array = new ArrayField<>( "floats" ); array.add( 1.0f ); array.add( 2.0f ); array.add( 3.0f ); // all unique values, we expect 3 values testPrimitiveArray( array, IndexingUtils.FIELD_DOUBLE, 3 ); } @Test public void testDoubleArray() { final ArrayField<Double> array = new ArrayField<>( "doubles" ); array.add( 1.0d ); array.add( 2.0d ); array.add( 3.0d ); // all unique values, we expect 3 values testPrimitiveArray( array, IndexingUtils.FIELD_DOUBLE, 3 ); } /** * Test primitive arrays in the root of an object * * @param indexType the field name for the expected type in ES */ private <T> void testPrimitiveArray( final ArrayField<T> array, final String indexType, int expectedCount ) { Entity entity = new Entity( "test" ); entity.setField( array ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( entity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, entity ); final Set<EntityField> fieldSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); final List<EntityField> fields = new ArrayList<>(); fields.addAll(fieldSet); assertEquals( expectedCount, fields.size() ); // take the incoming test ArrayField value and unique it since the conversion uses a Set and won't allow dups Set<T> uniqueInputSet = new HashSet<>(); array.getValue().forEach( item -> uniqueInputSet.add(item)); List<T> uniqueInputList = new ArrayList<>(); uniqueInputList.addAll(uniqueInputSet); ArrayField<T> uniqueArrayField = new ArrayField<>( array.getName(), uniqueInputList); for ( int i = 0; i < uniqueArrayField.size(); i++ ) { final EntityField field = fields.get( i ); assertEquals( uniqueArrayField.getName(), field.get( IndexingUtils.FIELD_NAME ) ); // we already verified our size and it is unique, just check contains so we don't have to sort a list //noinspection SuspiciousMethodCalls assertTrue( uniqueArrayField.getValue().contains( field.get( indexType ) ) ); } } @Test public void testStringFieldSubObject() { testNestedField( new StringField( "name", "value" ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_STRING ) ) ); } @Test public void testBooleanFieldSubObject() { testNestedField( new BooleanField( "name", true ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_BOOLEAN ) ) ); } @Test public void testIntegerFieldSubObject() { testNestedField( new IntegerField( "name", 100 ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_LONG ) ) ); } @Test public void testLongFieldSubObject() { testNestedField( new LongField( "name", 100l ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_LONG ) ) ); } @Test public void testFloadFieldSubObject() { testNestedField( new FloatField( "name", 1.10f ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_DOUBLE ) ) ); } @Test public void testDoubleFieldSubObject() { testNestedField( new DoubleField( "name", 2.20d ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_DOUBLE ) ) ); } @Test public void testLocationFieldSubObject() { testNestedField( new LocationField( "name", new Location( 10, 20 ) ), ( field, entityField ) -> { final Map<String, Double> latLong = ( Map<String, Double> ) entityField.get( IndexingUtils.FIELD_LOCATION ); assertEquals( Double.valueOf( 10 ), latLong.get( "lat" ) ); assertEquals( Double.valueOf( 20 ), latLong.get( "lon" ) ); } ); } /** * Test primitive arrays in the root of an object * * @param storedField The field stored on the nested object * @param assertFunction The function */ private <T> void testNestedField( final Field<T> storedField, Action2<Field, EntityField> assertFunction ) { EntityObject leafEntity = new EntityObject(); leafEntity.setField( storedField ); final EntityObjectField nested2Field = new EntityObjectField( "nested2", leafEntity ); final EntityObject nested2 = new EntityObject(); nested2.setField( nested2Field ); final EntityObjectField nested1Field = new EntityObjectField( "nested1", nested2 ); Entity rootEntity = new Entity( "test" ); rootEntity.setField( nested1Field ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( rootEntity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, rootEntity ); final Set<EntityField> fieldSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); final List<EntityField> fields = new ArrayList<>(); fields.addAll(fieldSet); assertEquals( 1, fields.size() ); for ( int i = 0; i < fields.size(); i++ ) { final EntityField field = fields.get( i ); final String path = "nested1.nested2." + storedField.getName(); assertEquals( path, field.get( IndexingUtils.FIELD_NAME ) ); assertFunction.call( storedField, field ); } } @Test public void testStringFieldSubObjectArray() { testNestedFieldArraySubObject( new StringField( "name", "value" ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_STRING ) ) ); } @Test public void testBooleanFieldSubObjectArray() { testNestedFieldArraySubObject( new BooleanField( "name", true ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_BOOLEAN ) ) ); } @Test public void testIntegerFieldSubObjectArray() { testNestedFieldArraySubObject( new IntegerField( "name", 100 ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_LONG ) ) ); } @Test public void testLongFieldSubObjectArray() { testNestedFieldArraySubObject( new LongField( "name", 100l ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_LONG ) ) ); } @Test public void testFloadFieldSubObjectArray() { testNestedFieldArraySubObject( new FloatField( "name", 1.10f ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_DOUBLE ) ) ); } @Test public void testDoubleFieldSubObjectArray() { testNestedFieldArraySubObject( new DoubleField( "name", 2.20d ), ( field, entityField ) -> assertEquals( field.getValue(), entityField.get( IndexingUtils.FIELD_DOUBLE ) ) ); } @Test public void testLocationFieldSubObjectArray() { testNestedFieldArraySubObject( new LocationField( "name", new Location( 10, 20 ) ), ( field, entityField ) -> { final Map<String, Double> latLong = ( Map<String, Double> ) entityField.get( IndexingUtils.FIELD_LOCATION ); assertEquals( Double.valueOf( 10 ), latLong.get("lat" ) ); assertEquals( Double.valueOf( 20 ), latLong.get("lon" ) ); } ); } /** * Test primitive arrays in the root of an object * * @param storedField The field stored on the nested object * @param assertFunction The function */ private <T> void testNestedFieldArraySubObject( final Field<T> storedField, Action2<Field, EntityField> assertFunction ) { EntityObject leafEntity = new EntityObject(); leafEntity.setField( storedField ); final EntityObjectField nested2Field = new EntityObjectField( "nested2", leafEntity ); final EntityObject nested2 = new EntityObject(); nested2.setField( nested2Field ); final EntityObjectField nested1Field = new EntityObjectField( "nested1", nested2 ); final EntityObject nested1 = new EntityObject(); nested1.setField( nested1Field ); final ArrayField<EntityObject> array = new ArrayField<>( "array" ); array.add( nested1 ); Entity rootEntity = new Entity( "test" ); rootEntity.setField( array ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( rootEntity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, rootEntity ); final Set<EntityField> fieldSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); final List<EntityField> fields = new ArrayList<>(); fields.addAll(fieldSet); assertEquals( 1, fields.size() ); for ( int i = 0; i < fields.size(); i++ ) { final EntityField field = fields.get( i ); final String path = "array.nested1.nested2." + storedField.getName(); assertEquals( path, field.get( IndexingUtils.FIELD_NAME ) ); assertFunction.call( storedField, field ); } } /** * 2d + arrays aren't supported, ensure we drop elements with a depth > 1 */ @Test public void testNDimensionalArray() { //create an object with a string value in it, this should be indexed final StringField string = new StringField( "string", "value" ); final EntityObject nested1 = new EntityObject(); nested1.setField( string ); //create a sub array, this shouldn't get indexed final ArrayField<String> subArray = new ArrayField<>( "subArray" ); subArray.add( "test2" ); final ArrayField<Object> array = new ArrayField<>( "array" ); //not indexed array.add( subArray ); //indexed array.add( nested1 ); Entity rootEntity = new Entity( "test" ); rootEntity.setField( array ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( rootEntity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, rootEntity ); final Set<EntityField> fieldSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); final List<EntityField> fields = new ArrayList<>(); fields.addAll(fieldSet); assertEquals( 1, fields.size() ); final EntityField field = fields.get( 0 ); final String path = "array.string"; assertEquals( path, field.get( IndexingUtils.FIELD_NAME ) ); assertEquals( "value", field.get( IndexingUtils.FIELD_STRING ) ); } /** * Objects nested within arrays are allowed to be indexed (just not n+1 nested arrays themselves) */ @Test public void testObjectNestedInArray() { final ArrayField<Object> array = new ArrayField<>("array"); // this should not get indexed final ArrayField<Object> nestedArray1 = new ArrayField<>("nestedArray"); nestedArray1.add("1"); // this should get indexed final EntityObject nestedObject1 = new EntityObject(); final ArrayField<Object> objectArray = new ArrayField<>("nestedArrayInObject"); final EntityObject nestedObject2 = new EntityObject(); nestedObject2.setField(new StringField("nestedKey", "nestedValue")); objectArray.add(nestedObject2); nestedObject1.setField(objectArray); array.add(nestedArray1); array.add(nestedObject1); Entity rootEntity = new Entity( "test" ); rootEntity.setField( array ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( rootEntity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, rootEntity ); final Set<EntityField> fieldSet = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); final List<EntityField> fields = new ArrayList<>(); fields.addAll(fieldSet); // if size of fields is not == 1, then we either // 1) did not index anything or 2) indexed the nested array that shouldn't be indexed at all assertEquals( 1, fields.size() ); final EntityField field = fields.get( 0 ); final String path = "array.nestedArrayInObject.nestedKey".toLowerCase(); assertEquals( path, field.get( IndexingUtils.FIELD_NAME ) ); } /** * Objects nested within arrays are allowed to be indexed (just not n+1 nested arrays themselves) */ @Test public void testNullsWithinArray() { final ArrayField<Object> array = new ArrayField<>("array"); // add a couple null values array.add(null); array.add("test"); // this should get indexed Entity rootEntity = new Entity( "test" ); rootEntity.setField( array ); final UUID version = UUIDGenerator.newTimeUUID(); EntityUtils.setVersion( rootEntity, version ); final ApplicationScope scope = new ApplicationScopeImpl( createId( "application" ) ); final IndexEdge indexEdge = new IndexEdgeImpl( createId( "source" ), "testEdgeType", SearchEdge.NodeType.SOURCE, 1000 ); final Map<String, Object> entityMap = EntityToMapConverter.convert( scope, indexEdge, rootEntity ); final Set<EntityField> fields = ( Set<EntityField> ) entityMap.get( IndexingUtils.ENTITY_FIELDS ); List<EntityField> fieldsArray = new ArrayList<>(); fieldsArray.addAll(fields); assertEquals( 2, fields.size() ); final EntityField field = fieldsArray.get( 0 ); final EntityField field1 = fieldsArray.get( 1 ); // the fields get re-sorted on array to list conversion assertEquals( "test", field.get( IndexingUtils.FIELD_STRING ) ); assertEquals( null, field1.get( IndexingUtils.FIELD_NULL ) ); } @Test public void entityFieldEquality() { final EntityField e1 = EntityField.create("testname", "testvalue"); final EntityField e2 = EntityField.create("testname", "testvalue"); final EntityField e3 = EntityField.create("testname", "testvalue1"); assertTrue( e1.equals(e2)); assertTrue( !e1.equals(e3)); } }