You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2014/02/19 17:20:40 UTC
git commit: Beginnings of an Index module with some test
infrastructure / ElasticSearch experiments.
Repository: incubator-usergrid
Updated Branches:
refs/heads/two-dot-o c351bc827 -> 2fc1efea0
Beginnings of an Index module with some test infrastructure / ElasticSearch experiments.
Project: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/commit/2fc1efea
Tree: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/tree/2fc1efea
Diff: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/diff/2fc1efea
Branch: refs/heads/two-dot-o
Commit: 2fc1efea0688f8ae152cfc808f63dec42961619b
Parents: c351bc8
Author: Dave Johnson <dm...@apigee.com>
Authored: Wed Feb 19 10:35:57 2014 -0500
Committer: Dave Johnson <dm...@apigee.com>
Committed: Wed Feb 19 10:35:57 2014 -0500
----------------------------------------------------------------------
stack/corepersistence/index/pom.xml | 190 ++++++++++++++++
.../usergrid/persistence/index/EntityIndex.java | 61 +++++
.../index/impl/ElasticSearchUtils.java | 204 +++++++++++++++++
.../persistence/index/impl/EntityIndexImpl.java | 39 ++++
.../persistence/index/impl/IndexModule.java | 35 +++
.../persistence/index/EntityIndexTest.java | 122 ++++++++++
.../index/impl/ElasticSearchTest.java | 91 ++++++++
.../index/impl/ElasticSearchUtilsTest.java | 132 +++++++++++
.../persistence/utils/ElasticSearchRule.java | 66 ++++++
.../index/src/test/resources/log4j.properties | 13 ++
.../index/src/test/resources/sample.json | 222 +++++++++++++++++++
11 files changed, 1175 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/pom.xml
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/pom.xml b/stack/corepersistence/index/pom.xml
new file mode 100644
index 0000000..37984e8
--- /dev/null
+++ b/stack/corepersistence/index/pom.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <properties>
+ <guice.version>3.0</guice.version>
+ <guicyfig.version>3.3</guicyfig.version>
+ <slf4j.version>1.7.2</slf4j.version>
+ <log4j.version>1.2.17</log4j.version>
+ <chop.version>1.0</chop.version>
+ </properties>
+
+ <parent>
+ <artifactId>persistence</artifactId>
+ <groupId>org.apache.usergrid</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <description>Module provates indexing for Entities via ElasticSearch</description>
+
+ <artifactId>index</artifactId>
+
+ <build>
+
+ <testResources>
+ <testResource>
+ <directory>src/test/resources</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </testResource>
+ </testResources>
+
+ <plugins>
+
+ <plugin>
+ <groupId>org.safehaus.chop</groupId>
+ <artifactId>chop-maven-plugin</artifactId>
+ <version>${chop.version}</version>
+
+ <!--
+ NOTE: you should be putting most of these variables into your settings.xml
+ as an automatically activated profile.
+ -->
+
+ <configuration>
+ <accessKey>${aws.s3.key}</accessKey>
+ <secretKey>${aws.s3.secret}</secretKey>
+ <availabilityZone>${availabilityZone}</availabilityZone>
+ <bucketName>${aws.s3.bucket}</bucketName>
+ <managerAppUsername>admin</managerAppUsername>
+ <managerAppPassword>${manager.app.password}</managerAppPassword>
+ <testPackageBase>org.apache.usergrid</testPackageBase>
+ <runnerSSHKeyFile>${runner.ssh.key.file}</runnerSSHKeyFile>
+ <failIfCommitNecessary>false</failIfCommitNecessary>
+ <amiID>${ami.id}</amiID>
+ <instanceType>m1.large</instanceType>
+ <resultsDirectory>${resultsDirectory}</resultsDirectory>
+ <dumpType>${dumpType}</dumpType>
+ <coldRestartTomcat>true</coldRestartTomcat>
+ <awsSecurityGroup>${security.group}</awsSecurityGroup>
+ <runnerKeyPairName>${runner.keypair.name}</runnerKeyPairName>
+ <runnerCount>6</runnerCount>
+ <securityGroupExceptions>
+ <!--
+ Add your own IP address as an exception to allow access
+ but please do this in the settings.xml file .. essentially
+ all parameters should be in the settings.xml file.
+ -->
+ <param>${myip.address}/32:24981</param>
+ <param>${myip.address}/32:22</param>
+ </securityGroupExceptions>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <check>
+ <branchRate>85</branchRate>
+ <lineRate>85</lineRate>
+ <haltOnFailure>true</haltOnFailure>
+ <totalBranchRate>85</totalBranchRate>
+ <totalLineRate>85</totalLineRate>
+ <packageLineRate>85</packageLineRate>
+ <packageBranchRate>85</packageBranchRate>
+ </check>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>clean</goal>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.usergrid</groupId>
+ <artifactId>model</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <type>jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.usergrid</groupId>
+ <artifactId>collection</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <type>jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.safehaus.chop</groupId>
+ <artifactId>chop-api</artifactId>
+ <version>${chop.version}</version>
+ </dependency>
+
+ <!-- Google Guice Integration Test Injectors -->
+
+ <dependency>
+ <groupId>${project.parent.groupId}</groupId>
+ <artifactId>model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.elasticsearch</groupId>
+ <artifactId>elasticsearch</artifactId>
+ <version>1.0.0.RC1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.safehaus.guicyfig</groupId>
+ <artifactId>guicyfig</artifactId>
+ <version>${guicyfig.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jukito</groupId>
+ <artifactId>jukito</artifactId>
+ <version>${jukito.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- lang utils for setting uuids etc -->
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.1</version>
+ </dependency>
+
+ <!-- Google Guice -->
+
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <version>${guice.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+
+ </dependencies>
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/EntityIndex.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/EntityIndex.java b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/EntityIndex.java
new file mode 100644
index 0000000..6ee77c8
--- /dev/null
+++ b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/EntityIndex.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ *//*
+ * 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;
+
+import org.apache.usergrid.persistence.collection.CollectionScope;
+import org.apache.usergrid.persistence.model.entity.Entity;
+
+
+/**
+ * Provides indexing of Entities within Collections.
+ */
+public interface EntityIndex {
+
+ /**
+ * Index or update index of entity.
+ * @param entity Entity to be indexed.
+ * @param scope Collection that contains Entity.
+ */
+ public void index( Entity entity, CollectionScope scope );
+
+ /**
+ * Remove index of entity.
+ * @param entity Entity to be removed from index.
+ * @param scope Collection that contains Entity.
+ */
+ public void deindex( Entity entity, CollectionScope scope );
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtils.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtils.java b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtils.java
new file mode 100644
index 0000000..8241d2c
--- /dev/null
+++ b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtils.java
@@ -0,0 +1,204 @@
+/*
+ * 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.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Methods for preparing data for indexing in ElasticSearch, and for creating mapping.
+ */
+public class ElasticSearchUtils {
+ private static final Logger logger = LoggerFactory.getLogger( ElasticSearchUtils.class );
+
+ /**
+ * Duplicate each string field with a new field named {fieldname}_ug_analyzed
+ *
+ * @param name Name of JSON object to be emitted or null for none
+ * @param builder Builder to emit to
+ * @param type Type a.k.a. collection name
+ * @param data Data to be augmented with _ug_analyzed fields.
+ *
+ * @return builder With augmented content
+ * @throws IOException ON JSON generation error.
+ */
+ public static XContentBuilder prepareContentForIndexing( String name, XContentBuilder builder,
+ String type, Map<String, Object> data ) throws IOException {
+
+ if ( name != null ) {
+ builder = builder.startObject( name );
+ } else {
+ builder = builder.startObject();
+ }
+ for ( String key : data.keySet() ) {
+ Object value = data.get( key );
+ logger.info( "value class = " + value.getClass().getName() );
+ try {
+
+ if ( value instanceof Map ) {
+ builder = prepareContentForIndexing(
+ key, builder, type, (Map<String, Object>)data.get(key));
+
+ } else if ( value instanceof List ) {
+ builder = prepareContentForIndexing(key, builder, type, (List)value);
+
+ } else if ( value instanceof String ) {
+ builder = builder
+ .field( key + "_ug_analyzed", value )
+ .field( key, value );
+
+ } else {
+ builder = builder
+ .field( key, value );
+
+ }
+ } catch ( Exception e ) {
+ logger.error( "Error processing {} : {}", key, value, e );
+ throw new RuntimeException(e);
+ }
+ }
+ builder = builder.endObject();
+ return builder;
+ }
+
+
+ /**
+ * Duplicate each string field with a new field named {fieldname}_ug_analyzed
+ *
+ * @param builder Builder to emit to
+ * @param type Type a.k.a. collection name
+ * @param dataMap Data to be augmented with _ug_analyzed fields.
+ *
+ * @return builder With augmented content
+ * @throws IOException ON JSON generation error.
+ */
+ public static XContentBuilder prepareContentForIndexing( XContentBuilder builder,
+ String type, Map<String, Object> dataMap ) throws IOException {
+ return prepareContentForIndexing( null, builder, type, dataMap );
+ }
+
+
+ /**
+ * Duplicate each string field with a new field named {fieldname}_ug_analyzed
+ *
+ * @param name Name of JSON object to be emitted or null for none
+ * @param builder Builder to emit to
+ * @param type Type a.k.a. collection name
+ * @param dataList Data to be augmented with _ug_analyzed fields.
+ *
+ * @return builder With augmented content
+ * @throws IOException ON JSON generation error.
+ */
+ public static XContentBuilder prepareContentForIndexing( String name, XContentBuilder builder,
+ String type, List dataList ) throws IOException {
+
+ if ( name != null ) {
+ builder = builder.startArray( name );
+ } else {
+ builder = builder.startArray();
+ }
+ for ( Object o : dataList ) {
+
+ if ( o instanceof Map ) {
+ builder = prepareContentForIndexing( builder, type, (Map<String, Object>)o );
+
+ } else if ( o instanceof List ) {
+ builder = prepareContentForIndexing( builder, type, (List)o);
+
+ } else {
+ builder = builder.value( o );
+
+ }
+ }
+ builder = builder.endArray();
+ return builder;
+ }
+
+
+ /**
+ * Duplicate each string field with a new field named {fieldname}_ug_analyzed
+ *
+ * @param builder Builder to emit to
+ * @param type Type a.k.a. collection name
+ * @param dataList Data to be augmented with _ug_analyzed fields.
+ *
+ * @return builder With augmented content
+ * @throws IOException ON JSON generation error.
+ */
+ public static XContentBuilder prepareContentForIndexing( XContentBuilder builder,
+ String type, List dataList ) throws IOException {
+ return prepareContentForIndexing( null, builder, type, dataList );
+ }
+
+
+ /**
+ * Build mappings for data to be indexed. Setup String fields as not_analyzed and analyzed,
+ * where the analyzed field is named {name}_ug_analyzed
+ *
+ * @param builder Add JSON object to this builder.
+ * @param type ElasticSearch type of entity.
+ * @return Content builder with JSON for mapping.
+ *
+ * @throws java.io.IOException On JSON generation error.
+ */
+ public static XContentBuilder createDoubleStringIndexMapping(
+ XContentBuilder builder, String type ) throws IOException {
+
+ builder = builder
+ .startObject()
+ .startObject( type )
+ .startArray( "dynamic_templates" )
+
+ // any string with field name that ends with _ug_analyzed gets analyzed
+ .startObject()
+ .startObject( "template_1" )
+ .field( "match", "*_ug_analyzed")
+ .field( "match_mapping_type", "string")
+ .startObject( "mapping" )
+ .field( "type", "string" )
+ .field( "index", "analyzed" )
+ .endObject()
+ .endObject()
+ .endObject()
+
+ // all other strings are not analyzed
+ .startObject()
+ .startObject( "template_2" )
+ .field( "match", "*")
+ .field( "match_mapping_type", "string")
+ .startObject( "mapping" )
+ .field( "type", "string" )
+ .field( "index", "not_analyzed" )
+ .endObject()
+ .endObject()
+ .endObject()
+
+ .endArray()
+ .endObject()
+ .endObject();
+
+ return builder;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/EntityIndexImpl.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/EntityIndexImpl.java b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/EntityIndexImpl.java
new file mode 100644
index 0000000..338b68c
--- /dev/null
+++ b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/EntityIndexImpl.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. 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. For additional information regarding
+ * copyright in this work, please see the NOTICE file in the top level
+ * directory of this distribution.
+ */
+package org.apache.usergrid.persistence.index.impl;
+
+import org.apache.usergrid.persistence.collection.CollectionScope;
+import org.apache.usergrid.persistence.index.EntityIndex;
+import org.apache.usergrid.persistence.model.entity.Entity;
+import org.elasticsearch.client.Client;
+
+
+public class EntityIndexImpl implements EntityIndex {
+ private Client client;
+
+ public EntityIndexImpl( Client client, String index ) {
+ this.client = client;
+ }
+
+ public void index( Entity entity, CollectionScope scope ) {
+ }
+
+ public void deindex( Entity entity, CollectionScope scope ) {
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/IndexModule.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/IndexModule.java b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/IndexModule.java
new file mode 100644
index 0000000..ff9bbee
--- /dev/null
+++ b/stack/corepersistence/index/src/main/java/org/apache/usergrid/persistence/index/impl/IndexModule.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.google.inject.AbstractModule;
+import org.apache.usergrid.persistence.index.EntityIndex;
+
+/**
+ *
+ * @author ApigeeCorporation
+ */
+public class IndexModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind( EntityIndex.class ).to( EntityIndexImpl.class );
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/EntityIndexTest.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/EntityIndexTest.java b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/EntityIndexTest.java
new file mode 100644
index 0000000..6addad7
--- /dev/null
+++ b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/EntityIndexTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. 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. For additional information regarding
+ * copyright in this work, please see the NOTICE file in the top level
+ * directory of this distribution.
+ */
+package org.apache.usergrid.persistence.index;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.usergrid.persistence.collection.CollectionScope;
+import org.apache.usergrid.persistence.index.impl.EntityIndexImpl;
+import org.apache.usergrid.persistence.model.entity.Entity;
+import org.apache.usergrid.persistence.utils.ElasticSearchRule;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import static org.junit.Assert.assertEquals;
+import org.junit.Rule;
+import org.junit.Test;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class EntityIndexTest {
+ private static final Logger logger = LoggerFactory.getLogger( EntityIndexTest.class );
+
+ @Rule
+ public ElasticSearchRule elasticSearchRule = new ElasticSearchRule();
+
+ @Test
+ public void testIndex() throws IOException {
+
+ Client client = elasticSearchRule.getClient();
+ final CollectionScope scope = mock( CollectionScope.class );
+ when( scope.getName() ).thenReturn( "contacts" );
+ String index = RandomStringUtils.randomAlphanumeric(20 ).toLowerCase();
+ String type = scope.getName();
+
+ EntityIndex entityIndex = new EntityIndexImpl( client, index );
+ indexSampleData( entityIndex, scope );
+ testQueries( client, index, type );
+
+ client.close();
+ }
+
+ private void testQueries( Client client, String index, String type ) {
+
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "name", "Orr Byers" ), 1 );
+
+ // term query is exact match
+ testQuery( client, index, type,
+ QueryBuilders.termQuery("name", "Byers" ), 0 );
+
+ // match query allows partial match
+ testQuery( client, index, type,
+ QueryBuilders.matchQuery( "name_ug_analyzed", "Byers" ), 1 );
+
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "company", "Geologix" ), 1 );
+
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "gender", "female" ), 3 );
+
+ // query of nested object fields supported
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "contact.email", "orrbyers@bittor.com" ), 1 );
+ }
+
+
+ private void testQuery( Client client, String index, String type, QueryBuilder qb, int num ) {
+ SearchResponse sr = client.prepareSearch( index ).setTypes( type )
+ .setQuery( qb ).setFrom( 0 ).setSize( 20 ).execute().actionGet();
+ assertEquals( num, sr.getHits().getTotalHits() );
+ }
+
+
+ private void indexSampleData( EntityIndex index, CollectionScope scope ) throws IOException {
+
+ InputStream is = this.getClass().getResourceAsStream( "/sample.json" );
+ ObjectMapper mapper = new ObjectMapper();
+ List<Object> sampleJson = mapper.readValue( is, new TypeReference<List<Object>>() {} );
+
+ for ( Object o : sampleJson ) {
+ Map<String, Object> item = (Map<String, Object>)o;
+ String id = item.get( "id" ).toString();
+
+ Entity entity = new Entity();
+
+ // map item to Entity
+
+ index.index( entity, scope );
+
+ }
+ }
+
+ @Test
+ public void testRemoveIndex() {
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchTest.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchTest.java b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchTest.java
new file mode 100644
index 0000000..bcd73dc
--- /dev/null
+++ b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.Date;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.usergrid.persistence.utils.ElasticSearchRule;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.client.Client;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Rule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Elastic search experiments in the form of a test.
+ */
+public class ElasticSearchTest {
+ private static final Logger logger = LoggerFactory.getLogger( ElasticSearchTest.class );
+
+ @Rule
+ public ElasticSearchRule elasticSearchRule = new ElasticSearchRule();
+
+ @Test
+ public void testSimpleCrud() {
+
+ String indexName = RandomStringUtils.randomAlphanumeric(20 ).toLowerCase();
+ String collectionName = "testtype1";
+ String id = RandomStringUtils.randomAlphanumeric(20 );
+
+ Client client = elasticSearchRule.getClient();
+
+ Map<String, Object> json = new HashMap<String, Object>();
+ json.put( "user", "edward" );
+ json.put( "postDate", new Date() );
+ json.put( "message", "who knows if the moon’s a baloon" );
+
+ // create
+ IndexResponse indexResponse = client.prepareIndex(indexName, collectionName, id)
+ .setSource( json ).execute().actionGet();
+ assertTrue( indexResponse.isCreated() );
+
+ // retrieve
+ GetResponse getResponse = client.prepareGet( indexName, collectionName, id).get();
+ assertEquals( "edward", getResponse.getSource().get( "user" ) );
+
+ // update
+ json.put( "message", "If freckles were lovely, and day was night");
+ client.prepareUpdate( indexName, collectionName, id).setDoc( json ).execute().actionGet();
+ getResponse = client.prepareGet( indexName, collectionName, id).get();
+ assertEquals("If freckles were lovely, and day was night",
+ getResponse.getSource().get( "message" ) );
+
+ // update via script
+ client.prepareUpdate( indexName, collectionName, id)
+ .setScript( "ctx._source.message = \"coming out of a keen city in the sky\"" )
+ .execute().actionGet();
+ getResponse = client.prepareGet( indexName, collectionName, id).get();
+ assertEquals("coming out of a keen city in the sky",
+ getResponse.getSource().get( "message" ) );
+
+ // delete
+ client.prepareDelete(indexName, collectionName, id).execute().actionGet();
+ getResponse = client.prepareGet( indexName, collectionName, id).get();
+ assertFalse( getResponse.isExists() );
+
+ client.close();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtilsTest.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtilsTest.java b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtilsTest.java
new file mode 100644
index 0000000..2977db0
--- /dev/null
+++ b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/index/impl/ElasticSearchUtilsTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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 com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.usergrid.persistence.utils.ElasticSearchRule;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.AdminClient;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import static org.junit.Assert.assertEquals;
+import org.junit.Rule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Test indexing and searching using the methods from ElasticSearchUtils.
+ */
+public class ElasticSearchUtilsTest {
+ private static final Logger logger = LoggerFactory.getLogger( ElasticSearchUtilsTest.class );
+
+ @Rule
+ public ElasticSearchRule elasticSearchRule = new ElasticSearchRule();
+
+ @Test
+ public void testStringDoubleIndexDynamicMapping() throws IOException {
+
+ Client client = elasticSearchRule.getClient();
+
+ AdminClient admin = client.admin();
+
+ String index = RandomStringUtils.randomAlphanumeric(20).toLowerCase();
+ String type = "testtype";
+ admin.indices().prepareCreate( index ).execute().actionGet();
+
+ // add dynamic string-double index mapping
+ XContentBuilder mxcb = ElasticSearchUtils
+ .createDoubleStringIndexMapping( jsonBuilder(), type );
+ PutMappingResponse pmr = admin.indices().preparePutMapping(index)
+ .setType( type ).setSource( mxcb ).execute().actionGet();
+
+ indexSampleData( type, client, index );
+
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "name", "Orr Byers" ), 1 );
+
+ // term query is exact match
+ testQuery( client, index, type,
+ QueryBuilders.termQuery("name", "Byers" ), 0 );
+
+ // match query allows partial match
+ testQuery( client, index, type,
+ QueryBuilders.matchQuery( "name_ug_analyzed", "Byers" ), 1 );
+
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "company", "Geologix" ), 1 );
+
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "gender", "female" ), 3 );
+
+ // query of nested object fields supported
+ testQuery( client, index, type,
+ QueryBuilders.termQuery( "contact.email", "orrbyers@bittor.com" ), 1 );
+
+ client.close();
+ }
+
+ private void indexSampleData( String type, Client client, String index )
+ throws ElasticsearchException, IOException {
+
+ InputStream is = this.getClass().getResourceAsStream( "/sample.json" );
+ ObjectMapper mapper = new ObjectMapper();
+ List<Object> sampleJson = mapper.readValue( is, new TypeReference<List<Object>>() {} );
+
+ for ( Object o : sampleJson ) {
+ Map<String, Object> item = (Map<String, Object>)o;
+ String id = item.get( "id" ).toString();
+
+ XContentBuilder dxcb = ElasticSearchUtils
+ .prepareContentForIndexing( jsonBuilder(), type, item );
+
+ IndexResponse ir = client.prepareIndex(index, type, id )
+ .setSource( dxcb ).setRefresh( true ).execute().actionGet();
+ }
+ }
+
+ private void testQuery( Client client, String index, String type, QueryBuilder qb, int num ) {
+ SearchResponse sr = client.prepareSearch( index ).setTypes( type )
+ .setQuery( qb ).setFrom( 0 ).setSize( 20 ).execute().actionGet();
+ assertEquals( num, sr.getHits().getTotalHits() );
+ }
+
+ void log( GetResponse getResponse ) {
+ logger.info( "-------------------------------------------------------------------------" );
+ logger.info( "id: " + getResponse.getId() );
+ logger.info( "type: " + getResponse.getType() );
+ logger.info( "version: " + getResponse.getVersion() );
+ logger.info( "index: " + getResponse.getIndex() );
+ logger.info( "source: " + getResponse.getSourceAsString() );
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/utils/ElasticSearchRule.java
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/utils/ElasticSearchRule.java b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/utils/ElasticSearchRule.java
new file mode 100644
index 0000000..a573c40
--- /dev/null
+++ b/stack/corepersistence/index/src/test/java/org/apache/usergrid/persistence/utils/ElasticSearchRule.java
@@ -0,0 +1,66 @@
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. 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. For additional information regarding
+ * copyright in this work, please see the NOTICE file in the top level
+ * directory of this distribution.
+ */
+
+package org.apache.usergrid.persistence.utils;
+
+import org.apache.usergrid.persistence.collection.util.AvailablePortFinder;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.Node;
+import org.elasticsearch.node.NodeBuilder;
+import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class ElasticSearchRule extends ExternalResource {
+ private static final Logger log = LoggerFactory.getLogger( ElasticSearchRule.class );
+
+ private Client client;
+
+ @Override
+ protected void after() {
+ client.close();
+ }
+
+ @Override
+ protected void before() throws Throwable {
+
+ // use unique port and directory names so multiple instances of ES can run concurrently.
+ int port = AvailablePortFinder.getNextAvailable(2000);
+ Settings settings = ImmutableSettings.settingsBuilder()
+ .put("node.http.enabled", true)
+ .put("transport.tcp.port", port )
+ .put("path.logs","target/elasticsearch/logs_" + port )
+ .put("path.data","target/elasticsearch/data_" + port )
+ .put("gateway.type", "none" )
+ .put("index.store.type", "memory" )
+ .put("index.number_of_shards", 1 )
+ .put("index.number_of_replicas", 1).build();
+
+ Node node = NodeBuilder.nodeBuilder().local(true).settings(settings).node();
+ client = node.client();
+ }
+
+ public Client getClient() {
+ return client;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/test/resources/log4j.properties b/stack/corepersistence/index/src/test/resources/log4j.properties
new file mode 100644
index 0000000..62aaa1e
--- /dev/null
+++ b/stack/corepersistence/index/src/test/resources/log4j.properties
@@ -0,0 +1,13 @@
+log4j.rootLogger=INFO,stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p %c{3}.%M(%L)<%t>- %m%n
+
+log4j.logger.org.safehaus.chop.plugin=DEBUG
+log4j.logger.org.safehaus.guicyfig=ERROR
+log4j.logger.org.safehaus.chop.api.store.amazon=DEBUG
+log4j.logger.org.apache.http=ERROR
+log4j.logger.com.amazonaws.request=ERROR
+
+log4j.logger.org.apache.usergrid=DEBUG
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2fc1efea/stack/corepersistence/index/src/test/resources/sample.json
----------------------------------------------------------------------
diff --git a/stack/corepersistence/index/src/test/resources/sample.json b/stack/corepersistence/index/src/test/resources/sample.json
new file mode 100644
index 0000000..91f336c
--- /dev/null
+++ b/stack/corepersistence/index/src/test/resources/sample.json
@@ -0,0 +1,222 @@
+[
+ {
+ "id": 0,
+ "guid": "41a6482a-f8d5-4b28-a2f1-90b110b25167",
+ "isActive": false,
+ "balance": "$1,600.00",
+ "picture": "http://placehold.it/32x32",
+ "age": 33,
+ "name": "Molly Fitzpatrick",
+ "gender": "female",
+ "company": "Geologix",
+ "contact": {
+ "email": "mollyfitzpatrick@geologix.com",
+ "phone": "+1 (968) 560-2206",
+ "address": "895 Karweg Place, Lookingglass, Utah, 1989"
+ },
+ "about": "Eu nisi Lorem ad exercitation enim cupidatat culpa consectetur voluptate fugiat pariatur. Irure reprehenderit est quis aliquip fugiat enim. Elit ex qui cupidatat velit nisi cillum sunt dolor eu aute duis et. Culpa anim proident veniam occaecat eiusmod non adipisicing consequat fugiat eu irure laborum. Ea est sint deserunt laboris. Enim pariatur in ut magna ipsum fugiat ullamco dolor est voluptate veniam velit.\r\n",
+ "registered": "2013-02-02T16:18:24 +05:00",
+ "latitude": 10.237003,
+ "longitude": -86.677799,
+ "tags": [
+ "sint",
+ "laboris",
+ "laboris",
+ "enim",
+ "irure",
+ "dolore",
+ "non"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "Concepcion Michael"
+ },
+ {
+ "id": 1,
+ "name": "Harris Mcdonald"
+ },
+ {
+ "id": 2,
+ "name": "Marshall Cote"
+ }
+ ],
+ "randomArrayItem": "lemon"
+ },
+ {
+ "id": 1,
+ "guid": "13158ddc-c6fe-4dc6-84ad-1a20b2bc8494",
+ "isActive": false,
+ "balance": "$3,898.00",
+ "picture": "http://placehold.it/32x32",
+ "age": 40,
+ "name": "Tami Mccullough",
+ "gender": "female",
+ "company": "Eventex",
+ "contact": {
+ "email": "tamimccullough@eventex.com",
+ "phone": "+1 (875) 421-2839",
+ "address": "367 Orange Street, Wright, Michigan, 913"
+ },
+ "about": "Occaecat Lorem dolore in consectetur enim Lorem minim. Do eu aliqua aliquip consequat adipisicing Lorem qui reprehenderit do nisi nisi incididunt velit consequat. Aliqua anim dolor est cillum dolore et ullamco. Adipisicing dolore sunt sunt tempor nisi.\r\n",
+ "registered": "2013-06-10T03:06:40 +04:00",
+ "latitude": -38.108123,
+ "longitude": -128.352006,
+ "tags": [
+ "ipsum",
+ "magna",
+ "eu",
+ "anim",
+ "magna",
+ "nisi",
+ "nisi"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "Elva Whitfield"
+ },
+ {
+ "id": 1,
+ "name": "King Waters"
+ },
+ {
+ "id": 2,
+ "name": "Sandra Rodriguez"
+ }
+ ],
+ "randomArrayItem": "apple"
+ },
+ {
+ "id": 2,
+ "guid": "cc4541b1-d8e3-4f2d-924f-ad1f78ca994a",
+ "isActive": false,
+ "balance": "$1,715.00",
+ "picture": "http://placehold.it/32x32",
+ "age": 34,
+ "name": "Tina Dudley",
+ "gender": "female",
+ "company": "Reversus",
+ "contact": {
+ "email": "tinadudley@reversus.com",
+ "phone": "+1 (873) 549-2696",
+ "address": "772 Arion Place, Tibbie, Virginia, 8573"
+ },
+ "about": "Do laboris fugiat qui est mollit aliqua esse non duis ex non eiusmod consequat. Tempor eu et voluptate adipisicing. Nostrud veniam laborum officia eiusmod esse commodo culpa. Officia cupidatat officia consequat laboris duis cillum fugiat nostrud in.\r\n",
+ "registered": "1998-04-01T23:20:58 +05:00",
+ "latitude": 0.298655,
+ "longitude": -91.268836,
+ "tags": [
+ "enim",
+ "ut",
+ "elit",
+ "officia",
+ "ut",
+ "nostrud",
+ "ullamco"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "Haney Spears"
+ },
+ {
+ "id": 1,
+ "name": "Erna Trevino"
+ },
+ {
+ "id": 2,
+ "name": "Trisha Vaughan"
+ }
+ ],
+ "randomArrayItem": "apple"
+ },
+ {
+ "id": 3,
+ "guid": "2bb6de28-5d2d-4cf6-bde0-b3cf60bf6bb3",
+ "isActive": true,
+ "balance": "$2,016.00",
+ "picture": "http://placehold.it/32x32",
+ "age": 23,
+ "name": "Norman Hester",
+ "gender": "male",
+ "company": "Playce",
+ "contact": {
+ "email": "normanhester@playce.com",
+ "phone": "+1 (856) 431-2945",
+ "address": "406 Minna Street, Glenville, Connecticut, 3785"
+ },
+ "about": "Qui labore nostrud id occaecat culpa irure ad. Dolore non laborum occaecat occaecat ad sunt amet officia nisi in ullamco est officia. Lorem officia pariatur sit aute ea incididunt do. Consectetur id ad id mollit amet cupidatat dolor consectetur ipsum aute amet do. Reprehenderit consectetur amet esse fugiat et exercitation eiusmod. Labore nostrud sit quis sit qui id. Culpa pariatur aliquip sint magna irure occaecat officia magna sit proident pariatur.\r\n",
+ "registered": "1988-08-11T12:52:13 +04:00",
+ "latitude": -21.888779,
+ "longitude": 65.493829,
+ "tags": [
+ "Lorem",
+ "officia",
+ "nostrud",
+ "eu",
+ "do",
+ "eu",
+ "duis"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "Georgina Hess"
+ },
+ {
+ "id": 1,
+ "name": "Parsons Wilson"
+ },
+ {
+ "id": 2,
+ "name": "Harmon Wise"
+ }
+ ],
+ "randomArrayItem": "apple"
+ },
+ {
+ "id": 4,
+ "guid": "32fedc30-8e37-4b65-abb3-8faa829f4ad3",
+ "isActive": true,
+ "balance": "$2,642.00",
+ "picture": "http://placehold.it/32x32",
+ "age": 33,
+ "name": "Orr Byers",
+ "gender": "male",
+ "company": "Bittor",
+ "contact": {
+ "email": "orrbyers@bittor.com",
+ "phone": "+1 (997) 569-2415",
+ "address": "730 Lloyd Street, Marshall, Vermont, 3170"
+ },
+ "about": "Lorem culpa adipisicing consequat eu incididunt ullamco sunt id est. Aliquip labore ex sit culpa aliqua ex occaecat aliquip voluptate non labore culpa irure. Consequat tempor consequat laboris sint nulla commodo sit. Est velit dolor ut quis veniam aliqua amet dolore mollit labore. Magna commodo deserunt anim est velit laboris. Commodo sunt sit qui eu mollit eu esse excepteur ea sit incididunt.\r\n",
+ "registered": "1996-08-30T07:13:22 +04:00",
+ "latitude": 76.747258,
+ "longitude": -114.875056,
+ "tags": [
+ "ut",
+ "esse",
+ "reprehenderit",
+ "aute",
+ "laborum",
+ "esse",
+ "dolore"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "Spencer Hurley"
+ },
+ {
+ "id": 1,
+ "name": "Adrienne Griffith"
+ },
+ {
+ "id": 2,
+ "name": "Peck Haley"
+ }
+ ],
+ "randomArrayItem": "apple"
+ }
+]