You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2020/08/20 13:16:27 UTC

[unomi] branch master updated: UNOMI-358 Implement JSON property type (#183)

This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/master by this push:
     new 3515f98  UNOMI-358 Implement JSON property type (#183)
3515f98 is described below

commit 3515f98f0956e835bc9e6645457f1050ec14376e
Author: Pavel Milkevich <pa...@gmail.com>
AuthorDate: Thu Aug 20 16:13:29 2020 +0300

    UNOMI-358 Implement JSON property type (#183)
---
 .../CreateOrUpdateProfilePropertiesCommand.java    |   3 +-
 .../converters/UnomiToGraphQLConverter.java        |   1 +
 .../providers/CDPDefaultGraphQLProvider.java       |   2 +
 .../graphql/types/input/CDPPropertyInput.java      |  16 ++-
 .../types/input/property/CDPJsonPropertyInput.java |  59 +++++++++++
 .../types/output/property/CDPJsonPropertyType.java |  41 ++++++++
 .../main/resources/META-INF/cxs/values/json.json   |   3 +
 .../test/java/org/apache/unomi/itests/BaseIT.java  |   6 ++
 .../itests/graphql/GraphQLProfilePropertiesIT.java |  17 ++-
 .../create-or-update-profile-properties.json       |   9 ++
 .../profile/get-profile-with-new-property.json     |   2 +-
 .../ElasticSearchPersistenceServiceImpl.java       | 117 +++++++++++----------
 12 files changed, 217 insertions(+), 59 deletions(-)

diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateProfilePropertiesCommand.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateProfilePropertiesCommand.java
index 2c1b089..e59fa55 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateProfilePropertiesCommand.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateProfilePropertiesCommand.java
@@ -111,7 +111,8 @@ public class CreateOrUpdateProfilePropertiesCommand extends BaseCommand<Boolean>
                         prop.getDatePropertyTypeInput(),
                         prop.getBooleanPropertyTypeInput(),
                         prop.getGeoPointPropertyTypeInput(),
-                        prop.getSetPropertyTypeInput());
+                        prop.getSetPropertyTypeInput(),
+                        prop.getJsonPropertyTypeInput());
 
                 final List<BaseCDPPropertyInput> filteredProperties = properties.stream().filter(Objects::nonNull).collect(Collectors.toList());
 
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/converters/UnomiToGraphQLConverter.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/converters/UnomiToGraphQLConverter.java
index a711ed6..450b30d 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/converters/UnomiToGraphQLConverter.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/converters/UnomiToGraphQLConverter.java
@@ -62,6 +62,7 @@ public interface UnomiToGraphQLConverter {
                 graphQLType = Scalars.GraphQLFloat;
                 break;
             case "set":
+            case "json":
                 graphQLType = JSONFunction.JSON_SCALAR;
                 break;
             case "geopoint":
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/providers/CDPDefaultGraphQLProvider.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/providers/CDPDefaultGraphQLProvider.java
index 3aeb293..359487d 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/providers/CDPDefaultGraphQLProvider.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/providers/CDPDefaultGraphQLProvider.java
@@ -35,6 +35,7 @@ import org.apache.unomi.graphql.types.output.property.CDPFloatPropertyType;
 import org.apache.unomi.graphql.types.output.property.CDPGeoPointPropertyType;
 import org.apache.unomi.graphql.types.output.property.CDPIdentifierPropertyType;
 import org.apache.unomi.graphql.types.output.property.CDPIntPropertyType;
+import org.apache.unomi.graphql.types.output.property.CDPJsonPropertyType;
 import org.apache.unomi.graphql.types.output.property.CDPLongPropertyType;
 import org.apache.unomi.graphql.types.output.property.CDPSetPropertyType;
 import org.apache.unomi.graphql.types.output.property.CDPStringPropertyType;
@@ -66,6 +67,7 @@ public class CDPDefaultGraphQLProvider
         additionalTypes.add(CDPLongPropertyType.class);
         additionalTypes.add(CDPSetPropertyType.class);
         additionalTypes.add(CDPStringPropertyType.class);
+        additionalTypes.add(CDPJsonPropertyType.class);
 
         return additionalTypes;
     }
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPPropertyInput.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPPropertyInput.java
index 523e1c8..e2a3d81 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPPropertyInput.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPPropertyInput.java
@@ -25,6 +25,7 @@ import org.apache.unomi.graphql.types.input.property.CDPFloatPropertyInput;
 import org.apache.unomi.graphql.types.input.property.CDPGeoPointPropertyInput;
 import org.apache.unomi.graphql.types.input.property.CDPIdentifierPropertyInput;
 import org.apache.unomi.graphql.types.input.property.CDPIntPropertyInput;
+import org.apache.unomi.graphql.types.input.property.CDPJsonPropertyInput;
 import org.apache.unomi.graphql.types.input.property.CDPLongPropertyInput;
 import org.apache.unomi.graphql.types.input.property.CDPSetPropertyInput;
 import org.apache.unomi.graphql.types.input.property.CDPStringPropertyInput;
@@ -72,6 +73,10 @@ public class CDPPropertyInput {
     @GraphQLName("set")
     private CDPSetPropertyInput setPropertyTypeInput;
 
+    @GraphQLField
+    @GraphQLName("json")
+    private CDPJsonPropertyInput jsonPropertyTypeInput;
+
     public CDPPropertyInput(
             final @GraphQLName("identifier") CDPIdentifierPropertyInput identifierPropertyTypeInput,
             final @GraphQLName("string") CDPStringPropertyInput stringPropertyTypeInput,
@@ -81,7 +86,8 @@ public class CDPPropertyInput {
             final @GraphQLName("date") CDPDatePropertyInput datePropertyTypeInput,
             final @GraphQLName("boolean") CDPBooleanPropertyInput booleanPropertyTypeInput,
             final @GraphQLName("geopoint") CDPGeoPointPropertyInput geoPointPropertyTypeInput,
-            final @GraphQLName("set") CDPSetPropertyInput setPropertyTypeInput) {
+            final @GraphQLName("set") CDPSetPropertyInput setPropertyTypeInput,
+            final @GraphQLName("json") CDPJsonPropertyInput jsonPropertyTypeInput) {
         this.identifierPropertyTypeInput = identifierPropertyTypeInput;
         this.stringPropertyTypeInput = stringPropertyTypeInput;
         this.integerPropertyTypeInput = integerPropertyTypeInput;
@@ -91,6 +97,7 @@ public class CDPPropertyInput {
         this.booleanPropertyTypeInput = booleanPropertyTypeInput;
         this.geoPointPropertyTypeInput = geoPointPropertyTypeInput;
         this.setPropertyTypeInput = setPropertyTypeInput;
+        this.jsonPropertyTypeInput = jsonPropertyTypeInput;
     }
 
     public CDPIdentifierPropertyInput getIdentifierPropertyTypeInput() {
@@ -129,6 +136,10 @@ public class CDPPropertyInput {
         return setPropertyTypeInput;
     }
 
+    public CDPJsonPropertyInput getJsonPropertyTypeInput() {
+        return jsonPropertyTypeInput;
+    }
+
     public BaseCDPPropertyInput getProperty() {
         final List<BaseCDPPropertyInput> properties = Arrays.asList(
                 identifierPropertyTypeInput,
@@ -139,7 +150,8 @@ public class CDPPropertyInput {
                 datePropertyTypeInput,
                 booleanPropertyTypeInput,
                 geoPointPropertyTypeInput,
-                setPropertyTypeInput);
+                setPropertyTypeInput,
+                jsonPropertyTypeInput);
 
         return properties.stream().filter(Objects::nonNull).findFirst().orElse(null);
     }
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/property/CDPJsonPropertyInput.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/property/CDPJsonPropertyInput.java
new file mode 100644
index 0000000..e6954c1
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/property/CDPJsonPropertyInput.java
@@ -0,0 +1,59 @@
+/*
+ * 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.unomi.graphql.types.input.property;
+
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLName;
+import graphql.annotations.annotationTypes.GraphQLPrettify;
+import org.apache.unomi.api.PropertyType;
+
+import java.util.List;
+
+@GraphQLName("CDP_JsonPropertyInput")
+public class CDPJsonPropertyInput extends BaseCDPPropertyInput {
+
+    private Object defaultValue;
+
+    public CDPJsonPropertyInput(@GraphQLName("name") String name,
+                                @GraphQLName("minOccurrences") Integer minOccurrences,
+                                @GraphQLName("maxOccurrences") Integer maxOccurrences,
+                                @GraphQLName("tags") List<String> tags,
+                                @GraphQLName("defaultValue") Object defaultValue) {
+        super(name, minOccurrences, maxOccurrences, tags);
+        this.defaultValue = defaultValue;
+    }
+
+    @GraphQLField
+    @GraphQLPrettify
+    public Object getDefaultValue() {
+        return defaultValue;
+    }
+
+    @Override
+    public String getCDPPropertyType() {
+        return "json";
+    }
+
+    @Override
+    public void updateType(final PropertyType type) {
+        if (type == null) {
+            return;
+        }
+        super.updateType(type);
+        type.setDefaultValue(defaultValue != null ? defaultValue.toString() : null);
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/property/CDPJsonPropertyType.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/property/CDPJsonPropertyType.java
new file mode 100644
index 0000000..7d219fd
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/property/CDPJsonPropertyType.java
@@ -0,0 +1,41 @@
+/*
+ * 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.unomi.graphql.types.output.property;
+
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLName;
+import org.apache.unomi.api.PropertyType;
+import org.apache.unomi.graphql.types.output.CDPPropertyInterface;
+
+import static org.apache.unomi.graphql.types.output.property.CDPJsonPropertyType.TYPE_NAME;
+
+@GraphQLName(TYPE_NAME)
+public class CDPJsonPropertyType extends CDPPropertyType implements CDPPropertyInterface {
+
+    public static final String TYPE_NAME = "CDP_JsonProperty";
+
+    public static final String UNOMI_TYPE = "json";
+
+    public CDPJsonPropertyType(final PropertyType type) {
+        super(type);
+    }
+
+    @GraphQLField
+    public Object defaultValue() {
+        return type != null ? type.getDefaultValue() : null;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/resources/META-INF/cxs/values/json.json b/graphql/cxs-impl/src/main/resources/META-INF/cxs/values/json.json
new file mode 100644
index 0000000..8a95793
--- /dev/null
+++ b/graphql/cxs-impl/src/main/resources/META-INF/cxs/values/json.json
@@ -0,0 +1,3 @@
+{
+    "id": "json"
+}
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index bc2df79..ffa9be9 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -80,6 +80,12 @@ public abstract class BaseIT {
         refreshPersistence();
     }
 
+    protected void recreateIndex(final String itemType) {
+        if (persistenceService.removeIndex(itemType)) {
+            persistenceService.createIndex(itemType);
+        }
+    }
+
     protected void refreshPersistence() throws InterruptedException {
         persistenceService.refresh();
         Thread.sleep(1000);
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfilePropertiesIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfilePropertiesIT.java
index d77c35e..8cdd6ad 100644
--- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfilePropertiesIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfilePropertiesIT.java
@@ -16,13 +16,16 @@
  */
 package org.apache.unomi.itests.graphql;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.unomi.api.Consent;
 import org.apache.unomi.api.ConsentStatus;
 import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.PropertyType;
 import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.graphql.utils.DateUtils;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.ops4j.pax.exam.util.Filter;
 import org.slf4j.Logger;
@@ -30,6 +33,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import java.time.OffsetDateTime;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
@@ -43,6 +47,13 @@ public class GraphQLProfilePropertiesIT extends BaseGraphQLIT {
 
     private final static Logger LOGGER = LoggerFactory.getLogger(GraphQLProfilePropertiesIT.class);
 
+    @Override
+    @Before
+    public void setUp() throws InterruptedException {
+        super.setUp();
+        recreateIndex(PropertyType.ITEM_TYPE);
+    }
+
     @Test
     public void testCreateAndDeleteProfileProperty() throws Exception {
         try (CloseableHttpResponse response = post("graphql/profile/create-or-update-profile-properties.json")) {
@@ -54,9 +65,10 @@ public class GraphQLProfilePropertiesIT extends BaseGraphQLIT {
         keepTrying("Failed waiting for the creation of the property for profiles", () -> profileService.getPropertyType("testProperty"), Objects::nonNull, 1000, 100);
 
         final Profile profile = new Profile("profileId_createOrUpdateProfilePropertiesTest");
-        Map<String, String> testPropertyMap = new HashMap<>();
+        Map<String, Object> testPropertyMap = new HashMap<>();
         testPropertyMap.put("testStringProperty", "testStringPropertyValue");
         testPropertyMap.put("testLongProperty", String.valueOf(9007199254740991L));
+        testPropertyMap.put("testJsonProperty", Collections.singletonMap("value", 1));
         profile.setProperty("testProperty", testPropertyMap);
         profileService.save(profile);
 
@@ -65,13 +77,16 @@ public class GraphQLProfilePropertiesIT extends BaseGraphQLIT {
         Assert.assertNotNull(testProperty);
         Assert.assertEquals("testStringPropertyValue", testProperty.get("testStringProperty"));
         Assert.assertEquals(String.valueOf(9007199254740991L), testProperty.get("testLongProperty"));
+        Assert.assertEquals("{value=1}", StringUtils.join(testProperty.get("testJsonProperty")));
 
         try (CloseableHttpResponse response = post("graphql/profile/get-profile-with-new-property.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
+            LOGGER.info(StringUtils.join(context.getResponseAsMap()));
 
             Assert.assertNotNull(context.getValue("data.cdp.getProfile.testProperty"));
             Assert.assertEquals("testStringPropertyValue", context.getValue("data.cdp.getProfile.testProperty.testStringProperty"));
             Assert.assertEquals(9007199254740991L, (long) context.getValue("data.cdp.getProfile.testProperty.testLongProperty"));
+            Assert.assertEquals("{value=1}", StringUtils.join((HashMap)context.getValue("data.cdp.getProfile.testProperty.testJsonProperty")));
         }
 
         try (CloseableHttpResponse response = post("graphql/profile/delete-profile-properties.json")) {
diff --git a/itests/src/test/resources/graphql/profile/create-or-update-profile-properties.json b/itests/src/test/resources/graphql/profile/create-or-update-profile-properties.json
index 72d5923..8aa257d 100644
--- a/itests/src/test/resources/graphql/profile/create-or-update-profile-properties.json
+++ b/itests/src/test/resources/graphql/profile/create-or-update-profile-properties.json
@@ -16,6 +16,15 @@
                 "name": "testLongProperty",
                 "defaultValue": 9007199254740991
               }
+            },
+            {
+              "json": {
+                "name": "testJsonProperty",
+                "defaultValue": {
+                  "default": true,
+                  "value": 0
+                }
+              }
             }
           ]
         }
diff --git a/itests/src/test/resources/graphql/profile/get-profile-with-new-property.json b/itests/src/test/resources/graphql/profile/get-profile-with-new-property.json
index 50274cb..3a72720 100644
--- a/itests/src/test/resources/graphql/profile/get-profile-with-new-property.json
+++ b/itests/src/test/resources/graphql/profile/get-profile-with-new-property.json
@@ -9,5 +9,5 @@
     },
     "createIfMissing": true
   },
-  "query": "query getProfile($profileID: CDP_ProfileIDInput!, $createIfMissing: Boolean) {\n  cdp {\n    getProfile(profileID: $profileID, createIfMissing: $createIfMissing) {\n      testProperty {\n        testStringProperty\n        testLongProperty\n      }\n    }\n  }\n}\n"
+  "query": "query getProfile($profileID: CDP_ProfileIDInput!) {\n  cdp {\n    getProfile(profileID: $profileID, createIfMissing: false) {\n      cdp_profileIDs {\n        id\n      }\n      firstName\n      lastName\n      testProperty {\n        testStringProperty\n        testLongProperty\n        testJsonProperty\n      }\n    }\n  }\n}\n"
 }
diff --git a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
index 57a3a28..b519c51 100644
--- a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
+++ b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
@@ -405,11 +405,11 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
                 org.elasticsearch.Version clusterVersion = org.elasticsearch.Version.fromString(version.getNumber());
                 org.elasticsearch.Version minimalVersion = org.elasticsearch.Version.fromString(minimalElasticSearchVersion);
                 org.elasticsearch.Version maximalVersion = org.elasticsearch.Version.fromString(maximalElasticSearchVersion);
-                    if (clusterVersion.before(minimalVersion) ||
-                            clusterVersion.equals(maximalVersion) ||
-                            clusterVersion.after(maximalVersion)) {
-                        throw new Exception("ElasticSearch version is not within [" + minimalVersion + "," + maximalVersion + "), aborting startup !");
-                    }
+                if (clusterVersion.before(minimalVersion) ||
+                        clusterVersion.equals(maximalVersion) ||
+                        clusterVersion.after(maximalVersion)) {
+                    throw new Exception("ElasticSearch version is not within [" + minimalVersion + "," + maximalVersion + "), aborting startup !");
+                }
 
                 loadPredefinedMappings(bundleContext, false);
 
@@ -764,7 +764,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean save(final Item item, final boolean useBatching) {
-        Boolean result =  new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".saveItem") {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".saveItem") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String source = ESCustomObjectMapper.getObjectMapper().writeValueAsString(item);
@@ -1044,7 +1044,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
             protected Boolean execute(Object... args) throws IOException {
                 boolean executedSuccessfully = true;
                 for (String itemName : itemsMonthlyIndexed) {
-                    PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest("context-"+itemName+"-date-template")
+                    PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest("context-" + itemName + "-date-template")
                             .patterns(Collections.singletonList(getMonthlyIndexForQuery(itemName)))
                             .order(1)
                             .settings("{\n" +
@@ -1124,22 +1124,22 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     private void internalCreateIndex(String indexName, String mappingSource) throws IOException {
         CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
         createIndexRequest.settings("{\n" +
-                        "    \"index\" : {\n" +
-                        "        \"number_of_shards\" : " + numberOfShards + ",\n" +
-                        "        \"number_of_replicas\" : " + numberOfReplicas + ",\n" +
-                        "        \"mapping.total_fields.limit\" : " + indexMappingTotalFieldsLimit + ",\n" +
-                        "        \"max_docvalue_fields_search\" : " + indexMaxDocValueFieldsSearch + "\n" +
-                        "    },\n" +
-                        "    \"analysis\": {\n" +
-                        "      \"analyzer\": {\n" +
-                        "        \"folding\": {\n" +
-                        "          \"type\":\"custom\",\n" +
-                        "          \"tokenizer\": \"keyword\",\n" +
-                        "          \"filter\":  [ \"lowercase\", \"asciifolding\" ]\n" +
-                        "        }\n" +
-                        "      }\n" +
-                        "    }\n" +
-                        "}\n", XContentType.JSON);
+                "    \"index\" : {\n" +
+                "        \"number_of_shards\" : " + numberOfShards + ",\n" +
+                "        \"number_of_replicas\" : " + numberOfReplicas + ",\n" +
+                "        \"mapping.total_fields.limit\" : " + indexMappingTotalFieldsLimit + ",\n" +
+                "        \"max_docvalue_fields_search\" : " + indexMaxDocValueFieldsSearch + "\n" +
+                "    },\n" +
+                "    \"analysis\": {\n" +
+                "      \"analyzer\": {\n" +
+                "        \"folding\": {\n" +
+                "          \"type\":\"custom\",\n" +
+                "          \"tokenizer\": \"keyword\",\n" +
+                "          \"filter\":  [ \"lowercase\", \"asciifolding\" ]\n" +
+                "        }\n" +
+                "      }\n" +
+                "    }\n" +
+                "}\n", XContentType.JSON);
 
         createIndexRequest.mapping(mappingSource, XContentType.JSON);
         CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
@@ -1166,12 +1166,6 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public void setPropertyMapping(final PropertyType property, final String itemType) {
-        final String esType = convertValueTypeToESType(property.getValueTypeId());
-        if (esType == null) {
-            logger.warn("No predefined type found for property[" + property.getValueTypeId() + "], letting ES decide");
-            // we don't have a fixed type for that property so let ES decide it
-            return;
-        }
         try {
             Map<String, Map<String, Object>> mappings = getPropertiesMapping(itemType);
             if (mappings == null) {
@@ -1179,7 +1173,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
             }
             Map<String, Object> subMappings = mappings.computeIfAbsent("properties", k -> new HashMap<>());
             Map<String, Object> subSubMappings = (Map<String, Object>) subMappings.computeIfAbsent("properties", k -> new HashMap<>());
-            mergePropertiesMapping(subSubMappings, createPropertyMapping(property.getItemId(), esType));
+            mergePropertiesMapping(subSubMappings, createPropertyMapping(property));
 
             Map<String, Object> mappingsWrapper = new HashMap<>();
             mappingsWrapper.put("properties", mappings);
@@ -1191,27 +1185,41 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
         }
     }
 
-    private Map<String, Object> createPropertyMapping(final String fieldName, final String fieldType) {
+    private Map<String, Object> createPropertyMapping(final PropertyType property) {
+        final String esType = convertValueTypeToESType(property.getValueTypeId());
         final HashMap<String, Object> definition = new HashMap<>();
-        definition.put("type", fieldType);
-        if ("text".equals(fieldType)) {
-            definition.put("analyzer", "folding");
-            final Map<String, Object> fields = new HashMap<>();
-            final Map<String, Object> keywordField = new HashMap<>();
-            keywordField.put("type", "keyword");
-            keywordField.put("ignore_above", 256);
-            fields.put("keyword", keywordField);
-            definition.put("fields", fields);
+
+        if (esType == null) {
+            logger.warn("No predefined type found for property[" + property.getValueTypeId() + "], letting ES decide");
+            // we don't have a fixed type for that property so let ES decide it
+        } else {
+            definition.put("type", esType);
+            if ("text".equals(esType)) {
+                definition.put("analyzer", "folding");
+                final Map<String, Object> fields = new HashMap<>();
+                final Map<String, Object> keywordField = new HashMap<>();
+                keywordField.put("type", "keyword");
+                keywordField.put("ignore_above", 256);
+                fields.put("keyword", keywordField);
+                definition.put("fields", fields);
+            }
+        }
+
+        if ("set".equals(property.getValueTypeId())) {
+            Map<String, Object> childProperties = new HashMap<>();
+            property.getChildPropertyTypes().forEach(childType -> {
+                mergePropertiesMapping(childProperties, createPropertyMapping(childType));
+            });
+            definition.put("properties", childProperties);
         }
 
-        final HashMap<String, Object> map = new HashMap<>();
-        map.put(fieldName, definition);
-        return map;
+        return Collections.singletonMap(property.getItemId(), definition);
     }
 
     private String convertValueTypeToESType(String valueTypeId) {
         switch (valueTypeId) {
             case "set":
+            case "json":
                 return "object";
             case "boolean":
                 return "boolean";
@@ -1219,7 +1227,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
                 return "geo_point";
             case "integer":
                 return "integer";
-            case "long" :
+            case "long":
                 return "long";
             case "float":
                 return "float";
@@ -1264,8 +1272,8 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
                                     for (Map.Entry<String, Object> subentry : entry.getValue().entrySet()) {
                                         if (subResult.containsKey(subentry.getKey())
-                                            && subResult.get(subentry.getKey()) instanceof Map
-                                            && subentry.getValue() instanceof Map) {
+                                                && subResult.get(subentry.getKey()) instanceof Map
+                                                && subentry.getValue() instanceof Map) {
                                             mergePropertiesMapping((Map) subResult.get(subentry.getKey()), (Map) subentry.getValue());
                                         } else {
                                             subResult.put(subentry.getKey(), subentry.getValue());
@@ -1278,7 +1286,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
                         }
                     }
                 } catch (Throwable t) {
-                    throw new Exception("Cannot get mapping for itemType="+ itemType, t);
+                    throw new Exception("Cannot get mapping for itemType=" + itemType, t);
                 }
                 return result;
             }
@@ -1286,6 +1294,9 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     private void mergePropertiesMapping(Map<String, Object> result, Map<String, Object> entry) {
+        if (entry == null || entry.isEmpty()) {
+            return;
+        }
         for (Map.Entry<String, Object> subentry : entry.entrySet()) {
             if (result.containsKey(subentry.getKey())
                     && result.get(subentry.getKey()) instanceof Map
@@ -1691,7 +1702,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     private Map<String, Long> aggregateQuery(final Condition filter, final BaseAggregate aggregate, final String itemType,
-            final boolean optimizedQuery) {
+                                             final boolean optimizedQuery) {
         return new InClassLoaderExecute<Map<String, Long>>(metricsService, this.getClass().getName() + ".aggregateQuery") {
 
             @Override
@@ -1869,7 +1880,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     @Override
-    public <T extends Item> void refreshIndex(Class<T> clazz, Date dateHint){
+    public <T extends Item> void refreshIndex(Class<T> clazz, Date dateHint) {
         new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".refreshIndex") {
             protected Boolean execute(Object... args) {
                 try {
@@ -1885,7 +1896,6 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
 
-
     @Override
     public void purge(final Date date) {
         new InClassLoaderExecute<Object>(metricsService, this.getClass().getName() + ".purgeWithDate") {
@@ -2033,7 +2043,6 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
 
-
     private String getConfig(Map<String, String> settings, String key,
                              String defaultValue) {
         if (settings != null && settings.get(key) != null) {
@@ -2096,7 +2105,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
         if (!isCacheActiveForClass(className)) {
             return null;
         }
-        Map<String,T> itemCache = hazelcastInstance.getMap(className);
+        Map<String, T> itemCache = hazelcastInstance.getMap(className);
         return itemCache.get(itemId);
     }
 
@@ -2105,7 +2114,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
         if (!isCacheActiveForClass(className)) {
             return null;
         }
-        Map<String,T> itemCache = hazelcastInstance.getMap(className);
+        Map<String, T> itemCache = hazelcastInstance.getMap(className);
         return itemCache.put(itemId, item);
     }
 
@@ -2114,7 +2123,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
         if (!isCacheActiveForClass(className)) {
             return null;
         }
-        Map<String,T> itemCache = hazelcastInstance.getMap(className);
+        Map<String, T> itemCache = hazelcastInstance.getMap(className);
         return itemCache.remove(itemId);
     }