diff --git a/bom/pom.xml b/bom/pom.xml
index c97c5e7888..4926bdd1e0 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -17,11 +17,12 @@
UTF-8
-
+
21.5
21.0
3.2.2
- 3.0.7
+ 3.0.8
3.14.1
3.3.1
6.3.5
@@ -49,7 +50,8 @@
1.15.5
-
+
@@ -183,11 +185,11 @@
org.slf4j
- slf4j-api
+ slf4j-api
org.slf4j
- jcl-over-slf4j
+ jcl-over-slf4j
@@ -198,11 +200,11 @@
org.slf4j
- slf4j-api
+ slf4j-api
org.slf4j
- jcl-over-slf4j
+ jcl-over-slf4j
@@ -270,7 +272,7 @@
com.jayway.jsonpath
json-path
- 2.9.0
+ 2.10.0
org.skyscreamer
@@ -283,7 +285,7 @@
2.7.1
- org.testcontainers
+ org.testcontainers
testcontainers
${testcontainers.version}
@@ -381,6 +383,11 @@
vertx-hazelcast
${vertx.version}
+
+ io.vertx
+ vertx-json-schema
+ ${vertx.version}
+
io.vertx
vertx-auth-common
@@ -808,9 +815,9 @@
3.2.0
- jakarta.persistence
- jakarta.persistence-api
- 3.2.0
+ jakarta.persistence
+ jakarta.persistence-api
+ 3.2.0
com.squareup.inject
@@ -833,9 +840,9 @@
${hazelcast-hibernate.version}
- org.hibernate.orm
- hibernate-jcache
- ${hibernate.version}
+ org.hibernate.orm
+ hibernate-jcache
+ ${hibernate.version}
org.hibernate.orm
diff --git a/changelog/src/changelog/entries/2026/04/8741.GPU-2210.enhancement b/changelog/src/changelog/entries/2026/04/8741.GPU-2210.enhancement
new file mode 100644
index 0000000000..a1649553ad
--- /dev/null
+++ b/changelog/src/changelog/entries/2026/04/8741.GPU-2210.enhancement
@@ -0,0 +1 @@
+Core: A new field type, `JSON`, has been added. Having a string in its base, it provides such additional functionality, as JSON structure / schema verification, and extended JsonPath filtering in GraphQL requests.
\ No newline at end of file
diff --git a/common/src/main/resources/i18n/translations_de.properties b/common/src/main/resources/i18n/translations_de.properties
index a3e4950c55..766652b9c7 100644
--- a/common/src/main/resources/i18n/translations_de.properties
+++ b/common/src/main/resources/i18n/translations_de.properties
@@ -179,6 +179,7 @@ node_error_invalid_microschema_field_value=Der Wert für das Feld "{0}" darf nic
node_list_item_not_found=Der in der Liste angegebene Node mit der uuid {0} konnte nicht gefunden werden.
node_update_failed=Aktualisierung des Node "{0}" ist fehlgeschlagen.
node_error_invalid_string_field_value=Das String Feld "{0}" darf nicht mit dem Wert "{1}" befüllt werden.
+node_error_invalid_json_field_value=Das JSON Feld "{0}" darf nicht mit dem Wert "{1}" befüllt werden.
node_error_string_field_value_too_long=Das String Feld "{0}" kann höchstens {1} Zeichen enthalten, übergeben wurden {2} Zeichen.
node_conflicting_segmentfield_update=Das Segmentfeld "{0}" kann nicht mit dem Wert "{1}" befüllt werden, weil dieser Wert bereits verwendet wird.
node_conflicting_segmentfield_upload=Die Datei "{1}" kann nicht in das Segmentfeld "{0}" geladen werden, weil der Dateiname bereits verwendet wird.
diff --git a/common/src/main/resources/i18n/translations_en.properties b/common/src/main/resources/i18n/translations_en.properties
index 784e3f577d..a2ed07210b 100644
--- a/common/src/main/resources/i18n/translations_en.properties
+++ b/common/src/main/resources/i18n/translations_en.properties
@@ -178,6 +178,7 @@ node_error_invalid_schema_field_value=The field "{0}" is not allowed to use sche
node_list_item_not_found=Node within node list with uuid {0} could not be found.
node_update_failed=Update of node "{0}" failed.
node_error_invalid_string_field_value=The string field "{0}" must not be set to value "{1}".
+node_error_invalid_json_field_value=The JSON field "{0}" must not be set to value "{1}".
node_error_string_field_value_too_long=The string Feld "{0}" can only hold up to {1} characters, but {2} characters were given.
node_conflicting_segmentfield_update=The segment field "{0}" must not be set to value "{1}" because this value is already used.
node_conflicting_segmentfield_upload=The file "{1}" cannot be uploaded into the segment field "{0}" because the filename is already in use.
diff --git a/common/src/main/resources/i18n/translations_zh.properties b/common/src/main/resources/i18n/translations_zh.properties
index a78c3f5a21..7811ab55ca 100644
--- a/common/src/main/resources/i18n/translations_zh.properties
+++ b/common/src/main/resources/i18n/translations_zh.properties
@@ -169,6 +169,7 @@ node_error_invalid_microschema_field_value=字段“{0}”的值不得使用内
node_list_item_not_found=在uuid为{0}的节点列表中找不到节点。
node_update_failed=节点“{0}”更新失败。
node_error_invalid_string_field_value=字符串字段“{0}”不得设置为值“{1}”。
+node_error_invalid_json_field_value=JSON 字段“{0}”不能设置为值“{1}”。
node_conflicting_segmentfield_update=分节字段“{0}”不得设置为值“{1}”,因为该值已被使用。
node_conflicting_segmentfield_upload=文件“{1}”无法上传到分节字段“{0}”中,因为文件名已被使用。
node_conflicting_segmentfield_move=无法移动节点,因为分节字段“{0}”中的值“{1}”发生冲突。
diff --git a/connectors/common/src/main/java/com/gentics/mesh/database/connector/AbstractDatabaseConnector.java b/connectors/common/src/main/java/com/gentics/mesh/database/connector/AbstractDatabaseConnector.java
index 3cce115ba6..bb17f05dc0 100644
--- a/connectors/common/src/main/java/com/gentics/mesh/database/connector/AbstractDatabaseConnector.java
+++ b/connectors/common/src/main/java/com/gentics/mesh/database/connector/AbstractDatabaseConnector.java
@@ -37,6 +37,7 @@
import org.hibernate.query.Query;
import org.hibernate.query.internal.QueryOptionsImpl;
import org.hibernate.query.spi.Limit;
+import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import org.slf4j.Logger;
@@ -46,6 +47,7 @@
import com.gentics.mesh.core.data.project.HibProject;
import com.gentics.mesh.core.data.schema.HibFieldSchemaVersionElement;
import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.database.HibernateTx;
import com.gentics.mesh.etc.config.HibernateMeshOptions;
import com.gentics.mesh.hibernate.MeshTablePrefixStrategy;
@@ -385,6 +387,11 @@ public String getSqlTypeName(FieldTypes type, String uuidTypeName) {
TypeConfiguration typeConfig = getSessionMetadataIntegrator().getSessionFactoryImplementor().getTypeConfiguration();
Dialect dialect = getHibernateDialect();
switch (type) {
+ case JSON:
+ return typeConfig.getDdlTypeRegistry().getTypeName(
+ SqlTypes.JSON,
+ new Size(getSqlTypePrecision(type), getSqlTypeScale(type), getSqlTypeLength(type)),
+ typeConfig.getBasicTypeForJavaType(JsonContent.class));
case STRING:
case HTML:
return typeConfig.getDdlTypeRegistry().getTypeName(
diff --git a/connectors/common/src/main/resources/META-INF/liquibase/changelog-master.xml b/connectors/common/src/main/resources/META-INF/liquibase/changelog-master.xml
index 156e01d2e4..d854a653f2 100644
--- a/connectors/common/src/main/resources/META-INF/liquibase/changelog-master.xml
+++ b/connectors/common/src/main/resources/META-INF/liquibase/changelog-master.xml
@@ -25,4 +25,6 @@
+
+
diff --git a/connectors/common/src/main/resources/META-INF/liquibase/entries-3.3.x/1-features/changelog-gpu-2210.xml b/connectors/common/src/main/resources/META-INF/liquibase/entries-3.3.x/1-features/changelog-gpu-2210.xml
new file mode 100644
index 0000000000..b3331f3706
--- /dev/null
+++ b/connectors/common/src/main/resources/META-INF/liquibase/entries-3.3.x/1-features/changelog-gpu-2210.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/connectors/hsqldb/src/main/java/com/gentics/mesh/database/connector/HSQLDBConnector.java b/connectors/hsqldb/src/main/java/com/gentics/mesh/database/connector/HSQLDBConnector.java
index c618e45ce6..65341deb5f 100644
--- a/connectors/hsqldb/src/main/java/com/gentics/mesh/database/connector/HSQLDBConnector.java
+++ b/connectors/hsqldb/src/main/java/com/gentics/mesh/database/connector/HSQLDBConnector.java
@@ -3,10 +3,10 @@
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
-import org.hibernate.dialect.HSQLDialect;
import org.hsqldb.jdbcDriver;
import com.gentics.mesh.etc.config.HibernateMeshOptions;
+import com.gentics.mesh.hibernate.dialect.HSQLJsonAwareDialect;
/**
* HSQLDB Mesh database connector
@@ -47,8 +47,7 @@ protected String getDefaultDriverClassName() {
@Override
protected String getDefaultDialectClassName() {
- // TODO Auto-generated method stub
- return HSQLDialect.class.getCanonicalName();
+ return HSQLJsonAwareDialect.class.getCanonicalName();
}
@Override
diff --git a/connectors/hsqldb/src/main/java/com/gentics/mesh/hibernate/dialect/HSQLJsonAwareDialect.java b/connectors/hsqldb/src/main/java/com/gentics/mesh/hibernate/dialect/HSQLJsonAwareDialect.java
new file mode 100644
index 0000000000..1d9ac22988
--- /dev/null
+++ b/connectors/hsqldb/src/main/java/com/gentics/mesh/hibernate/dialect/HSQLJsonAwareDialect.java
@@ -0,0 +1,43 @@
+package com.gentics.mesh.hibernate.dialect;
+
+import static org.hibernate.type.SqlTypes.JSON;
+
+import java.sql.Types;
+
+import org.hibernate.boot.model.TypeContributions;
+import org.hibernate.dialect.HSQLDialect;
+import org.hibernate.service.ServiceRegistry;
+import org.hibernate.type.SqlTypes;
+import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
+import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
+import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
+import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
+
+import com.gentics.mesh.database.connector.QueryUtils;
+
+/**
+ * JSON object supporting (via VARCHAR) dialect of HSQLDB.
+ */
+public class HSQLJsonAwareDialect extends HSQLDialect {
+
+ @Override
+ protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
+ super.registerColumnTypes( typeContributions, serviceRegistry );
+ final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
+ ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "varchar(" + QueryUtils.DEFAULT_STRING_LENGTH + ")", this ) );
+ }
+
+ @Override
+ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
+ final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
+ jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
+ super.contributeTypes( typeContributions, serviceRegistry );
+ }
+
+ @Override
+ public boolean equivalentTypes(int typeCode1, int typeCode2) {
+ return typeCode1 == Types.LONGVARCHAR && typeCode2 == SqlTypes.JSON
+ || typeCode1 == SqlTypes.JSON && typeCode2 == Types.LONGVARCHAR
+ || super.equivalentTypes( typeCode1, typeCode2 );
+ }
+}
diff --git a/core/src/main/java/com/gentics/mesh/core/endpoint/webrootfield/WebRootFieldHandler.java b/core/src/main/java/com/gentics/mesh/core/endpoint/webrootfield/WebRootFieldHandler.java
index 03d31ab656..3cf6a18e80 100644
--- a/core/src/main/java/com/gentics/mesh/core/endpoint/webrootfield/WebRootFieldHandler.java
+++ b/core/src/main/java/com/gentics/mesh/core/endpoint/webrootfield/WebRootFieldHandler.java
@@ -145,7 +145,7 @@ public void handleGetPathField(RoutingContext rc) {
// Check if field is JSON, to set corresponding HTTP header value
String contentType = rc.response().headers().get(HttpHeaders.CONTENT_TYPE);
ac.send(
- HttpConstants.APPLICATION_JSON_UTF8.equals(contentType) ? field.toJson(ac.isMinify(options.getHttpServerOptions())) : field.toString(),
+ (HttpConstants.APPLICATION_JSON_UTF8.equals(contentType) && FieldTypes.valueByName(field.getType()) != FieldTypes.JSON) ? field.toJson(ac.isMinify(options.getHttpServerOptions())) : field.toString(),
HttpResponseStatus.valueOf(rc.response().getStatusCode()),
contentType);
}
diff --git a/doc/src/main/hugo/content/docs/building-blocks.asciidoc b/doc/src/main/hugo/content/docs/building-blocks.asciidoc
index 09aa1918c5..f7276fb72b 100644
--- a/doc/src/main/hugo/content/docs/building-blocks.asciidoc
+++ b/doc/src/main/hugo/content/docs/building-blocks.asciidoc
@@ -143,7 +143,7 @@ include::content/docs/api/response{apiLatest}/\{project\}/nodes/\{nodeUuid\}/200
== Schema
-Typically, each project will require a set of different content types. Together they can be considered the content model of your project. Staying with the example of a product catalogue website: a product, product category, product image, and product manual each represent a separate content type. In Gentics Mesh, a *schema* is used to define such content types in terms of a couple of standard fields (e.g. ```uuid```, ```name```, ```description```, ```version```, etc.) and an arbitrary number of custom fields. Available <> are ```string```, ```number```, ```HTML```, ```date```, ```binary```, ```list```, ```node```, ```micronode```, ```boolean```. You can think of a schema as a blueprint for new content items.
+Typically, each project will require a set of different content types. Together they can be considered the content model of your project. Staying with the example of a product catalogue website: a product, product category, product image, and product manual each represent a separate content type. In Gentics Mesh, a *schema* is used to define such content types in terms of a couple of standard fields (e.g. ```uuid```, ```name```, ```description```, ```version```, etc.) and an arbitrary number of custom fields. Available <> are ```string```, ```number```, ```HTML```, ```date```, ```binary```, ```list```, ```node```, ```micronode```, ```boolean```, ```json```. You can think of a schema as a blueprint for new content items.
TIP: Using the ```container``` property, a schema can be configured to allow for hierarchically structuring nodes. Nodes based on a such a schema may contain child nodes. This is the basis for building link:{{< relref "features.asciidoc" >}}#_contenttrees[content trees] in Gentics Mesh and leveraging the power of automatic link:{{< relref "features.asciidoc" >}}#_navigation[navigation menus], link:{{< relref "features.asciidoc" >}}#_breadcrumbs[breadcrumbs] and link:{{< relref "features.asciidoc" >}}#_prettyurls[pretty URLs].
@@ -287,6 +287,12 @@ The ```html``` field type stores HTML data.
The ```required``` property indicates if the field is mandatory or not.
+===== JSON
+The ```json``` field type stores JSON data.
+The ```required``` property indicates if the field is mandatory or not.
+The optional ```allow``` property acts as a whitelist for allowed field schemas.
+
+
===== Micronode
A ```micronode``` field type stores a single micronode. A micronode is similar to a node. Typically they do not exist on their own but are tied to their (parent) node, e.g. a caption to be used in a image node. For a detailed description see our definition of <>.
The ```required``` property indicates if the field is mandatory or not.
@@ -302,7 +308,7 @@ A ```node``` field type must have an ```allow``` property, which acts as a white
===== List
A ```list``` field type allows for specifying a list with elements on the basis of other field types and thus represents a powerful mechanism for building your content model:
-(1) Within a node you can have simple lists of arbitrary length. The ```listType``` property then has to be of type ```string```, ```number```, ```date```, ```boolean```, or ```HTML```. E.g. handling your recipe nodes of your food blog will be a breeze with string-typed lists for ingredients.
+(1) Within a node you can have simple lists of arbitrary length. The ```listType``` property then has to be of type ```string```, ```number```, ```date```, ```boolean```, ```json```, or ```HTML```. E.g. handling your recipe nodes of your food blog will be a breeze with string-typed lists for ingredients.
(2) You can unleash the power of micronodes, by specifying a list with the ```listType``` property set to ```micronode```, and the ```allow``` property set to the allowed microschemas. For example, besides having title, teaser, date and author fields, your blog post schema could define a content field of type list allowing to insert any of your microschemas (e.g. YouTube Video, Image, Text, Galleries, Google Maps, etc.).
@@ -399,9 +405,9 @@ Microschemas share the same properties as schemas except for the properties ```d
=== Schema Field Types
-In comparison to nodes, micronodes can be built with schema field types ```String```, ```Number```, ```Date```, ```Boolean```, ```HTML```, ```Node```, and ```Lists```.
+In comparison to nodes, micronodes can be built with schema field types ```String```, ```Number```, ```Date```, ```Boolean```, ```JSON```, ```HTML```, ```Node```, and ```Lists```.
-Fields of type ```list``` in micronodes can be of type: ```String```, ```Number```, ```Date```, ```Boolean```, ```HTML```, and ```Node```.
+Fields of type ```list``` in micronodes can be of type: ```String```, ```Number```, ```Date```, ```Boolean```,```JSON``` , ```HTML```, and ```Node```.
== Tag
diff --git a/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerMappingProviderImpl.java b/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerMappingProviderImpl.java
index 3664bde3af..702f72fa5d 100644
--- a/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerMappingProviderImpl.java
+++ b/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerMappingProviderImpl.java
@@ -192,6 +192,9 @@ public Optional getFieldMapping(FieldSchema fieldSchema, HibBranch b
case BOOLEAN:
addBooleanFieldMapping(fieldInfo, customIndexOptions);
break;
+ case JSON:
+ addJsonFieldMapping(fieldInfo, customIndexOptions);
+ break;
case DATE:
addDataFieldMapping(fieldInfo, customIndexOptions);
break;
@@ -233,6 +236,14 @@ private void addBooleanFieldMapping(JsonObject fieldInfo, JsonObject customIndex
}
}
+ private void addJsonFieldMapping(JsonObject fieldInfo, JsonObject customIndexOptions) {
+ if (isStrictMode()) {
+ fieldInfo.mergeIn(customIndexOptions);
+ } else {
+ fieldInfo.put("type", OBJECT);
+ }
+ }
+
private void addDataFieldMapping(JsonObject fieldInfo, JsonObject customIndexOptions) {
if (isStrictMode()) {
fieldInfo.mergeIn(customIndexOptions);
@@ -282,21 +293,21 @@ private void addListFieldMapping(JsonObject fieldInfo, HibBranch branch, ListFie
} else {
ListFieldSchemaImpl listFieldSchema = (ListFieldSchemaImpl) fieldSchema;
String type = listFieldSchema.getListType();
- switch (type) {
- case "node":
+ switch (FieldTypes.valueByName(type)) {
+ case NODE:
fieldInfo.put("type", KEYWORD);
fieldInfo.put("index", INDEX_VALUE);
break;
- case "date":
+ case DATE:
fieldInfo.put("type", DATE);
break;
- case "number":
+ case NUMBER:
fieldInfo.put("type", DOUBLE);
break;
- case "boolean":
+ case BOOLEAN:
fieldInfo.put("type", BOOLEAN);
break;
- case "micronode":
+ case MICRONODE:
fieldInfo.put("type", NESTED);
// All allowed microschemas
@@ -307,13 +318,19 @@ private void addListFieldMapping(JsonObject fieldInfo, HibBranch branch, ListFie
// fieldProps.put(field.getName(), fieldInfo);
break;
- case "string":
- case "html":
+ case STRING:
+ case HTML:
fieldInfo.put("type", TEXT);
if (customIndexOptions != null) {
fieldInfo.put("fields", customIndexOptions);
}
break;
+ case JSON:
+ fieldInfo.put("type", OBJECT);
+ if (customIndexOptions != null) {
+ fieldInfo.put("fields", customIndexOptions);
+ }
+ break;
default:
throw new RuntimeException("Unknown mapping type for the field type {" + type + "}.");
}
diff --git a/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerTransformer.java b/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerTransformer.java
index 18dab69b6a..523afe4e16 100644
--- a/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerTransformer.java
+++ b/elasticsearch/src/main/java/com/gentics/mesh/search/index/node/NodeContainerTransformer.java
@@ -41,11 +41,13 @@
import com.gentics.mesh.core.data.node.field.HibDateField;
import com.gentics.mesh.core.data.node.field.HibHtmlField;
import com.gentics.mesh.core.data.node.field.HibImageDataField;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
import com.gentics.mesh.core.data.node.field.HibNumberField;
import com.gentics.mesh.core.data.node.field.HibStringField;
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -62,6 +64,7 @@
import com.gentics.mesh.core.db.Tx;
import com.gentics.mesh.core.rest.common.ContainerType;
import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.node.field.binary.BinaryMetadata;
import com.gentics.mesh.core.rest.node.field.binary.Location;
import com.gentics.mesh.core.rest.schema.BinaryExtractOptions;
@@ -236,6 +239,12 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(name, booleanField.getBoolean());
}
break;
+ case JSON:
+ HibJsonField jsonField = container.getJson(name);
+ if (jsonField != null) {
+ fieldsMap.put(name, jsonField.getJson());
+ }
+ break;
case DATE:
HibDateField dateField = container.getDate(name);
if (dateField != null) {
@@ -262,8 +271,8 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
case LIST:
if (fieldSchema instanceof ListFieldSchemaImpl) {
ListFieldSchemaImpl listFieldSchema = (ListFieldSchemaImpl) fieldSchema;
- switch (listFieldSchema.getListType()) {
- case "node":
+ switch (FieldTypes.valueByName(listFieldSchema.getListType())) {
+ case NODE:
HibNodeFieldList graphNodeList = container.getNodeList(fieldSchema.getName());
if (graphNodeList != null) {
List nodeItems = new ArrayList<>();
@@ -276,7 +285,7 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(fieldSchema.getName(), nodeItems);
}
break;
- case "date":
+ case DATE:
HibDateFieldList graphDateList = container.getDateList(fieldSchema.getName());
if (graphDateList != null) {
List dateItems = new ArrayList<>();
@@ -286,7 +295,7 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(fieldSchema.getName(), dateItems);
}
break;
- case "number":
+ case NUMBER:
HibNumberFieldList graphNumberList = container.getNumberList(fieldSchema.getName());
if (graphNumberList != null) {
List numberItems = new ArrayList<>();
@@ -298,7 +307,7 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(fieldSchema.getName(), numberItems);
}
break;
- case "boolean":
+ case BOOLEAN:
HibBooleanFieldList graphBooleanList = container.getBooleanList(fieldSchema.getName());
if (graphBooleanList != null) {
List booleanItems = new ArrayList<>();
@@ -308,7 +317,7 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(fieldSchema.getName(), booleanItems);
}
break;
- case "micronode":
+ case MICRONODE:
HibMicronodeFieldList micronodeGraphFieldList = container.getMicronodeList(fieldSchema.getName());
if (micronodeGraphFieldList != null) {
// Add list of micronode objects
@@ -323,7 +332,7 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
}).toList().blockingGet());
}
break;
- case "string":
+ case STRING:
HibStringFieldList graphStringList = container.getStringList(fieldSchema.getName());
if (graphStringList != null) {
List stringItems = new ArrayList<>();
@@ -337,7 +346,7 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(fieldSchema.getName(), stringItems);
}
break;
- case "html":
+ case HTML:
HibHtmlFieldList graphHtmlList = container.getHTMLList(fieldSchema.getName());
if (graphHtmlList != null) {
List htmlItems = new ArrayList<>();
@@ -356,6 +365,16 @@ public void addFields(JsonObject document, String fieldKey, HibFieldContainer co
fieldsMap.put(fieldSchema.getName(), htmlItems);
}
break;
+ case JSON:
+ HibJsonFieldList sqlJsonList = container.getJsonList(fieldSchema.getName());
+ if (sqlJsonList != null) {
+ List jsonItems = new ArrayList<>();
+ for (HibJsonField listItem : sqlJsonList.getList()) {
+ jsonItems.add(listItem.getJson());
+ }
+ fieldsMap.put(fieldSchema.getName(), jsonItems);
+ }
+ break;
default:
log.error("Unknown list type {" + listFieldSchema.getListType() + "}");
break;
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/HibFieldContainer.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/HibFieldContainer.java
index 6b704f7ab5..3e763962ec 100644
--- a/mdm/api/src/main/java/com/gentics/mesh/core/data/HibFieldContainer.java
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/HibFieldContainer.java
@@ -22,11 +22,13 @@
import com.gentics.mesh.core.data.node.field.HibBooleanField;
import com.gentics.mesh.core.data.node.field.HibDateField;
import com.gentics.mesh.core.data.node.field.HibHtmlField;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
import com.gentics.mesh.core.data.node.field.HibNumberField;
import com.gentics.mesh.core.data.node.field.HibStringField;
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -250,6 +252,14 @@ default Stream extends HibNodeFieldContainer> getContents() {
*/
HibStringField getString(String key);
+ /**
+ * Return the JSON object field for the given key.
+ *
+ * @param key
+ * @return
+ */
+ HibJsonField getJson(String key);
+
/**
* Return the binary field for the given key.
*
@@ -354,6 +364,14 @@ default Stream extends HibNodeFieldContainer> getContents() {
*/
HibNumberFieldList getNumberList(String fieldKey);
+ /**
+ * Return JSOB object list.
+ *
+ * @param fieldKey
+ * @return
+ */
+ HibJsonFieldList getJsonList(String fieldKey);
+
/**
* Return node list.
*
@@ -426,6 +444,14 @@ default Stream extends HibNodeFieldContainer> getContents() {
*/
HibNumberFieldList createNumberList(String fieldKey);
+ /**
+ * Create a new JSON object list.
+ *
+ * @param fieldKey
+ * @return
+ */
+ HibJsonFieldList createJsonList(String fieldKey);
+
/**
* Create a new html list.
*
@@ -492,6 +518,14 @@ default Stream extends HibNodeFieldContainer> getContents() {
*/
HibDateField createDate(String key);
+ /**
+ * Create a new JSON object field.
+ *
+ * @param key
+ * @return
+ */
+ HibJsonField createJson(String key);
+
/**
* Create a new node field.
*
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/dao/ContentDao.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/dao/ContentDao.java
index 712626f4c7..2358f3e585 100644
--- a/mdm/api/src/main/java/com/gentics/mesh/core/data/dao/ContentDao.java
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/dao/ContentDao.java
@@ -37,6 +37,7 @@
import com.gentics.mesh.core.rest.error.Errors;
import com.gentics.mesh.core.rest.event.node.NodeMeshEventModel;
import com.gentics.mesh.core.rest.node.FieldMap;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.node.field.NodeFieldListItem;
import com.gentics.mesh.core.rest.node.version.VersionInfo;
import com.gentics.mesh.core.rest.schema.SchemaModel;
@@ -1102,6 +1103,13 @@ default Result extends HibNodeFieldContainerEdge> getFieldEdges(HibNode fieldN
*/
boolean supportsPrefetchingListFieldValues();
+ /**
+ * Get the JSON object list field values for the given list UUIDs
+ * @param listUuids list UUIDs
+ * @return map of list UUIDs to lists of JSON values
+ */
+ Map> getJsonListFieldValues(List listUuids);
+
/**
* Get the boolean list field values for the given list UUIDs
* @param listUuids list UUIDs
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/node/field/HibJsonField.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/node/field/HibJsonField.java
new file mode 100644
index 0000000000..bd3196f9b6
--- /dev/null
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/node/field/HibJsonField.java
@@ -0,0 +1,55 @@
+package com.gentics.mesh.core.data.node.field;
+
+import com.gentics.mesh.core.data.HibField;
+import com.gentics.mesh.core.data.HibFieldContainer;
+import com.gentics.mesh.core.data.node.field.nesting.HibListableField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
+import com.gentics.mesh.handler.ActionContext;
+import com.gentics.mesh.util.CompareUtils;
+
+public interface HibJsonField extends HibListableField, HibBasicField {
+
+ /**
+ * Set the JSON object within the field.
+ *
+ * @param json
+ */
+ void setJson(JsonContent json);
+
+ /**
+ * Return the JSON object which is stored in the field.
+ *
+ * @return
+ */
+ JsonContent getJson();
+
+ @Override
+ default HibField cloneTo(HibFieldContainer container) {
+ HibJsonField clone = container.createJson(getFieldKey());
+ clone.setJson(getJson());
+ return clone;
+ }
+
+ @Override
+ default JsonField transformToRest(ActionContext ac) {
+ JsonField jsonField = new JsonFieldImpl();
+ jsonField.setJson(getJson());
+ return jsonField;
+ }
+
+ default boolean jsonEquals(Object obj) {
+ if (obj instanceof HibJsonField) {
+ JsonContent jsonA = getJson();
+ JsonContent jsonB = ((HibJsonField) obj).getJson();
+ return CompareUtils.equals(jsonA, jsonB);
+ }
+ if (obj instanceof JsonField) {
+ JsonContent jsonA = getJson();
+ JsonContent jsonB = ((JsonField) obj).getJson();
+ return CompareUtils.equals(jsonA, jsonB);
+ }
+ return false;
+ }
+}
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/node/field/list/HibJsonFieldList.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/node/field/list/HibJsonFieldList.java
new file mode 100644
index 0000000000..2fec6fd62e
--- /dev/null
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/node/field/list/HibJsonFieldList.java
@@ -0,0 +1,70 @@
+package com.gentics.mesh.core.data.node.field.list;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import com.gentics.mesh.context.InternalActionContext;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
+import com.gentics.mesh.core.data.node.field.nesting.HibMicroschemaListableField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
+import com.gentics.mesh.json.JsonUtil;
+import com.gentics.mesh.util.CompareUtils;
+
+public interface HibJsonFieldList extends HibMicroschemaListableField, HibListField {
+
+ String TYPE = "json";
+
+ /**
+ * Add another sql field to the list of sql fields.
+ *
+ * @param json
+ * Json to be set for the new field
+ * @return
+ */
+ HibJsonField createJson(JsonContent json);
+
+ /**
+ * Create an ordered list of json fields from values, adding all to the list.
+ *
+ * @param jsons
+ */
+ default void createJsons(List jsons) {
+ jsons.stream().forEach(this::createJson);
+ }
+
+ /**
+ * Return the json field at the given index of the list.
+ *
+ * @param index
+ * @return
+ */
+ HibJsonField getJson(int index);
+
+ @Override
+ default JsonFieldListImpl transformToRest(InternalActionContext ac, String fieldKey, List languageTags, int level) {
+ JsonFieldListImpl restModel = new JsonFieldListImpl();
+ for (HibJsonField item : getList()) {
+ restModel.add(item.getJson());
+ }
+ return restModel;
+ }
+
+ @Override
+ default List getValues() {
+ return getList().stream().map(HibJsonField::getJson).collect(Collectors.toList());
+ }
+
+ @Override
+ default boolean listEquals(Object obj) {
+ if (obj instanceof JsonFieldListImpl) {
+ JsonFieldListImpl restField = (JsonFieldListImpl) obj;
+ List restList = restField.getItems();
+ List extends HibJsonField> sqlList = getList();
+ List valueList = sqlList.stream().map(e -> e.getJson()).collect(Collectors.toList());
+ return CompareUtils.equals(restList, valueList, Optional.of((a, b) -> JsonUtil.COMPARATOR.compare(a, b) == 0));
+ }
+ return HibListField.super.listEquals(obj);
+ }
+}
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/node/handler/TypeConverter.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/node/handler/TypeConverter.java
index 64d16262d0..80f648a7ff 100644
--- a/mdm/api/src/main/java/com/gentics/mesh/core/data/node/handler/TypeConverter.java
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/node/handler/TypeConverter.java
@@ -19,6 +19,10 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.node.field.ListField;
import com.gentics.mesh.core.rest.node.field.MicronodeField;
import com.gentics.mesh.core.rest.node.field.NodeField;
@@ -31,15 +35,13 @@
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.MicronodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListItemImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.StringFieldListImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
* Type converter for the script engine used by the node migration handler.
*/
@@ -84,6 +86,17 @@ public StringFieldListImpl toStringList(Object value) {
return listField(StringFieldListImpl::new, this::toString, value);
}
+ /**
+ * Convert the given value to a string list
+ *
+ * @param value
+ * Value to be converted
+ * @return String array
+ */
+ public JsonFieldListImpl toJsonList(Object value) {
+ return listField(JsonFieldListImpl::new, this::toJsonContent, value);
+ }
+
/**
* Convert the given value to an HTML list
*
@@ -118,6 +131,29 @@ public Boolean toBoolean(Object value) {
}
}
+ /**
+ * Convert the given value to a boolean.
+ *
+ * @param value
+ * Value to be converted
+ * @return Boolean value
+ */
+ public JsonContent toJsonContent(Object value) {
+ value = firstIfList(value);
+
+ if (value == null) {
+ return null;
+ }
+ try {
+ return JsonContent.fromString(value.toString());
+ } catch (Exception e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Could not convert to JsonObject {" + value.toString() + "}", e);
+ }
+ }
+ return null;
+ }
+
/**
* Convert the given value to a boolean array.
*
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibAddFieldChange.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibAddFieldChange.java
index d733716778..235497be83 100644
--- a/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibAddFieldChange.java
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibAddFieldChange.java
@@ -1,27 +1,33 @@
package com.gentics.mesh.core.data.schema;
import static com.gentics.mesh.core.rest.error.Errors.error;
-import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel.*;
+import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel.ADD_FIELD_AFTER_KEY;
+import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel.ALLOW_KEY;
+import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel.LIST_TYPE_KEY;
+import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel.TYPE_KEY;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;
-import com.gentics.mesh.core.rest.schema.*;
-import com.gentics.mesh.core.rest.schema.impl.*;
-import io.vertx.core.json.JsonObject;
import org.apache.commons.lang3.BooleanUtils;
+import com.gentics.mesh.core.rest.JsonSchema;
import com.gentics.mesh.core.rest.common.FieldContainer;
+import com.gentics.mesh.core.rest.common.FieldTypes;
import com.gentics.mesh.core.rest.node.field.Field;
import com.gentics.mesh.core.rest.schema.BinaryExtractOptions;
import com.gentics.mesh.core.rest.schema.BinaryFieldSchema;
import com.gentics.mesh.core.rest.schema.FieldSchema;
import com.gentics.mesh.core.rest.schema.FieldSchemaContainer;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.MicronodeFieldSchema;
import com.gentics.mesh.core.rest.schema.NodeFieldSchema;
+import com.gentics.mesh.core.rest.schema.S3BinaryExtractOptions;
+import com.gentics.mesh.core.rest.schema.S3BinaryFieldSchema;
import com.gentics.mesh.core.rest.schema.StringFieldSchema;
import com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel;
import com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeOperation;
@@ -29,12 +35,16 @@
import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NumberFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.S3BinaryFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.StringFieldSchemaImpl;
+import io.vertx.core.json.JsonObject;
+
/**
* Change entry which contains information for a field to be added to the schema.
*/
@@ -159,19 +169,24 @@ default R apply(R container) {
String position = getInsertAfterPosition();
FieldSchema field = null;
// TODO avoid case switches like this. We need a central delegator implementation which will be used in multiple places
- switch (getType()) {
- case "html":
+ switch (FieldTypes.valueByName(getType())) {
+ case JSON:
+ JsonFieldSchema jsonField = new JsonFieldSchemaImpl();
+ jsonField.setAllowedSchemas(getAllowProp() != null ? Arrays.stream(getAllowProp()).map(JsonSchema::new).toArray(size -> new JsonSchema[size]) : null);
+ field = jsonField;
+ break;
+ case HTML:
field = new HtmlFieldSchemaImpl();
break;
- case "string":
+ case STRING:
StringFieldSchema stringField = new StringFieldSchemaImpl();
stringField.setAllowedValues(getAllowProp());
field = stringField;
break;
- case "number":
+ case NUMBER:
field = new NumberFieldSchemaImpl();
break;
- case "binary":
+ case BINARY:
BinaryFieldSchema binaryField = new BinaryFieldSchemaImpl();
Boolean content = getRestProperty(BinaryFieldSchemaImpl.CHANGE_EXTRACT_CONTENT_KEY);
Boolean metadata = getRestProperty(BinaryFieldSchemaImpl.CHANGE_EXTRACT_METADATA_KEY);
@@ -183,7 +198,7 @@ default R apply(R container) {
}
field = binaryField;
break;
- case "s3binary":
+ case S3BINARY:
S3BinaryFieldSchema s3binaryField = new S3BinaryFieldSchemaImpl();
Boolean s3Content = getRestProperty(S3BinaryFieldSchemaImpl.CHANGE_EXTRACT_CONTENT_KEY);
Boolean s3Metadata = getRestProperty(S3BinaryFieldSchemaImpl.CHANGE_EXTRACT_METADATA_KEY);
@@ -195,31 +210,34 @@ default R apply(R container) {
}
field = s3binaryField;
break;
- case "node":
+ case NODE:
NodeFieldSchema nodeField = new NodeFieldSchemaImpl();
nodeField.setAllowedSchemas(getAllowProp());
field = nodeField;
break;
- case "micronode":
+ case MICRONODE:
MicronodeFieldSchema micronodeFieldSchema = new MicronodeFieldSchemaImpl();
micronodeFieldSchema.setAllowedMicroSchemas(getAllowProp());
field = micronodeFieldSchema;
break;
- case "date":
+ case DATE:
field = new DateFieldSchemaImpl();
break;
- case "boolean":
+ case BOOLEAN:
field = new BooleanFieldSchemaImpl();
break;
- case "list":
+ case LIST:
ListFieldSchema listField = new ListFieldSchemaImpl();
listField.setListType(getListType());
field = listField;
- switch (getListType()) {
- case "node":
- case "micronode":
+ switch (FieldTypes.valueByName(getListType())) {
+ case NODE:
+ case MICRONODE:
+ case JSON:
listField.setAllowedSchemas(getAllowProp());
break;
+ default:
+ break;
}
break;
default:
diff --git a/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibFieldTypeChange.java b/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibFieldTypeChange.java
index 820573811c..cadc59be2d 100644
--- a/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibFieldTypeChange.java
+++ b/mdm/api/src/main/java/com/gentics/mesh/core/data/schema/HibFieldTypeChange.java
@@ -3,28 +3,31 @@
import static com.gentics.mesh.core.rest.error.Errors.error;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
-import java.util.*;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import com.gentics.mesh.core.data.node.handler.TypeConverter;
import com.gentics.mesh.core.rest.common.FieldContainer;
-import com.gentics.mesh.core.rest.node.field.*;
+import com.gentics.mesh.core.rest.common.FieldTypes;
import com.gentics.mesh.core.rest.node.field.BinaryField;
import com.gentics.mesh.core.rest.node.field.BooleanField;
import com.gentics.mesh.core.rest.node.field.DateField;
import com.gentics.mesh.core.rest.node.field.Field;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonField;
import com.gentics.mesh.core.rest.node.field.MicronodeField;
import com.gentics.mesh.core.rest.node.field.NodeField;
import com.gentics.mesh.core.rest.node.field.NumberField;
+import com.gentics.mesh.core.rest.node.field.S3BinaryField;
import com.gentics.mesh.core.rest.node.field.StringField;
import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.FieldList;
@@ -34,15 +37,16 @@
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel;
import com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeOperation;
-import com.gentics.mesh.core.rest.schema.impl.*;
import com.gentics.mesh.core.rest.schema.impl.BinaryFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NumberFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.S3BinaryFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.StringFieldSchemaImpl;
import com.google.common.collect.ImmutableSet;
@@ -117,39 +121,42 @@ default R apply(R container) {
String newType = getType();
if (newType != null) {
- switch (newType) {
- case "boolean":
+ switch (FieldTypes.valueByName(newType)) {
+ case BOOLEAN:
field = new BooleanFieldSchemaImpl();
break;
- case "number":
+ case NUMBER:
field = new NumberFieldSchemaImpl();
break;
- case "date":
+ case DATE:
field = new DateFieldSchemaImpl();
break;
- case "html":
+ case HTML:
field = new HtmlFieldSchemaImpl();
break;
- case "string":
+ case STRING:
field = new StringFieldSchemaImpl();
break;
- case "binary":
+ case BINARY:
field = new BinaryFieldSchemaImpl();
break;
- case "s3binary":
+ case S3BINARY:
field = new S3BinaryFieldSchemaImpl();
break;
- case "list":
+ case LIST:
ListFieldSchema listField = new ListFieldSchemaImpl();
listField.setListType(getListType());
field = listField;
break;
- case "micronode":
+ case MICRONODE:
field = new MicronodeFieldSchemaImpl();
break;
- case "node":
+ case NODE:
field = new NodeFieldSchemaImpl();
break;
+ case JSON:
+ field = new JsonFieldSchemaImpl();
+ break;
default:
throw error(BAD_REQUEST, "Unknown type {" + newType + "} for change " + getUuid());
}
@@ -181,27 +188,29 @@ default R apply(R container) {
default Map createFields(FieldSchemaContainer oldSchema, FieldContainer oldContent) {
String newType = getType();
- switch (newType) {
- case "boolean":
+ switch (FieldTypes.valueByName(newType)) {
+ case BOOLEAN:
return Collections.singletonMap(getFieldName(), changeToBoolean(oldSchema, oldContent));
- case "number":
+ case NUMBER:
return Collections.singletonMap(getFieldName(), changeToNumber(oldSchema, oldContent));
- case "date":
+ case DATE:
return Collections.singletonMap(getFieldName(), changeToDate(oldSchema, oldContent));
- case "html":
+ case HTML:
return Collections.singletonMap(getFieldName(), changeToHtml(oldSchema, oldContent));
- case "string":
+ case STRING:
return Collections.singletonMap(getFieldName(), changeToString(oldSchema, oldContent));
- case "binary":
+ case BINARY:
return Collections.singletonMap(getFieldName(), changeToBinary(oldSchema, oldContent));
- case "s3binary":
+ case S3BINARY:
return Collections.singletonMap(getFieldName(), changeToS3Binary(oldSchema, oldContent));
- case "list":
+ case LIST:
return Collections.singletonMap(getFieldName(), changeToList(oldSchema, oldContent));
- case "micronode":
+ case MICRONODE:
return Collections.singletonMap(getFieldName(), changeToMicronode(oldSchema, oldContent));
- case "node":
+ case NODE:
return Collections.singletonMap(getFieldName(), changeToNode(oldSchema, oldContent));
+ case JSON:
+ return Collections.singletonMap(getFieldName(), changeToJson(oldSchema, oldContent));
default:
throw error(BAD_REQUEST, "Unknown type {" + newType + "} for change " + getUuid());
}
@@ -252,6 +261,17 @@ private DateField changeToDate(FieldSchemaContainer oldSchema, FieldContainer ol
return new DateFieldImpl().setDate(typeConverter.toDate(oldField.getValue()));
}
+ private JsonField changeToJson(FieldSchemaContainer oldSchema, FieldContainer oldContent) {
+ String fieldName = getFieldName();
+ FieldSchema fieldSchema = oldSchema.getField(fieldName);
+
+ Field oldField = oldContent.getFields().getField(fieldName, fieldSchema);
+ if (oldField == null) {
+ return null;
+ }
+ return new JsonFieldImpl().setJson(typeConverter.toJsonContent(oldField.getValue()));
+ }
+
private HtmlField changeToHtml(FieldSchemaContainer oldSchema, FieldContainer oldContent) {
String fieldName = getFieldName();
FieldSchema fieldSchema = oldSchema.getField(fieldName);
@@ -318,36 +338,42 @@ private FieldList changeToList(FieldSchemaContainer oldSchema, FieldContainer ol
Object oldValue = oldField.getValue();
String oldType = fieldSchema.getType();
- switch (listType) {
- case "boolean":
+ switch (FieldTypes.valueByName(listType)) {
+ case BOOLEAN:
return typeConverter.toBooleanList(oldValue);
- case "number":
+ case NUMBER:
if (isUuidType(fieldSchema)) {
return null;
}
- switch (oldType) {
- case "number":
+ switch (FieldTypes.valueByName(oldType)) {
+ case NUMBER:
return new NumberFieldListImpl().setItems(Collections.singletonList(oldContent.getFields().getNumberField(fieldName).getNumber()));
default:
return typeConverter.toNumberList(oldValue);
}
- case "date":
+ case DATE:
return typeConverter.toDateList(oldValue);
- case "html":
+ case HTML:
if (isNonNodeUuidType(fieldSchema)) {
return null;
} else {
return typeConverter.toHtmlList(oldValue);
}
- case "string":
+ case STRING:
if (isNonNodeUuidType(fieldSchema)) {
return null;
} else {
return typeConverter.toStringList(oldValue);
}
- case "micronode":
+ case JSON:
+ if (isNonNodeUuidType(fieldSchema)) {
+ return null;
+ } else {
+ return typeConverter.toJsonList(oldValue);
+ }
+ case MICRONODE:
return typeConverter.toMicronodeList(oldField);
- case "node":
+ case NODE:
return typeConverter.toNodeList(oldField);
default:
throw error(BAD_REQUEST, "Unknown list type {" + listType + "} for change " + getUuid());
@@ -367,6 +393,7 @@ private boolean isUuidType(FieldSchema fieldSchema) {
}
}
+ @SuppressWarnings("unused")
private FieldList nullableList(T[] input, Supplier> output) {
if (input == null) {
return null;
diff --git a/mdm/common/pom.xml b/mdm/common/pom.xml
index 479367fa63..fd1ba1c369 100644
--- a/mdm/common/pom.xml
+++ b/mdm/common/pom.xml
@@ -28,7 +28,7 @@
com.gentics.mesh
mesh-plugin-api
-
+
com.google.dagger
diff --git a/mdm/common/src/main/java/com/gentics/mesh/FieldUtil.java b/mdm/common/src/main/java/com/gentics/mesh/FieldUtil.java
index 971ec8b2f8..7e7b70633b 100644
--- a/mdm/common/src/main/java/com/gentics/mesh/FieldUtil.java
+++ b/mdm/common/src/main/java/com/gentics/mesh/FieldUtil.java
@@ -5,8 +5,26 @@
import com.gentics.mesh.core.rest.microschema.impl.MicroschemaCreateRequest;
import com.gentics.mesh.core.rest.microschema.impl.MicroschemaModelImpl;
import com.gentics.mesh.core.rest.microschema.impl.MicroschemaUpdateRequest;
-import com.gentics.mesh.core.rest.node.field.*;
-import com.gentics.mesh.core.rest.node.field.impl.*;
+import com.gentics.mesh.core.rest.node.field.BinaryField;
+import com.gentics.mesh.core.rest.node.field.BooleanField;
+import com.gentics.mesh.core.rest.node.field.DateField;
+import com.gentics.mesh.core.rest.node.field.Field;
+import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
+import com.gentics.mesh.core.rest.node.field.MicronodeField;
+import com.gentics.mesh.core.rest.node.field.NumberField;
+import com.gentics.mesh.core.rest.node.field.S3BinaryField;
+import com.gentics.mesh.core.rest.node.field.StringField;
+import com.gentics.mesh.core.rest.node.field.impl.BinaryFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.NodeFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.S3BinaryFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
@@ -15,8 +33,32 @@
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListItemImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.StringFieldListImpl;
-import com.gentics.mesh.core.rest.schema.*;
-import com.gentics.mesh.core.rest.schema.impl.*;
+import com.gentics.mesh.core.rest.schema.BinaryFieldSchema;
+import com.gentics.mesh.core.rest.schema.BooleanFieldSchema;
+import com.gentics.mesh.core.rest.schema.DateFieldSchema;
+import com.gentics.mesh.core.rest.schema.HtmlFieldSchema;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
+import com.gentics.mesh.core.rest.schema.ListFieldSchema;
+import com.gentics.mesh.core.rest.schema.MicronodeFieldSchema;
+import com.gentics.mesh.core.rest.schema.NodeFieldSchema;
+import com.gentics.mesh.core.rest.schema.NumberFieldSchema;
+import com.gentics.mesh.core.rest.schema.S3BinaryFieldSchema;
+import com.gentics.mesh.core.rest.schema.SchemaVersionModel;
+import com.gentics.mesh.core.rest.schema.StringFieldSchema;
+import com.gentics.mesh.core.rest.schema.impl.BinaryFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.MicroschemaReferenceImpl;
+import com.gentics.mesh.core.rest.schema.impl.NodeFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.NumberFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.S3BinaryFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.SchemaCreateRequest;
+import com.gentics.mesh.core.rest.schema.impl.SchemaModelImpl;
+import com.gentics.mesh.core.rest.schema.impl.StringFieldSchemaImpl;
import com.gentics.mesh.util.Tuple;
/**
@@ -104,6 +146,19 @@ public static StringFieldSchema createStringFieldSchema(String name) {
return fieldSchema;
}
+ /**
+ * Create a new JSON object field schema.
+ *
+ * @param name
+ * Name of the field schema
+ * @return
+ */
+ public static JsonFieldSchema createJsonFieldSchema(String name) {
+ JsonFieldSchema fieldSchema = new JsonFieldSchemaImpl();
+ fieldSchema.setName(name);
+ return fieldSchema;
+ }
+
/**
* Create a string field and set the given value.
*
@@ -116,12 +171,38 @@ public static StringField createStringField(String stringValue) {
return field;
}
+ /**
+ * Create a JSON object field and set the given value.
+ *
+ * @param stringValue
+ * @return
+ */
+ public static JsonField createJsonField(JsonContent jsonValue) {
+ JsonField field = new JsonFieldImpl();
+ field.setJson(jsonValue);
+ return field;
+ }
+
+ /**
+ * Create a HTML field and set the given value.
+ *
+ * @param htmlValue
+ * @return
+ */
public static HtmlField createHtmlField(String htmlValue) {
HtmlField field = new HtmlFieldImpl();
field.setHTML(htmlValue);
return field;
}
+ /**
+ * Create a binary field and set the given value.
+ *
+ * @param uuid target binary UUID
+ * @param fileName
+ * @param hashSum
+ * @return
+ */
public static BinaryField createBinaryField(String uuid, String fileName, String hashSum) {
BinaryField field = new BinaryFieldImpl();
field.setBinaryUuid(uuid);
@@ -130,6 +211,12 @@ public static BinaryField createBinaryField(String uuid, String fileName, String
return field;
}
+ /**
+ * Create a S3 binary field and set the given value.
+ *
+ * @param fileName
+ * @return
+ */
public static S3BinaryField createS3BinaryField(String fileName) {
S3BinaryField field = new S3BinaryFieldImpl();
field.setFileName(fileName);
@@ -160,6 +247,12 @@ public static BooleanField createBooleanField(Boolean value) {
return field;
}
+ /**
+ * Create a date field and set the given value.
+ *
+ * @param iso8601Date
+ * @return
+ */
public static DateField createDateField(String iso8601Date) {
DateField field = new DateFieldImpl();
field.setDate(iso8601Date);
diff --git a/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestGetters.java b/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestGetters.java
index 09145a0599..85f03a22dd 100644
--- a/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestGetters.java
+++ b/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestGetters.java
@@ -22,6 +22,10 @@ public class RestGetters {
public static FieldGetter HTML_LIST_GETTER = (container, fieldSchema) -> container.getHTMLList(fieldSchema.getName());
+ public static FieldGetter JSON_GETTER = (container, fieldSchema) -> container.getJson(fieldSchema.getName());
+
+ public static FieldGetter JSON_LIST_GETTER = (container, fieldSchema) -> container.getJsonList(fieldSchema.getName());
+
public static FieldGetter MICRONODE_GETTER = (container, fieldSchema) -> container.getMicronode(fieldSchema.getName());
public static FieldGetter MICRONODE_LIST_GETTER = (container, fieldSchema) -> container.getMicronodeList(fieldSchema.getName());
diff --git a/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestTransformers.java b/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestTransformers.java
index 5c8af8ad78..8d05cc61bc 100644
--- a/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestTransformers.java
+++ b/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestTransformers.java
@@ -6,6 +6,7 @@
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -22,6 +23,8 @@
import com.gentics.mesh.core.rest.node.field.DateField;
import com.gentics.mesh.core.rest.node.field.Field;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
import com.gentics.mesh.core.rest.node.field.MicronodeField;
import com.gentics.mesh.core.rest.node.field.NodeField;
import com.gentics.mesh.core.rest.node.field.NumberField;
@@ -32,8 +35,10 @@
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.StringFieldListImpl;
+import com.gentics.mesh.json.JsonUtil;
import com.gentics.mesh.parameter.LinkType;
public interface RestTransformers {
@@ -158,6 +163,38 @@ public interface RestTransformers {
}
};
+ FieldTransformer JSON_TRANSFORMER = (container, ac, fieldKey, fieldSchema, languageTags, level, parentNode) -> {
+ CommonTx tx = CommonTx.get();
+ WebRootLinkReplacer webRootLinkReplacer = tx.data().mesh().webRootLinkReplacer();
+ HibJsonField sqlJsonField = container.getJson(fieldKey);
+ if (sqlJsonField == null) {
+ return null;
+ } else {
+ JsonField field = sqlJsonField.transformToRest(ac);
+ // If needed resolve links within the JSON
+ if (ac.getNodeParameters().getResolveLinks() != LinkType.OFF) {
+ HibProject project = tx.getProject(ac);
+ if (project == null) {
+ project = parentNode.get().getProject();
+ }
+ field.setJson(JsonContent.fromString(webRootLinkReplacer.replace(ac, tx.getBranch(ac).getUuid(),
+ ContainerType.forVersion(ac.getVersioningParameters().getVersion()), JsonUtil.toJson(field.getJson(), true),
+ ac.getNodeParameters().getResolveLinks(), project.getName(), languageTags)));
+ }
+ return field;
+ }
+ };
+
+ FieldTransformer JSON_LIST_TRANSFORMER = (container, ac, fieldKey, fieldSchema, languageTags, level,
+ parentNode) -> {
+ HibJsonFieldList jsonFieldList = container.getJsonList(fieldKey);
+ if (jsonFieldList == null) {
+ return null;
+ } else {
+ return jsonFieldList.transformToRest(ac, fieldKey, languageTags, level);
+ }
+ };
+
FieldTransformer MICRONODE_TRANSFORMER = (container, ac, fieldKey, fieldSchema, languageTags, level,
parentNode) -> {
HibMicronodeField micronodeGraphField = container.getMicronode(fieldKey);
diff --git a/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestUpdaters.java b/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestUpdaters.java
index 5966b5f4f6..984931e08f 100644
--- a/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestUpdaters.java
+++ b/mdm/common/src/main/java/com/gentics/mesh/core/data/node/field/RestUpdaters.java
@@ -9,6 +9,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
@@ -26,6 +27,7 @@
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -37,11 +39,14 @@
import com.gentics.mesh.core.data.s3binary.S3HibBinaryField;
import com.gentics.mesh.core.data.schema.HibMicroschemaVersion;
import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.rest.JsonSchema;
import com.gentics.mesh.core.rest.node.field.BinaryCheckStatus;
import com.gentics.mesh.core.rest.node.field.BinaryField;
import com.gentics.mesh.core.rest.node.field.BooleanField;
import com.gentics.mesh.core.rest.node.field.DateField;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
import com.gentics.mesh.core.rest.node.field.MicronodeField;
import com.gentics.mesh.core.rest.node.field.NodeField;
import com.gentics.mesh.core.rest.node.field.NodeFieldListItem;
@@ -57,16 +62,19 @@
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.StringFieldListImpl;
import com.gentics.mesh.core.rest.node.field.s3binary.S3BinaryMetadata;
import com.gentics.mesh.core.rest.schema.BinaryFieldSchema;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.MicronodeFieldSchema;
import com.gentics.mesh.core.rest.schema.MicroschemaReference;
import com.gentics.mesh.core.rest.schema.NodeFieldSchema;
import com.gentics.mesh.core.rest.schema.S3BinaryFieldSchema;
import com.gentics.mesh.core.rest.schema.StringFieldSchema;
+import com.gentics.mesh.json.JsonUtil;
import com.gentics.mesh.util.DateUtils;
public class RestUpdaters {
@@ -366,6 +374,91 @@ public class RestUpdaters {
graphBooleanFieldList.createBooleans(booleanList.getItems());
};
+ public static FieldUpdater JSON_UPDATER = (container, ac, fieldMap, fieldKey, fieldSchema, schema) -> {
+ JsonField jsonField = fieldMap.getJsonField(fieldKey);
+ HibJsonField jsonSqlField = container.getJson(fieldKey);
+ boolean isJsonFieldSetToNull = fieldMap.hasField(fieldKey) && (jsonField == null || jsonField.getJson() == null);
+ HibField.failOnDeletionOfRequiredField(jsonSqlField, isJsonFieldSetToNull, fieldSchema, fieldKey, schema);
+ boolean isJsonFieldNull = jsonField == null || jsonField.getJson() == null;
+
+ // Skip this check for no migrations
+ if (!ac.isMigrationContext()) {
+ HibField.failOnMissingRequiredField(jsonSqlField, isJsonFieldNull, fieldSchema, fieldKey, schema);
+ }
+
+ // Handle Deletion - The field was explicitly set to null and is currently set within the graph thus we must remove it.
+ if (isJsonFieldSetToNull && jsonSqlField != null) {
+ container.removeField(jsonSqlField);
+ return;
+ }
+
+ // Rest model is empty or null - Abort
+ if (isJsonFieldNull) {
+ return;
+ }
+
+ // check value length
+ checkStringLength(JsonUtil.toJson(jsonField.getJson()), fieldKey);
+
+ // check value restrictions
+ JsonFieldSchema jsonFieldSchema = (JsonFieldSchema) fieldSchema;
+ JsonSchema[] allowedSchemas = jsonFieldSchema.getAllowedSchemas();
+ if (allowedSchemas != null && allowedSchemas.length != 0) {
+ Object jsonContent = jsonField.getJson().getContent();
+ if (jsonField.getJson() != null && Arrays.asList(allowedSchemas).stream()
+ .noneMatch(schema1 -> JsonUtil.newJsonSchemaValidator(schema1.getVertxSchema()).validate(jsonContent).getValid() == Boolean.TRUE)) {
+ throw error(BAD_REQUEST, "node_error_invalid_json_field_value", fieldKey, JsonUtil.toJson(jsonContent));
+ }
+ }
+
+ // Handle Update / Create - Create new graph field if no existing one could be found
+ if (jsonSqlField == null) {
+ container.createJson(fieldKey).setJson(jsonField.getJson());
+ } else {
+ jsonSqlField.setJson(jsonField.getJson());
+ }
+ };
+
+ public static FieldUpdater JSON_LIST_UPDATER = (container, ac, fieldMap, fieldKey, fieldSchema, schema) -> {
+ HibJsonFieldList graphJsonFieldList = container.getJsonList(fieldKey);
+ JsonFieldListImpl jsonList = fieldMap.getJsonFieldList(fieldKey);
+ boolean isJsonListFieldSetToNull = fieldMap.hasField(fieldKey) && jsonList == null;
+ HibField.failOnDeletionOfRequiredField(graphJsonFieldList, isJsonListFieldSetToNull, fieldSchema, fieldKey, schema);
+ boolean restIsNull = jsonList == null;
+
+ // Skip this check for no migrations
+ if (!ac.isMigrationContext()) {
+ HibField.failOnMissingRequiredField(graphJsonFieldList, jsonList == null, fieldSchema, fieldKey, schema);
+ }
+
+ // Handle Deletion
+ if (isJsonListFieldSetToNull && graphJsonFieldList != null) {
+ container.removeField(graphJsonFieldList);
+ return;
+ }
+
+ // Rest model is empty or null - Abort
+ if (restIsNull) {
+ return;
+ }
+
+ // check strings length
+ checkStringsLength(jsonList.getItems(), fieldKey, JsonUtil::toJson);
+
+ // Always create a new list.
+ // This will effectively unlink the old list and create a new one.
+ // Otherwise the list which is linked to old versions would be updated.
+ graphJsonFieldList = container.createJsonList(fieldKey);
+
+ // Add items from rest model
+ for (JsonContent item : jsonList.getItems()) {
+ if (item == null) {
+ throw error(BAD_REQUEST, "field_list_error_null_not_allowed", fieldKey);
+ }
+ }
+ graphJsonFieldList.createJsons(jsonList.getItems());
+ };
+
public static FieldUpdater HTML_UPDATER = (container, ac, fieldMap, fieldKey, fieldSchema, schema) -> {
HtmlField htmlField = fieldMap.getHtmlField(fieldKey);
HibHtmlField htmlGraphField = container.getHtml(fieldKey);
@@ -882,9 +975,18 @@ private static void checkStringLength(String string, String fieldKey) {
* @param fieldKey field key
*/
private static void checkStringsLength(List strings, String fieldKey) {
- if (CollectionUtils.isEmpty(strings)) {
+ checkStringsLength(strings, fieldKey, Function.identity());
+ }
+
+ /**
+ * Check whether all the given strings are within the allowed length bounds. If not, throw an error
+ * @param list string contents
+ * @param fieldKey field key
+ */
+ private static void checkStringsLength(List list, String fieldKey, Function toString) {
+ if (CollectionUtils.isEmpty(list)) {
return;
}
- strings.forEach(string -> checkStringLength(string, fieldKey));
+ list.forEach(item -> checkStringLength(toString.apply(item), fieldKey));
}
}
diff --git a/mdm/common/src/main/java/com/gentics/mesh/core/data/schema/handler/AbstractFieldSchemaContainerComparator.java b/mdm/common/src/main/java/com/gentics/mesh/core/data/schema/handler/AbstractFieldSchemaContainerComparator.java
index 3bd8788262..c92982a952 100644
--- a/mdm/common/src/main/java/com/gentics/mesh/core/data/schema/handler/AbstractFieldSchemaContainerComparator.java
+++ b/mdm/common/src/main/java/com/gentics/mesh/core/data/schema/handler/AbstractFieldSchemaContainerComparator.java
@@ -5,6 +5,7 @@
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -13,10 +14,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gentics.mesh.core.rest.JsonSchema;
import com.gentics.mesh.core.rest.schema.BinaryExtractOptions;
import com.gentics.mesh.core.rest.schema.BinaryFieldSchema;
import com.gentics.mesh.core.rest.schema.FieldSchema;
import com.gentics.mesh.core.rest.schema.FieldSchemaContainer;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.MicronodeFieldSchema;
import com.gentics.mesh.core.rest.schema.MicroschemaModel;
@@ -25,6 +28,7 @@
import com.gentics.mesh.core.rest.schema.StringFieldSchema;
import com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel;
import com.gentics.mesh.core.rest.schema.impl.BinaryFieldSchemaImpl;
+import com.gentics.mesh.json.JsonUtil;
import com.gentics.mesh.util.CompareUtils;
import io.vertx.core.json.JsonObject;
@@ -103,6 +107,10 @@ protected List diff(FC containerA, FC containerB, Class ext
if (fieldInB instanceof StringFieldSchema) {
change.setProperty(SchemaChangeModel.ALLOW_KEY, ((StringFieldSchema) fieldInB).getAllowedValues());
}
+ if (fieldInB instanceof JsonFieldSchema) {
+ JsonSchema[] schemas = ((JsonFieldSchema) fieldInB).getAllowedSchemas();
+ change.setProperty(SchemaChangeModel.ALLOW_KEY, schemas == null ? null : Arrays.stream(schemas).map(JsonUtil::toJson).toArray(size -> new String[size]));
+ }
if (fieldInB instanceof BinaryFieldSchema) {
BinaryFieldSchema field = (BinaryFieldSchema) fieldInB;
BinaryExtractOptions options = field.getBinaryExtractOptions();
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/cache/ListableFieldCacheImpl.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/cache/ListableFieldCacheImpl.java
index 442dc32e3d..9c4a2d9b9c 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/cache/ListableFieldCacheImpl.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/cache/ListableFieldCacheImpl.java
@@ -11,6 +11,8 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.gentics.mesh.cache.impl.EventAwareCacheFactory;
import com.gentics.mesh.cache.impl.EventAwareCacheImpl.Builder;
@@ -20,19 +22,19 @@
import com.gentics.mesh.core.endpoint.admin.debuginfo.DebugInfoProvider;
import com.gentics.mesh.core.endpoint.admin.debuginfo.DebugInfoUtil;
import com.gentics.mesh.core.rest.MeshEvent;
+import com.gentics.mesh.etc.config.ConfigUtils;
import com.gentics.mesh.etc.config.HibernateMeshOptions;
import com.gentics.mesh.etc.config.hibernate.HibernateCacheConfig;
-import com.gentics.mesh.etc.config.ConfigUtils;
import com.gentics.mesh.hibernate.data.domain.AbstractHibListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibDateListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibHtmlListFieldEdgeImpl;
+import com.gentics.mesh.hibernate.data.domain.HibJsonListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibNumberListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibStringListFieldEdgeImpl;
import com.gentics.mesh.hibernate.util.StringScale;
+import com.gentics.mesh.json.JsonUtil;
import io.reactivex.Flowable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Implementation of {@link ListableFieldCache}
@@ -81,7 +83,9 @@ private static int listFieldWeight(UUID uuid, Optional getJavaClass() {
switch (fieldType) {
+ case JSON:
+ return JsonContent.class;
case STRING:
case HTML:
return String.class;
@@ -95,6 +98,13 @@ public Object transformToPersistedValue(Object value) {
} else {
return ContentColumn.super.transformToPersistedValue(value);
}
+// if JSON were BLOB, not String
+// } else if (fieldType == FieldTypes.JSON) {
+// if (value instanceof String) {
+// return value.toString().getBytes(StandardCharsets.UTF_8);
+// } else {
+// return null;
+// }
} else {
return ContentColumn.super.transformToPersistedValue(value);
}
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/dagger/HibernateModule.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/dagger/HibernateModule.java
index 785dd0e446..5208c379d8 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/dagger/HibernateModule.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/dagger/HibernateModule.java
@@ -37,6 +37,7 @@
import com.gentics.mesh.check.DateListItemCheck;
import com.gentics.mesh.check.HibernateBranchCheck;
import com.gentics.mesh.check.HtmlListItemCheck;
+import com.gentics.mesh.check.JsonListItemCheck;
import com.gentics.mesh.check.MicronodeFieldRefCheck;
import com.gentics.mesh.check.MicronodeListItemCheck;
import com.gentics.mesh.check.NodeContentConsistencyCheck;
@@ -338,6 +339,7 @@ public static HazelcastInstance hazelcast(HibClusterManager clusterManager) {
new NodeFieldContainerCheck(),
new NodeFieldContainerVersionsEdgeCheck(),
new ContentRefCheck(),
+ new JsonListItemCheck(),
new NodeContentConsistencyCheck())
);
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/database/DefaultSQLDatabase.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/database/DefaultSQLDatabase.java
index 51aaaa48f9..9af5cabff4 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/database/DefaultSQLDatabase.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/database/DefaultSQLDatabase.java
@@ -10,10 +10,12 @@
import org.hibernate.hikaricp.internal.HikariCPConnectionProvider;
import org.hibernate.jpa.boot.spi.IntegratorProvider;
import org.hibernate.jpa.boot.spi.JpaSettings;
+import org.hibernate.type.format.jackson.JacksonJsonFormatMapper;
import com.gentics.mesh.database.connector.DatabaseConnector;
import com.gentics.mesh.etc.config.HibernateMeshOptions;
import com.gentics.mesh.hibernate.ContentInterceptor;
+import com.gentics.mesh.json.JsonUtil;
import com.google.common.collect.ImmutableMap;
import com.hazelcast.hibernate.HazelcastLocalCacheRegionFactory;
@@ -117,6 +119,7 @@ private void setOtherOptions(ImmutableMap.Builder optionBuilder)
.put(AvailableSettings.ORDER_UPDATES, "true")
.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, databaseConnector.getPhysicalNamingStrategyClass().getCanonicalName())
.put(AvailableSettings.INTERCEPTOR, ContentInterceptor.class.getName())
+ .put(AvailableSettings.JSON_FORMAT_MAPPER, new JacksonJsonFormatMapper(JsonUtil.getMapper()))
.put(JpaSettings.INTEGRATOR_PROVIDER, (IntegratorProvider) () -> Collections.singletonList(databaseConnector.getSessionMetadataIntegrator()))
// don't save timezones in the dates for backwards compatibility
.put(AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP");
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentDaoImpl.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentDaoImpl.java
index 2c098016af..556cc297f8 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentDaoImpl.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentDaoImpl.java
@@ -66,6 +66,7 @@
import com.gentics.mesh.core.rest.common.ContainerType;
import com.gentics.mesh.core.rest.common.FieldTypes;
import com.gentics.mesh.core.rest.common.ReferenceType;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.schema.FieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.SchemaModel;
@@ -88,6 +89,7 @@
import com.gentics.mesh.hibernate.data.domain.HibFieldEdge;
import com.gentics.mesh.hibernate.data.domain.HibHtmlListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibImageVariantImpl;
+import com.gentics.mesh.hibernate.data.domain.HibJsonListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibMicronodeContainerImpl;
import com.gentics.mesh.hibernate.data.domain.HibMicronodeFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibMicronodeListFieldEdgeImpl;
@@ -110,6 +112,7 @@
import com.gentics.mesh.hibernate.data.node.field.impl.HibBooleanListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibDateListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibHtmlListFieldImpl;
+import com.gentics.mesh.hibernate.data.node.field.impl.HibJsonListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibMicronodeFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibMicronodeListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibNumberListFieldImpl;
@@ -525,6 +528,7 @@ public void deleteUnreferencedMicronodes(HibMicroschemaVersion version) {
case HTML:
case NUMBER:
case DATE:
+ case JSON:
case BOOLEAN:
// nothing to do
break;
@@ -600,6 +604,7 @@ public void delete(Set contentKeys) {
case HTML:
case NUMBER:
case DATE:
+ case JSON:
case BOOLEAN:
// These are stored in the content table, so they will be deleted later on.
break;
@@ -649,6 +654,7 @@ private void deleteUnreferencedForVersion(HibSchemaVersion version) {
case HTML:
case NUMBER:
case DATE:
+ case JSON:
case BOOLEAN:
// nothing to do
break;
@@ -870,6 +876,7 @@ public void delete(List extends HibNodeFieldContainer> containers) {
case HTML:
case NUMBER:
case DATE:
+ case JSON:
case BOOLEAN:
// These are stored in the content table, so they will be deleted later on.
break;
@@ -953,6 +960,8 @@ private Class> getListClass(FieldTypes listType) {
return HibStringListFieldEdgeImpl.class;
case HTML:
return HibHtmlListFieldEdgeImpl.class;
+ case JSON:
+ return HibJsonListFieldEdgeImpl.class;
case NUMBER:
return HibNumberListFieldEdgeImpl.class;
case DATE:
@@ -1531,6 +1540,7 @@ private void deleteMicronodesList(List containerUuids) {
case HTML:
case NUMBER:
case DATE:
+ case JSON:
case BOOLEAN:
// These are stored in the content table, so they will be deleted later on.
break;
@@ -1617,6 +1627,7 @@ private void deleteMicronodes(List containerUuids) {
case HTML:
case NUMBER:
case DATE:
+ case JSON:
case BOOLEAN:
// These are stored in the content table, so they will be deleted later on.
break;
@@ -1806,6 +1817,7 @@ public void loadListFields(List extends HibNodeFieldContainer> containers) {
getNumberListFieldValues(getListFieldListUuids(containers, HibNumberListFieldImpl.class));
getHtmlListFieldValues(getListFieldListUuids(containers, HibHtmlListFieldImpl.class));
getStringListFieldValues(getListFieldListUuids(containers, HibStringListFieldImpl.class));
+ getJsonListFieldValues(getListFieldListUuids(containers, HibJsonListFieldImpl.class));
getMicronodeListFieldValues(getListFieldListUuids(containers, HibMicronodeListFieldImpl.class));
}
@@ -1841,6 +1853,11 @@ public Map> getDateListFieldValues(List listUuids) {
return getListValues(listUuids, HibDateListFieldEdgeImpl::getDate, HibDateListFieldEdgeImpl.class);
}
+ @Override
+ public Map> getJsonListFieldValues(List listUuids) {
+ return getListValues(listUuids, HibJsonListFieldEdgeImpl::getJson, HibJsonListFieldEdgeImpl.class);
+ }
+
@Override
public Map> getNumberListFieldValues(List listUuids) {
return getListValues(listUuids, HibNumberListFieldEdgeImpl::getNumber, HibNumberListFieldEdgeImpl.class);
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentUtils.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentUtils.java
index 3eac950b3c..9e8c4ea4e1 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentUtils.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/ContentUtils.java
@@ -16,6 +16,7 @@
import com.gentics.mesh.hibernate.data.domain.HibBooleanListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibDateListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibHtmlListFieldEdgeImpl;
+import com.gentics.mesh.hibernate.data.domain.HibJsonListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibMicronodeListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibNodeFieldContainerEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibNodeFieldEdgeImpl;
@@ -85,6 +86,7 @@ public String[] getHibernateEntityName(Object... args) {
case BOOLEAN: return Stream.of(HibBooleanListFieldEdgeImpl.class);
case DATE: return Stream.of(HibDateListFieldEdgeImpl.class);
case HTML: return Stream.of(HibHtmlListFieldEdgeImpl.class);
+ case JSON: return Stream.of(HibJsonListFieldEdgeImpl.class);
case MICRONODE: return Stream.of(HibMicronodeListFieldEdgeImpl.class, HibMicronodeFieldImpl.class);
case NODE: return Stream.of(HibNodeListFieldEdgeImpl.class, HibNodeListFieldImpl.class);
default: return Stream.empty();
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/DaoHelper.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/DaoHelper.java
index fe47a57f5d..817be756e0 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/DaoHelper.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/dao/DaoHelper.java
@@ -100,6 +100,7 @@
import com.gentics.mesh.hibernate.data.domain.HibHtmlListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibImageVariantImpl;
import com.gentics.mesh.hibernate.data.domain.HibJobImpl;
+import com.gentics.mesh.hibernate.data.domain.HibJsonListFieldEdgeImpl;
import com.gentics.mesh.hibernate.data.domain.HibLanguageImpl;
import com.gentics.mesh.hibernate.data.domain.HibMicronodeContainerImpl;
import com.gentics.mesh.hibernate.data.domain.HibMicronodeFieldEdgeImpl;
@@ -1101,7 +1102,7 @@ private Optional maybeMakeLiteralOperand(FilterOperand> opera
String paramName = makeParamName(value);
if (value != null) {
if (UUIDUtil.isUUID(value.toString())) {
- if (maybeOperandType.filter(otype -> "string".equalsIgnoreCase(otype) || "html".equalsIgnoreCase(otype)).isEmpty()) {
+ if (maybeOperandType.filter(otype -> "string".equalsIgnoreCase(otype) || "json".equalsIgnoreCase(otype) || "html".equalsIgnoreCase(otype)).isEmpty()) {
value = UUIDUtil.toJavaUuid(value.toString());
}
} else if (value.getClass().isEnum()) {
@@ -1748,6 +1749,9 @@ private Optional> tableNameIntoClass(String tableName) {
case "STRINGLIST":
clss = HibStringListFieldEdgeImpl.class;
break;
+ case "JSONLIST":
+ clss = HibJsonListFieldEdgeImpl.class;
+ break;
case "NUMBERLIST":
clss = HibNumberListFieldEdgeImpl.class;
break;
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/AbstractHibPropertyContainerElement.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/AbstractHibPropertyContainerElement.java
index 385e479fbb..f3627eb296 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/AbstractHibPropertyContainerElement.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/AbstractHibPropertyContainerElement.java
@@ -70,6 +70,13 @@ public R property(String name) {
for (int i = 0; i < ja.size(); i++) {
Object item = ja.getValue(i);
item = isJson ? JsonUtil.readValue(item.toString(), cls) : item;
+ if (!cls.isAssignableFrom(item.getClass())) {
+ try {
+ item = cls.getConstructor(item.getClass()).newInstance(item);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
Array.set(array, i, item);
}
return (R) array;
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibJsonListFieldEdgeImpl.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibJsonListFieldEdgeImpl.java
new file mode 100644
index 0000000000..75b1f9154b
--- /dev/null
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibJsonListFieldEdgeImpl.java
@@ -0,0 +1,61 @@
+package com.gentics.mesh.hibernate.data.domain;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+
+import com.gentics.mesh.core.data.node.field.HibJsonField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.database.HibernateTx;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Index;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+
+/**
+ * JSON object list field definition edge.
+ *
+ * @author plyhun
+ *
+ */
+@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
+@Entity(name = "jsonlistitem")
+@Table(uniqueConstraints = {
+ @UniqueConstraint(
+ name = "KeyTypeVersionContainerListIndex",
+ columnNames = { "itemIndex", "listUuid", "fieldKey", "containerUuid", "containerType", "containerVersionUuid" }
+ )
+}, indexes = {
+ @Index(columnList = "listUuid")
+})
+public class HibJsonListFieldEdgeImpl extends AbstractHibPrimitiveListFieldEdgeImpl implements HibJsonField, Serializable {
+
+ private static final long serialVersionUID = -6554262711404820079L;
+
+ public HibJsonListFieldEdgeImpl() {
+ }
+
+ public HibJsonListFieldEdgeImpl(HibernateTx tx, UUID listUuid, int index, String fieldKey, JsonContent value,
+ HibUnmanagedFieldContainer,?,?,?,?> parentFieldContainer) {
+ super(tx, listUuid, index, fieldKey, value, parentFieldContainer);
+ }
+
+ @Override
+ public JsonContent getJson() {
+ return valueOrUuid;
+ }
+
+ @Override
+ public void setJson(JsonContent value) {
+ this.valueOrUuid = value;
+ HibernateTx.get().entityManager().merge(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return jsonEquals(obj);
+ }
+}
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibUnmanagedFieldContainer.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibUnmanagedFieldContainer.java
index dc2907c491..759e9053a4 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibUnmanagedFieldContainer.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/domain/HibUnmanagedFieldContainer.java
@@ -14,6 +14,7 @@
import java.util.function.Supplier;
import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
import com.gentics.mesh.contentoperation.CommonContentColumn;
import com.gentics.mesh.contentoperation.ContentColumn;
@@ -29,11 +30,13 @@
import com.gentics.mesh.core.data.node.field.HibBooleanField;
import com.gentics.mesh.core.data.node.field.HibDateField;
import com.gentics.mesh.core.data.node.field.HibHtmlField;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
import com.gentics.mesh.core.data.node.field.HibNumberField;
import com.gentics.mesh.core.data.node.field.HibStringField;
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -68,6 +71,8 @@
import com.gentics.mesh.hibernate.data.node.field.impl.HibDateListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibHtmlFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibHtmlListFieldImpl;
+import com.gentics.mesh.hibernate.data.node.field.impl.HibJsonFieldImpl;
+import com.gentics.mesh.hibernate.data.node.field.impl.HibJsonListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibMicronodeFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibMicronodeListFieldImpl;
import com.gentics.mesh.hibernate.data.node.field.impl.HibNodeFieldImpl;
@@ -79,8 +84,6 @@
import com.gentics.mesh.hibernate.data.node.field.impl.HibStringListFieldImpl;
import com.gentics.mesh.util.UUIDUtil;
-import org.slf4j.Logger;
-
/**
* A base for the field container entities, that are not known to the Hibernate entity manager.
*
@@ -239,6 +242,11 @@ default HibStringField getString(String key) {
return getFieldValueFromNullableColumn(key, FieldTypes.STRING, HibStringFieldImpl::new);
}
+ @Override
+ default HibJsonField getJson(String key) {
+ return getFieldValueFromNullableColumn(key, FieldTypes.JSON, HibJsonFieldImpl::new);
+ }
+
@Override
default HibBinaryFieldImpl getBinary(String key) {
return getReferenceFieldValueFromNullableColumn(key, FieldTypes.BINARY, HibBinaryFieldImpl::new);
@@ -328,6 +336,12 @@ default HibBooleanField createBoolean(String key) {
return new HibBooleanFieldImpl(key, this, null);
}
+ @Override
+ default HibJsonField createJson(String key) {
+ ensureColumnExists(key, FieldTypes.JSON);
+ return new HibJsonFieldImpl(key, this, null);
+ }
+
@Override
default HibStringField createString(String key) {
ensureColumnExists(key, FieldTypes.STRING);
@@ -450,6 +464,11 @@ default HibNumberListFieldImpl getNumberList(String key) {
return getListFieldFromNullableColumn(key, FieldTypes.NUMBER, HibNumberListFieldImpl::new);
}
+ @Override
+ default HibJsonListFieldImpl getJsonList(String key) {
+ return getListFieldFromNullableColumn(key, FieldTypes.JSON, HibJsonListFieldImpl::new);
+ }
+
@Override
default HibNumberFieldList createNumberList(String key) {
HibernateTx tx = HibernateTx.get();
@@ -458,6 +477,14 @@ default HibNumberFieldList createNumberList(String key) {
return HibNumberListFieldImpl.fromContainer(tx, this, key, Collections.emptyList());
}
+ @Override
+ default HibJsonFieldList createJsonList(String key) {
+ HibernateTx tx = HibernateTx.get();
+ ensureColumnExists(key, FieldTypes.LIST);
+ ensureOldReferenceRemoved(tx, key, this::getJsonList, false);
+ return HibJsonListFieldImpl.fromContainer(tx, this, key, Collections.emptyList());
+ }
+
@Override
default HibNodeListFieldImpl getNodeList(String key) {
return this.getListFieldFromNullableColumn(key, FieldTypes.NODE, HibNodeListFieldImpl::new);
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/HibFieldTypes.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/HibFieldTypes.java
index db3c2731cf..4e0a222991 100644
--- a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/HibFieldTypes.java
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/HibFieldTypes.java
@@ -7,6 +7,8 @@
import static com.gentics.mesh.core.data.node.field.RestGetters.DATE_LIST_GETTER;
import static com.gentics.mesh.core.data.node.field.RestGetters.HTML_GETTER;
import static com.gentics.mesh.core.data.node.field.RestGetters.HTML_LIST_GETTER;
+import static com.gentics.mesh.core.data.node.field.RestGetters.JSON_GETTER;
+import static com.gentics.mesh.core.data.node.field.RestGetters.JSON_LIST_GETTER;
import static com.gentics.mesh.core.data.node.field.RestGetters.MICRONODE_GETTER;
import static com.gentics.mesh.core.data.node.field.RestGetters.MICRONODE_LIST_GETTER;
import static com.gentics.mesh.core.data.node.field.RestGetters.NODE_GETTER;
@@ -23,6 +25,8 @@
import static com.gentics.mesh.core.data.node.field.RestTransformers.DATE_TRANSFORMER;
import static com.gentics.mesh.core.data.node.field.RestTransformers.HTML_LIST_TRANSFORMER;
import static com.gentics.mesh.core.data.node.field.RestTransformers.HTML_TRANSFORMER;
+import static com.gentics.mesh.core.data.node.field.RestTransformers.JSON_LIST_TRANSFORMER;
+import static com.gentics.mesh.core.data.node.field.RestTransformers.JSON_TRANSFORMER;
import static com.gentics.mesh.core.data.node.field.RestTransformers.MICRONODE_LIST_TRANSFORMER;
import static com.gentics.mesh.core.data.node.field.RestTransformers.MICRONODE_TRANSFORMER;
import static com.gentics.mesh.core.data.node.field.RestTransformers.NODE_LIST_TRANSFORMER;
@@ -39,6 +43,8 @@
import static com.gentics.mesh.core.data.node.field.RestUpdaters.DATE_UPDATER;
import static com.gentics.mesh.core.data.node.field.RestUpdaters.HTML_LIST_UPDATER;
import static com.gentics.mesh.core.data.node.field.RestUpdaters.HTML_UPDATER;
+import static com.gentics.mesh.core.data.node.field.RestUpdaters.JSON_LIST_UPDATER;
+import static com.gentics.mesh.core.data.node.field.RestUpdaters.JSON_UPDATER;
import static com.gentics.mesh.core.data.node.field.RestUpdaters.MICRONODE_LIST_UPDATER;
import static com.gentics.mesh.core.data.node.field.RestUpdaters.MICRONODE_UPDATER;
import static com.gentics.mesh.core.data.node.field.RestUpdaters.NODE_LIST_UPDATER;
@@ -83,6 +89,8 @@ public enum HibFieldTypes {
BOOLEAN_LIST("list.boolean", BOOLEAN_LIST_TRANSFORMER, BOOLEAN_LIST_UPDATER, BOOLEAN_LIST_GETTER),
HTML("html", HTML_TRANSFORMER, HTML_UPDATER, HTML_GETTER),
HTML_LIST("list.html", HTML_LIST_TRANSFORMER, HTML_LIST_UPDATER, HTML_LIST_GETTER),
+ JSON("json", JSON_TRANSFORMER, JSON_UPDATER, JSON_GETTER),
+ JSON_LIST("list.json", JSON_LIST_TRANSFORMER, JSON_LIST_UPDATER, JSON_LIST_GETTER),
MICRONODE("micronode", MICRONODE_TRANSFORMER, MICRONODE_UPDATER, MICRONODE_GETTER),
MICRONODE_LIST("list.micronode", MICRONODE_LIST_TRANSFORMER, MICRONODE_LIST_UPDATER, MICRONODE_LIST_GETTER),
NODE("node", NODE_TRANSFORMER, NODE_UPDATER, NODE_GETTER),
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/impl/HibJsonFieldImpl.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/impl/HibJsonFieldImpl.java
new file mode 100644
index 0000000000..8365ce2e7f
--- /dev/null
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/impl/HibJsonFieldImpl.java
@@ -0,0 +1,34 @@
+package com.gentics.mesh.hibernate.data.node.field.impl;
+
+import com.gentics.mesh.core.data.node.field.HibJsonField;
+import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.hibernate.data.domain.HibUnmanagedFieldContainer;
+
+/**
+ * JSON object field of Hibernate content.
+ *
+ * @author plyhun
+ *
+ */
+public class HibJsonFieldImpl extends AbstractBasicHibField implements HibJsonField {
+
+ public HibJsonFieldImpl(String fieldKey, HibUnmanagedFieldContainer, ?, ?, ?, ?> parent, JsonContent value) {
+ super(fieldKey, parent, FieldTypes.JSON, value);
+ }
+
+ @Override
+ public JsonContent getJson() {
+ return valueOrNull();
+ }
+
+ @Override
+ public void setJson(JsonContent string) {
+ storeValue(string);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return jsonEquals(obj);
+ }
+}
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/impl/HibJsonListFieldImpl.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/impl/HibJsonListFieldImpl.java
new file mode 100644
index 0000000000..f2c36984f9
--- /dev/null
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/data/node/field/impl/HibJsonListFieldImpl.java
@@ -0,0 +1,88 @@
+package com.gentics.mesh.hibernate.data.node.field.impl;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.IntStream;
+
+import com.gentics.mesh.core.data.HibField;
+import com.gentics.mesh.core.data.HibFieldContainer;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
+import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
+import com.gentics.mesh.database.HibernateTx;
+import com.gentics.mesh.hibernate.data.domain.HibJsonListFieldEdgeImpl;
+import com.gentics.mesh.hibernate.data.domain.HibUnmanagedFieldContainer;
+
+/**
+ * JSON object list field implementation.
+ *
+ * @author plyhun
+ *
+ */
+public class HibJsonListFieldImpl extends
+ AbstractHibHeterogenicPrimitiveListFieldImpl
+ implements HibJsonFieldList {
+
+ protected HibJsonListFieldImpl(HibernateTx tx, String fieldKey, HibUnmanagedFieldContainer, ?, ?, ?, ?> parent) {
+ super(tx, fieldKey, parent, HibJsonListFieldEdgeImpl.class);
+ }
+
+ public HibJsonListFieldImpl(String fieldKey, HibUnmanagedFieldContainer, ?, ?, ?, ?> parent, UUID initialValue) {
+ super(initialValue, fieldKey, parent, HibJsonListFieldEdgeImpl.class);
+ }
+
+ @Override
+ public HibField cloneTo(HibFieldContainer container) {
+ HibernateTx tx = HibernateTx.get();
+ HibUnmanagedFieldContainer, ?, ?, ?, ?> unmanagedBase = (HibUnmanagedFieldContainer, ?, ?, ?, ?>) container;
+ unmanagedBase.ensureColumnExists(getFieldKey(), FieldTypes.LIST);
+ unmanagedBase.ensureOldReferenceRemoved(tx, getFieldKey(), unmanagedBase::getJsonList, false);
+ return HibJsonListFieldImpl.fromContainer(tx, unmanagedBase, getFieldKey(), getValues());
+ }
+
+ @Override
+ public HibJsonField createJson(JsonContent value) {
+ return createItem(value);
+ }
+
+ @Override
+ public void createJsons(List items) {
+ createItems(items);
+ }
+
+ @Override
+ public HibJsonField getJson(int index) {
+ return get(index);
+ }
+
+ /**
+ * Make an edge for the given container, field key and values.
+ *
+ * @param tx
+ * @param container
+ * @param fieldKey
+ * @param values
+ * @return
+ */
+ public static HibJsonListFieldImpl fromContainer(HibernateTx tx,
+ HibUnmanagedFieldContainer, ?, ?, ?, ?> container, String fieldKey, List values) {
+ HibJsonListFieldImpl list = new HibJsonListFieldImpl(tx, fieldKey, container);
+ IntStream.range(0, values.size()).mapToObj(
+ i -> new HibJsonListFieldEdgeImpl(tx, list.valueOrNull(), i, fieldKey, values.get(i), container))
+ .forEach(item -> tx.entityManager().persist(item));
+ return list;
+ }
+
+ @Override
+ protected JsonContent getValue(HibJsonField field) {
+ return field.getJson();
+ }
+
+ @Override
+ protected HibListFieldItemConstructor getItemConstructor() {
+ return HibJsonListFieldEdgeImpl::new;
+ }
+
+}
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/type/CustomTypeContributor.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/type/CustomTypeContributor.java
new file mode 100644
index 0000000000..44301ca97a
--- /dev/null
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/type/CustomTypeContributor.java
@@ -0,0 +1,16 @@
+package com.gentics.mesh.hibernate.type;
+
+import org.hibernate.boot.model.TypeContributions;
+import org.hibernate.boot.model.TypeContributor;
+import org.hibernate.service.ServiceRegistry;
+
+/**
+ * Custom Mesh SQL/Java type contributor.
+ */
+public class CustomTypeContributor implements TypeContributor {
+
+ @Override
+ public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
+ typeContributions.contributeType(JsonContentType.INSTANCE);
+ }
+}
diff --git a/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/type/JsonContentType.java b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/type/JsonContentType.java
new file mode 100644
index 0000000000..d7122a6109
--- /dev/null
+++ b/mdm/hibernate-core/src/main/java/com/gentics/mesh/hibernate/type/JsonContentType.java
@@ -0,0 +1,80 @@
+package com.gentics.mesh.hibernate.type;
+
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.type.SqlTypes;
+import org.hibernate.usertype.UserType;
+
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.json.JsonUtil;
+
+/**
+ * JSON content mapping type
+ */
+public class JsonContentType implements UserType {
+
+ public static final JsonContentType INSTANCE = new JsonContentType();
+
+ @Override
+ public JsonContent nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ final String cellContent = rs.getString(position);
+ if (cellContent == null) {
+ return null;
+ }
+ try {
+ return JsonUtil.getMapper().readValue(cellContent.getBytes("UTF-8"), returnedClass());
+ } catch (final Exception ex) {
+ throw new RuntimeException("Failed to convert String to JsonObject: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public Serializable disassemble(JsonContent value) {
+ if (value == null) {
+ return null;
+ }
+ return JsonUtil.toJson(value);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, JsonContent value, int index, SharedSessionContractImplementor session) throws SQLException {
+ if (value == null) {
+ st.setNull(index, SqlTypes.LONGVARCHAR);
+ return;
+ }
+ try {
+ final StringWriter w = new StringWriter();
+ JsonUtil.getMapper().writeValue(w, value);
+ w.flush();
+ st.setObject(index, w.toString(), SqlTypes.LONGVARCHAR);
+ } catch (final Exception ex) {
+ throw new RuntimeException("Failed to convert JsonObject to String: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public JsonContent deepCopy(JsonContent value) {
+ String value1 = JsonUtil.toJson(value);
+ return JsonUtil.readValue(value1, JsonContent.class);
+ }
+
+ @Override
+ public int getSqlType() {
+ return SqlTypes.JSON;
+ }
+
+ @Override
+ public Class returnedClass() {
+ return JsonContent.class;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return true;
+ }
+}
diff --git a/mdm/hibernate-core/src/main/resources/META-INF/persistence.xml b/mdm/hibernate-core/src/main/resources/META-INF/persistence.xml
index 36b0aee30f..b00620ac2d 100644
--- a/mdm/hibernate-core/src/main/resources/META-INF/persistence.xml
+++ b/mdm/hibernate-core/src/main/resources/META-INF/persistence.xml
@@ -18,6 +18,7 @@
com.gentics.mesh.hibernate.data.domain.HibDateListFieldEdgeImpl
com.gentics.mesh.hibernate.data.domain.HibFieldTypeChangeImpl
com.gentics.mesh.hibernate.data.domain.HibGroupImpl
+ com.gentics.mesh.hibernate.data.domain.HibJsonListFieldEdgeImpl
com.gentics.mesh.hibernate.data.domain.HibHtmlListFieldEdgeImpl
com.gentics.mesh.hibernate.data.domain.HibImageVariantImpl
com.gentics.mesh.hibernate.data.domain.HibJobImpl
diff --git a/mdm/hibernate-core/src/main/resources/META-INF/services/org.hibernate.boot.model.TypeContributor b/mdm/hibernate-core/src/main/resources/META-INF/services/org.hibernate.boot.model.TypeContributor
new file mode 100644
index 0000000000..da329c7082
--- /dev/null
+++ b/mdm/hibernate-core/src/main/resources/META-INF/services/org.hibernate.boot.model.TypeContributor
@@ -0,0 +1 @@
+com.gentics.mesh.hibernate.type.CustomTypeContributor
\ No newline at end of file
diff --git a/rest-model/pom.xml b/rest-model/pom.xml
index f60789fc2f..fe6f352d89 100644
--- a/rest-model/pom.xml
+++ b/rest-model/pom.xml
@@ -56,6 +56,10 @@
commons-io
commons-io
+
+ io.vertx
+ vertx-json-schema
+
org.raml
raml-parser
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/JsonSchema.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/JsonSchema.java
new file mode 100644
index 0000000000..b6ad4f4502
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/JsonSchema.java
@@ -0,0 +1,81 @@
+package com.gentics.mesh.core.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.gentics.mesh.core.rest.common.RestModel;
+import com.gentics.mesh.json.JsonUtil;
+
+import io.vertx.core.json.JsonObject;
+
+/**
+ * A wrapper of serializable {@link JsonObject} to produce {@link io.vertx.reactivex.json.schema.JsonSchema}s out of it.
+ */
+public class JsonSchema extends JsonSchemaType {
+
+ private String[] required = new String[0];
+ private Map properties = new HashMap<>();
+
+
+ public JsonSchema() {
+ super();
+ }
+
+ public JsonSchema(JsonObject object) {
+ super((object == null || object.getString("type") == null) ? "object" : object.getString("type"));
+ setRequired((object == null || object.getJsonArray("required") == null)
+ ? new String[0]
+ : object.getJsonArray("required").stream().map(Object::toString).toArray(size -> new String[size]));
+ setProperties((object == null || object.getJsonObject("properties") == null)
+ ? new HashMap<>()
+ : object.getJsonObject("properties").getMap().entrySet().stream()
+ .map(e -> Pair.of(e.getKey(), JsonUtil.readValue(JsonUtil.toJson(e.getValue()), JsonSchemaType.class)))
+ .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
+ }
+ public JsonSchema(String json) {
+ this(json == null ? null : new JsonObject(json));
+ }
+
+ @Override
+ public JsonSchema setType(String type) {
+ super.setType(type);
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof RestModel) ? JsonUtil.equals(this, (RestModel) obj) : false;
+ }
+
+ @Override
+ public int hashCode() {
+ return JsonUtil.toJson(this).hashCode();
+ }
+
+ @JsonIgnore
+ public io.vertx.reactivex.json.schema.JsonSchema getVertxSchema() {
+ return io.vertx.reactivex.json.schema.JsonSchema.of(new JsonObject(toJson()));
+ }
+
+ public String[] getRequired() {
+ return required;
+ }
+
+ public JsonSchema setRequired(String[] required) {
+ this.required = required;
+ return this;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+ public JsonSchema setProperties(Map properties) {
+ this.properties = properties;
+ return this;
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/JsonSchemaType.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/JsonSchemaType.java
new file mode 100644
index 0000000000..980cc4755d
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/JsonSchemaType.java
@@ -0,0 +1,27 @@
+package com.gentics.mesh.core.rest;
+
+import com.gentics.mesh.core.rest.common.RestModel;
+
+/**
+ * Serializable JSON type container.
+ */
+public class JsonSchemaType implements RestModel {
+
+ private String type = "object";
+
+ public JsonSchemaType() {
+ }
+
+ public JsonSchemaType(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public JsonSchemaType setType(String type) {
+ this.type = type;
+ return this;
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/common/FieldTypes.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/common/FieldTypes.java
index 38977a2337..06f4899a19 100644
--- a/rest-model/src/main/java/com/gentics/mesh/core/rest/common/FieldTypes.java
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/common/FieldTypes.java
@@ -16,6 +16,8 @@ public enum FieldTypes {
HTML(HtmlFieldSchema.class, HtmlFieldSchemaImpl.class, HtmlField.class, HtmlFieldImpl.class),
+ JSON(JsonFieldSchema.class, JsonFieldSchemaImpl.class, JsonField.class, JsonFieldImpl.class),
+
NUMBER(NumberFieldSchema.class, NumberFieldSchemaImpl.class, NumberField.class, NumberFieldImpl.class),
DATE(DateFieldSchema.class, DateFieldSchemaImpl.class, DateField.class, DateFieldImpl.class),
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMap.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMap.java
index 77905d5395..c718072e7b 100644
--- a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMap.java
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMap.java
@@ -15,6 +15,7 @@
import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.MicronodeFieldList;
@@ -22,6 +23,7 @@
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.StringFieldListImpl;
import com.gentics.mesh.core.rest.schema.FieldSchema;
@@ -106,6 +108,22 @@ default Field putString(String fieldKey, String string) {
*/
NumberFieldListImpl getNumberFieldList(String fieldKey);
+ /**
+ * Return the JSON object field with the given key
+ *
+ * @param fieldKey
+ * @return
+ */
+ JsonFieldImpl getJsonField(String fieldKey);
+
+ /**
+ * Return the JSON object list field with the given key.
+ *
+ * @param fieldKey
+ * @return
+ */
+ JsonFieldListImpl getJsonFieldList(String fieldKey);
+
/**
* Return the html field with the given key
*
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMapImpl.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMapImpl.java
index f10ef7899c..e865b53611 100644
--- a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMapImpl.java
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/FieldMapImpl.java
@@ -21,14 +21,34 @@
import com.fasterxml.jackson.databind.node.POJONode;
import com.gentics.mesh.core.rest.common.FieldTypes;
import com.gentics.mesh.core.rest.micronode.MicronodeResponse;
-import com.gentics.mesh.core.rest.node.field.*;
-import com.gentics.mesh.core.rest.node.field.impl.*;
+import com.gentics.mesh.core.rest.node.field.BinaryField;
+import com.gentics.mesh.core.rest.node.field.BooleanField;
+import com.gentics.mesh.core.rest.node.field.DateField;
+import com.gentics.mesh.core.rest.node.field.Field;
+import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
+import com.gentics.mesh.core.rest.node.field.NodeField;
+import com.gentics.mesh.core.rest.node.field.NodeFieldListItem;
+import com.gentics.mesh.core.rest.node.field.NumberField;
+import com.gentics.mesh.core.rest.node.field.S3BinaryField;
+import com.gentics.mesh.core.rest.node.field.StringField;
+import com.gentics.mesh.core.rest.node.field.impl.BinaryFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.NodeFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.S3BinaryFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.FieldList;
import com.gentics.mesh.core.rest.node.field.list.MicronodeFieldList;
import com.gentics.mesh.core.rest.node.field.list.NodeFieldList;
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.MicronodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
@@ -39,6 +59,9 @@
import com.gentics.mesh.json.JsonUtil;
import com.google.common.collect.Lists;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
/**
* Implementation of a fieldmap which uses a central JsonNode to access the field specific data. Fields will be mapped during runtime.
*
@@ -83,6 +106,8 @@ public T getField(String key, FieldTypes type, String listType
return (T) transformNumberFieldJsonNode(jsonNode, key);
case BOOLEAN:
return (T) transformBooleanFieldJsonNode(jsonNode, key);
+ case JSON:
+ return (T) transformJsonFieldJsonNode(jsonNode, key);
case DATE:
return (T) transformDateFieldJsonNode(jsonNode, key);
case LIST:
@@ -104,8 +129,8 @@ private FieldList> transformListFieldJsonNode(JsonNode jsonNode, String key, S
ObjectMapper mapper = JsonUtil.getMapper();
// ListFieldSchemaImpl listFieldSchema = (ListFieldSchemaImpl) fieldSchema;
- switch (listType) {
- case "node":
+ switch (FieldTypes.valueByName(listType)) {
+ case NODE:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, NodeFieldList.class, key);
@@ -133,7 +158,7 @@ private FieldList> transformListFieldJsonNode(JsonNode jsonNode, String key, S
// throw new MeshJsonException("Could not read node field for key {" + fieldKey + "}", e);
// }
return nodeListField;
- case "micronode":
+ case MICRONODE:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, MicronodeFieldList.class, key);
@@ -144,35 +169,42 @@ private FieldList> transformListFieldJsonNode(JsonNode jsonNode, String key, S
}
return micronodeFieldList;
// Basic types
- case "string":
+ case STRING:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, StringFieldListImpl.class, key);
}
String[] itemsStringArray = mapper.treeToValue(jsonNode, String[].class);
return getBasicList(key, String[].class, new StringFieldListImpl(), String.class, itemsStringArray);
- case "html":
+ case HTML:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, HtmlFieldListImpl.class, key);
}
String[] itemsHtmlArray = mapper.treeToValue(jsonNode, String[].class);
return getBasicList(key, String[].class, new HtmlFieldListImpl(), String.class, itemsHtmlArray);
- case "date":
+ case DATE:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, DateFieldListImpl.class, key);
}
String[] itemsDateArray = mapper.treeToValue(jsonNode, String[].class);
return getBasicList(key, String[].class, new DateFieldListImpl(), String.class, itemsDateArray);
- case "number":
+ case JSON:
+ // Unwrap stored pojos
+ if (jsonNode.isPojo()) {
+ return pojoNodeToValue(jsonNode, JsonFieldListImpl.class, key);
+ }
+ JsonContent[] itemsJsonArray = mapper.treeToValue(jsonNode, JsonContent[].class);
+ return getBasicList(key, JsonObject[].class, new JsonFieldListImpl(), JsonContent.class, itemsJsonArray);
+ case NUMBER:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, NumberFieldListImpl.class, key);
}
Number[] itemsNumberArray = mapper.treeToValue(jsonNode, Number[].class);
return getBasicList(key, Number[].class, new NumberFieldListImpl(), Number.class, itemsNumberArray);
- case "boolean":
+ case BOOLEAN:
// Unwrap stored pojos
if (jsonNode.isPojo()) {
return pojoNodeToValue(jsonNode, BooleanFieldListImpl.class, key);
@@ -331,6 +363,30 @@ private BooleanField transformBooleanFieldJsonNode(JsonNode jsonNode, String key
return booleanField;
}
+ private JsonField transformJsonFieldJsonNode(JsonNode jsonNode, String key) {
+ // Unwrap stored pojos
+ if (jsonNode.isPojo()) {
+ JsonField field = pojoNodeToValue(jsonNode, JsonField.class, key);
+ if (field == null || field.getValue() == null) {
+ return null;
+ } else {
+ return field;
+ }
+ }
+
+ JsonField jsonField = new JsonFieldImpl();
+ if (!jsonNode.isNull()) {
+ if (jsonNode.isArray()) {
+ jsonField.setJson(JsonContent.fromArray(new JsonArray(jsonNode.toString())));
+ } else if (jsonNode.isObject()) {
+ jsonField.setJson(JsonContent.fromObject(new JsonObject(jsonNode.toString())));
+ } else {
+ throw error(BAD_REQUEST, "The field value for {" + key + "} is not a valid JSON. The value was {" + jsonNode.get("json") + "}");
+ }
+ }
+ return jsonField;
+ }
+
private NumberField transformNumberFieldJsonNode(JsonNode jsonNode, String key) {
// Unwrap stored pojos
if (jsonNode.isPojo()) {
@@ -445,6 +501,11 @@ public HtmlFieldImpl getHtmlField(String key) {
return getField(key, FieldTypes.HTML);
}
+ @Override
+ public JsonFieldImpl getJsonField(String key) {
+ return getField(key, FieldTypes.JSON);
+ }
+
@Override
public BinaryField getBinaryField(String key) {
return getField(key, FieldTypes.BINARY);
@@ -514,6 +575,11 @@ public NumberFieldListImpl getNumberFieldList(String key) {
return getField(key, FieldTypes.LIST, "number");
}
+ @Override
+ public JsonFieldListImpl getJsonFieldList(String key) {
+ return getField(key, FieldTypes.LIST, "json");
+ }
+
@Override
public BooleanFieldListImpl getBooleanFieldList(String key) {
return getField(key, FieldTypes.LIST, "boolean");
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/JsonContent.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/JsonContent.java
new file mode 100644
index 0000000000..89af6c4e28
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/JsonContent.java
@@ -0,0 +1,153 @@
+package com.gentics.mesh.core.rest.node.field;
+
+import java.util.Objects;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.gentics.mesh.json.JsonUtil;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+/**
+ * JSON POJO, common for JSON objects and arrays.
+ */
+public class JsonContent {
+
+ @JsonIgnore
+ private final String jsonString;
+
+ /**
+ * Inner ctor, accepting an already validated json content (array/object) string.
+ *
+ * @param jsonString
+ */
+ protected JsonContent(String jsonString) {
+ this.jsonString = jsonString;
+ }
+
+ /**
+ * Is this content a JSON array?
+ *
+ * @return
+ */
+ @JsonIgnore
+ public boolean isArray() {
+ return StringUtils.isNotBlank(jsonString) && jsonString.startsWith("[") && jsonString.endsWith("]");
+ }
+
+ /**
+ * Get either a {@link JsonObject} or {@link JsonArray} or null out of this content.
+ *
+ * @return
+ */
+ @JsonIgnore
+ public Object getContent() {
+ if (isArray()) {
+ return new JsonArray(jsonString);
+ } else if (StringUtils.isNotBlank(jsonString)) {
+ return new JsonObject(jsonString);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get a {@link JsonObject} of this content, if exists and applicable.
+ *
+ * @return
+ */
+ @JsonIgnore
+ public JsonObject getObject() {
+ if (isArray() || StringUtils.isBlank(jsonString)) {
+ return null;
+ } else {
+ return new JsonObject(jsonString);
+ }
+ }
+
+ /**
+ * Get a {@link JsonArray} of this content, if exists and applicable.
+ *
+ * @return
+ */
+ @JsonIgnore
+ public JsonArray getArray() {
+ if (isArray()) {
+ return new JsonArray(jsonString);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Utility method for creating a JsonObject from the JSON object.
+ *
+ * @param object
+ * @return
+ */
+ public static JsonContent fromObject(JsonObject object) {
+ return new JsonContent(JsonUtil.toJson(object));
+ }
+
+ /**
+ * Utility method for creating a JsonObject from the JSON array.
+ *
+ * @param array
+ * @return
+ */
+ public static JsonContent fromArray(JsonArray array) {
+ return new JsonContent(JsonUtil.toJson(array));
+ }
+
+ /**
+ * Get a string representation of the JSON content.
+ *
+ * @return
+ */
+ @JsonIgnore
+ public String getString() {
+ return jsonString;
+ }
+
+ /**
+ * Utility method for creating a JsonObject from the JSON object/array string representation. May return null.
+ *
+ * @param jsonString
+ * @return
+ */
+ public static JsonContent fromString(String jsonString) {
+ if (StringUtils.isNotBlank(jsonString)) {
+ if (jsonString.trim().startsWith("[")) {
+ return new JsonContent(JsonUtil.toJson(JsonUtil.readValue(jsonString, JsonArray.class), true));
+ } else {
+ return new JsonContent(JsonUtil.toJson(JsonUtil.readValue(jsonString, JsonObject.class), true));
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @JsonIgnore
+ public boolean equals(Object obj) {
+ if (obj instanceof JsonContent jc) {
+ boolean meArray = isArray();
+ boolean itArray = jc.isArray();
+ if (meArray ^ itArray) {
+ return false;
+ } else if (meArray) {
+ return Objects.equals(getArray(), jc.getArray());
+ } else {
+ return Objects.equals(getObject(), jc.getObject());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @JsonIgnore
+ public int hashCode() {
+ return isArray() ? getArray().hashCode() : Objects.hashCode(getObject());
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/JsonField.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/JsonField.java
new file mode 100644
index 0000000000..bd9dcedf06
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/JsonField.java
@@ -0,0 +1,39 @@
+package com.gentics.mesh.core.rest.node.field;
+
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
+
+/**
+ * REST POJO for the JSON formatted information.
+ */
+public interface JsonField extends ListableField, MicroschemaListableField {
+
+ /**
+ * Get the stored JSON object
+ *
+ * @return
+ */
+ JsonContent getJson();
+
+ /**
+ * Store the given JSON object
+ *
+ * @param json
+ * @return
+ */
+ JsonField setJson(JsonContent json);
+
+ @Override
+ default Object getValue() {
+ return getJson();
+ }
+
+ /**
+ * Shortcut for the fast REST model field creation.
+ *
+ * @param json
+ * @return
+ */
+ static JsonField of(JsonContent json) {
+ return new JsonFieldImpl().setJson(json);
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/impl/JsonFieldImpl.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/impl/JsonFieldImpl.java
new file mode 100644
index 0000000000..fcc9cb7787
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/impl/JsonFieldImpl.java
@@ -0,0 +1,37 @@
+package com.gentics.mesh.core.rest.node.field.impl;
+
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
+import com.gentics.mesh.json.JsonUtil;
+
+/**
+ * @see JsonField
+ */
+public class JsonFieldImpl implements JsonField {
+
+ @JsonPropertyDescription("JSON field value")
+ private JsonContent json;
+
+ @Override
+ public String getType() {
+ return FieldTypes.JSON.toString();
+ }
+
+ @Override
+ public JsonContent getJson() {
+ return json;
+ }
+
+ @Override
+ public JsonField setJson(JsonContent json) {
+ this.json = json;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return JsonUtil.toJson(getJson());
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/list/impl/JsonFieldListImpl.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/list/impl/JsonFieldListImpl.java
new file mode 100644
index 0000000000..115b4bf53d
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/node/field/list/impl/JsonFieldListImpl.java
@@ -0,0 +1,16 @@
+package com.gentics.mesh.core.rest.node.field.list.impl;
+
+import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.FieldMap;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+
+/**
+ * REST model for a JSON object list field. Please note that {@link FieldMap} will handle the actual JSON format building.
+ */
+public class JsonFieldListImpl extends AbstractFieldList {
+
+ @Override
+ public String getItemType() {
+ return FieldTypes.JSON.toString();
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/schema/JsonFieldSchema.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/schema/JsonFieldSchema.java
new file mode 100644
index 0000000000..a4a193b082
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/schema/JsonFieldSchema.java
@@ -0,0 +1,25 @@
+package com.gentics.mesh.core.rest.schema;
+
+import com.gentics.mesh.core.rest.JsonSchema;
+
+/**
+ * REST POJO for a JSON object field schema.
+ */
+public interface JsonFieldSchema extends FieldSchema {
+
+ /**
+ * Return a list of values which are allowed for this field. Null if no value restriction set
+ *
+ * @return Allowed values
+ */
+ JsonSchema[] getAllowedSchemas();
+
+ /**
+ * Set the list of values which are allowed for this field. Set to null to remove value restriction
+ *
+ * @param allowedSchemas
+ * Allowed values or null
+ * @return Fluent API
+ */
+ JsonFieldSchema setAllowedSchemas(JsonSchema... allowedSchemas);
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/core/rest/schema/impl/JsonFieldSchemaImpl.java b/rest-model/src/main/java/com/gentics/mesh/core/rest/schema/impl/JsonFieldSchemaImpl.java
new file mode 100644
index 0000000000..db90472e7a
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/core/rest/schema/impl/JsonFieldSchemaImpl.java
@@ -0,0 +1,68 @@
+package com.gentics.mesh.core.rest.schema.impl;
+
+import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel.ALLOW_KEY;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.gentics.mesh.core.rest.JsonSchema;
+import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
+
+import io.vertx.core.json.JsonArray;
+
+/**
+ * @see JsonFieldSchema
+ */
+public class JsonFieldSchemaImpl extends AbstractFieldSchema implements JsonFieldSchema {
+
+ @JsonProperty("allow")
+ private JsonSchema[] allowedSchemas;
+
+ @Override
+ public String getType() {
+ return FieldTypes.JSON.toString();
+ }
+
+ @Override
+ public JsonSchema[] getAllowedSchemas() {
+ return allowedSchemas;
+ }
+
+ @Override
+ public JsonFieldSchemaImpl setAllowedSchemas(JsonSchema... allowedSchemas) {
+ this.allowedSchemas = allowedSchemas;
+ return this;
+ }
+
+ @Override
+ public Map getAllChangeProperties() {
+ Map map = super.getAllChangeProperties();
+ map.put(ALLOW_KEY, getAllowedSchemas());
+ return map;
+ }
+
+ @Override
+ public void apply(Map fieldProperties) {
+ super.apply(fieldProperties);
+ Object allowedValues = fieldProperties.get(ALLOW_KEY);
+ if (allowedValues != null) {
+ if (allowedValues instanceof JsonSchema[] allowedSchemas) {
+ setAllowedSchemas(allowedSchemas);
+ } else if (allowedValues instanceof String[]) {
+ String[] values = (String[]) allowedValues;
+ setAllowedSchemas(Arrays.stream(values).map(JsonSchema::new).toArray(size -> new JsonSchema[size]));
+ } else if (allowedValues instanceof Collection) {
+ setAllowedSchemas(((Collection>) allowedValues).stream().map(Object::toString).map(JsonSchema::new).toArray(size -> new JsonSchema[size]));
+ } else if (allowedValues instanceof Object[]) {
+ setAllowedSchemas(Arrays.stream(((Object[]) allowedValues)).map(Object::toString).map(JsonSchema::new).toArray(size -> new JsonSchema[size]));
+ } else if (allowedValues instanceof JsonArray) {
+ setAllowedSchemas(((JsonArray) allowedValues).stream().map(Object::toString).map(JsonSchema::new).toArray(size -> new JsonSchema[size]));
+ } else {
+ throw new IllegalStateException("Unsupported allowed value type: " + allowedValues.getClass().getCanonicalName());
+ }
+ }
+ }
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/json/JsonUtil.java b/rest-model/src/main/java/com/gentics/mesh/json/JsonUtil.java
index 9b495f6ab5..b3081c096c 100644
--- a/rest-model/src/main/java/com/gentics/mesh/json/JsonUtil.java
+++ b/rest-model/src/main/java/com/gentics/mesh/json/JsonUtil.java
@@ -4,8 +4,11 @@
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import java.io.IOException;
+import java.util.Comparator;
import org.codehaus.jettison.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -25,6 +28,7 @@
import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
+import com.gentics.mesh.core.rest.common.RestModel;
import com.gentics.mesh.core.rest.error.AbstractRestException;
import com.gentics.mesh.core.rest.error.GenericRestException;
import com.gentics.mesh.core.rest.event.EventCauseInfo;
@@ -32,11 +36,13 @@
import com.gentics.mesh.core.rest.microschema.impl.MicroschemaModelImpl;
import com.gentics.mesh.core.rest.node.FieldMap;
import com.gentics.mesh.core.rest.node.FieldMapImpl;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.node.field.ListableField;
import com.gentics.mesh.core.rest.node.field.NodeFieldListItem;
import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.FieldList;
@@ -54,6 +60,7 @@
import com.gentics.mesh.json.deserializer.FieldMapDeserializer;
import com.gentics.mesh.json.deserializer.FieldSchemaDeserializer;
import com.gentics.mesh.json.deserializer.JsonArrayDeserializer;
+import com.gentics.mesh.json.deserializer.JsonContentDeserializer;
import com.gentics.mesh.json.deserializer.JsonObjectDeserializer;
import com.gentics.mesh.json.deserializer.NodeFieldListItemDeserializer;
import com.gentics.mesh.json.deserializer.PermissionChangedEventModelDeserializer;
@@ -62,18 +69,41 @@
import com.gentics.mesh.json.serializer.BasicFieldSerializer;
import com.gentics.mesh.json.serializer.FieldListSerializer;
import com.gentics.mesh.json.serializer.JsonArraySerializer;
+import com.gentics.mesh.json.serializer.JsonContentSerializer;
import com.gentics.mesh.json.serializer.JsonObjectSerializer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import io.vertx.json.schema.Draft;
+import io.vertx.json.schema.JsonSchemaOptions;
+import io.vertx.reactivex.json.schema.JsonSchema;
+import io.vertx.reactivex.json.schema.Validator;
/**
* Main JSON Util which is used to register all custom JSON specific handlers and deserializers.
*/
public final class JsonUtil {
+ /**
+ * JSON object comparator
+ */
+ public static Comparator COMPARATOR = (a,b) -> {
+ if (a == null && b == null) {
+ return 0;
+ }
+ if (a == null) {
+ return -1;
+ }
+ if (b == null) {
+ return 1;
+ }
+ if (a.equals(b)) {
+ return 0;
+ } else {
+ return a.toString().compareTo(b.toString());
+ }
+ };
+
protected static ObjectMapper defaultMapper;
protected static JsonSchemaGenerator schemaGen;
protected static PrettyPrinter minifyingPrettyPrinter;
@@ -104,9 +134,11 @@ private static void initDefaultMapper() {
module.addSerializer(StringFieldImpl.class, new BasicFieldSerializer());
module.addSerializer(DateFieldImpl.class, new BasicFieldSerializer());
module.addSerializer(BooleanFieldImpl.class, new BasicFieldSerializer());
+ module.addSerializer(JsonFieldImpl.class, new BasicFieldSerializer());
module.addSerializer(FieldList.class, new FieldListSerializer());
module.addSerializer(JsonObject.class, new JsonObjectSerializer());
module.addSerializer(JsonArray.class, new JsonArraySerializer());
+ module.addSerializer(JsonContent.class, new JsonContentSerializer());
module.addSerializer(FieldMapImpl.class, new JsonSerializer() {
@Override
@@ -123,6 +155,7 @@ public void serialize(FieldMapImpl value, JsonGenerator gen, SerializerProvider
module.addDeserializer(FieldSchema.class, new FieldSchemaDeserializer());
module.addDeserializer(EventCauseInfo.class, new EventCauseInfoDeserializer());
module.addDeserializer(PermissionChangedEventModel.class, new PermissionChangedEventModelDeserializer());
+ module.addDeserializer(JsonContent.class, new JsonContentDeserializer());
defaultMapper.registerModule(module);
defaultMapper.registerModule(new SimpleModule("interfaceMapping") {
@@ -253,6 +286,35 @@ public static String getJsonSchema(Class> clazz) {
}
}
+ /**
+ * Create new schema validator against the given input schema.
+ *
+ * @param schema
+ * @return
+ */
+ public static Validator newJsonSchemaValidator(JsonSchema schema) {
+ return Validator.create(schema, new JsonSchemaOptions().setBaseUri("https://gentics.com/mesh").setDraft(Draft.DRAFT202012));
+ }
+
+ /**
+ * Compare two {@link RestModel} implementor instances.
+ *
+ * @param
+ * @param
+ * @param a
+ * @param b
+ * @return
+ */
+ public static boolean equals(A a, B b) {
+ if (a == null && b == null) {
+ return true;
+ }
+ if (a != null && b != null) {
+ return new JsonObject(toJson(a)).equals(new JsonObject(toJson(b)));
+ }
+ return false;
+ }
+
/**
* Return the JSON object mapper.
*
diff --git a/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonContentDeserializer.java b/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonContentDeserializer.java
new file mode 100644
index 0000000000..02a19ebb64
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonContentDeserializer.java
@@ -0,0 +1,22 @@
+package com.gentics.mesh.json.deserializer;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+
+public class JsonContentDeserializer extends JsonDeserializer {
+
+ @Override
+ public JsonContent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
+ ObjectCodec oc = p.getCodec();
+ JsonNode node = oc.readTree(p);
+ return JsonContent.fromString(node.toString());
+ }
+
+}
diff --git a/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonObjectDeserializer.java b/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonObjectDeserializer.java
index b0b09e10ef..0e9b0c83fe 100644
--- a/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonObjectDeserializer.java
+++ b/rest-model/src/main/java/com/gentics/mesh/json/deserializer/JsonObjectDeserializer.java
@@ -22,5 +22,4 @@ public JsonObject deserialize(JsonParser jsonParser, DeserializationContext ctxt
JsonNode node = oc.readTree(jsonParser);
return new JsonObject(node.toString());
}
-
}
diff --git a/rest-model/src/main/java/com/gentics/mesh/json/serializer/BasicFieldSerializer.java b/rest-model/src/main/java/com/gentics/mesh/json/serializer/BasicFieldSerializer.java
index ce262a74f5..dfe50d3ad6 100644
--- a/rest-model/src/main/java/com/gentics/mesh/json/serializer/BasicFieldSerializer.java
+++ b/rest-model/src/main/java/com/gentics/mesh/json/serializer/BasicFieldSerializer.java
@@ -11,11 +11,13 @@
import com.gentics.mesh.core.rest.node.field.DateField;
import com.gentics.mesh.core.rest.node.field.Field;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonField;
import com.gentics.mesh.core.rest.node.field.NumberField;
import com.gentics.mesh.core.rest.node.field.StringField;
import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.schema.FieldSchema;
@@ -69,6 +71,14 @@ public void serialize(T value, JsonGenerator gen, SerializerProvider serializers
gen.writeBoolean(booleanField.getValue());
}
break;
+ case JSON:
+ JsonField jsonField = (JsonFieldImpl) value;
+ if (jsonField.getJson() == null) {
+ gen.writeNull();
+ } else {
+ gen.writePOJO(jsonField.getJson());
+ }
+ break;
case DATE:
DateField dateField = (DateFieldImpl) value;
if (dateField.getDate() == null) {
diff --git a/rest-model/src/main/java/com/gentics/mesh/json/serializer/JsonContentSerializer.java b/rest-model/src/main/java/com/gentics/mesh/json/serializer/JsonContentSerializer.java
new file mode 100644
index 0000000000..669746eb77
--- /dev/null
+++ b/rest-model/src/main/java/com/gentics/mesh/json/serializer/JsonContentSerializer.java
@@ -0,0 +1,19 @@
+package com.gentics.mesh.json.serializer;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+
+/**
+ * Custom JSON serializer for Vert.x {@link JsonContent}.
+ */
+public class JsonContentSerializer extends JsonSerializer {
+
+ @Override
+ public void serialize(JsonContent value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
+ jgen.writeObject(value.isArray() ? value.getArray() : value.getObject());
+ }
+}
diff --git a/tests/common/src/main/java/com/gentics/mesh/assertj/impl/MicronodeResponseAssert.java b/tests/common/src/main/java/com/gentics/mesh/assertj/impl/MicronodeResponseAssert.java
index fb5fb84e06..f9210bee29 100644
--- a/tests/common/src/main/java/com/gentics/mesh/assertj/impl/MicronodeResponseAssert.java
+++ b/tests/common/src/main/java/com/gentics/mesh/assertj/impl/MicronodeResponseAssert.java
@@ -5,6 +5,7 @@
import org.assertj.core.api.AbstractAssert;
+import com.gentics.mesh.core.rest.common.FieldTypes;
import com.gentics.mesh.core.rest.micronode.MicronodeResponse;
import com.gentics.mesh.core.rest.node.field.Field;
import com.gentics.mesh.core.rest.node.field.list.FieldList;
@@ -26,41 +27,47 @@ public MicronodeResponseAssert matches(MicronodeResponse expected, MicroschemaMo
for (FieldSchema fieldSchema : schema.getFields()) {
String key = fieldSchema.getName();
- switch (fieldSchema.getType()) {
- case "html":
+ switch (FieldTypes.valueByName(fieldSchema.getType())) {
+ case HTML:
assertThat(expected.getFields().getHtmlField(key)).isNotNull();
assertThat(actual.getFields().getHtmlField(key).getHTML()).as("Field " + key)
.isEqualTo(expected.getFields().getHtmlField(key).getHTML());
break;
- case "binary":
+ case MICRONODE:
+ case S3BINARY:
+ case BINARY:
break;
- case "boolean":
+ case BOOLEAN:
assertThat(expected.getFields().getBooleanField(key)).isNotNull();
assertThat(actual.getFields().getBooleanField(key).getValue()).as("Field " + key)
.isEqualTo(expected.getFields().getBooleanField(key).getValue());
break;
- case "date":
+ case DATE:
assertThat(expected.getFields().getDateField(key)).isNotNull();
assertThat(actual.getFields().getDateField(key).getDate()).as("Field " + key)
.isEqualTo(expected.getFields().getDateField(key).getDate());
break;
- case "node":
+ case NODE:
assertThat(expected.getFields().getNodeField(key)).isNotNull();
assertThat(actual.getFields().getNodeField(key).getUuid()).as("Field " + key)
.isEqualTo(expected.getFields().getNodeField(key).getUuid());
break;
- case "string":
+ case STRING:
assertThat(expected.getFields().getStringField(key)).isNotNull();
assertThat(actual.getFields().getStringField(key).getString()).as("Field " + key)
.isEqualTo(expected.getFields().getStringField(key).getString());
break;
- case "number":
+ case NUMBER:
assertThat(expected.getFields().getNumberField(key)).isNotNull();
assertThat(actual.getFields().getNumberField(key).getNumber().doubleValue()).as("Field " + key)
.isEqualTo(expected.getFields().getNumberField(key).getNumber().doubleValue());
break;
- case "list":
-
+ case JSON:
+ assertThat(expected.getFields().getJsonField(key)).isNotNull();
+ assertThat(actual.getFields().getJsonField(key).getJson()).as("Field " + key)
+ .isEqualTo(expected.getFields().getJsonField(key).getJson());
+ break;
+ case LIST:
Field field = actual.getFields().getField(key, fieldSchema);
if (field instanceof NodeFieldList) {
// compare list of nodes by comparing their uuids
@@ -72,9 +79,8 @@ public MicronodeResponseAssert matches(MicronodeResponse expected, MicroschemaMo
// assertThat(((FieldList>) field).getItems())
// .containsExactlyElementsOf(expected.getFields().get(key, FieldList.class).getItems());
}
-
+ break;
}
-
}
return this;
diff --git a/tests/common/src/main/java/com/gentics/mesh/test/context/TestHelper.java b/tests/common/src/main/java/com/gentics/mesh/test/context/TestHelper.java
index 569dd3dccb..0ecbe5dad9 100644
--- a/tests/common/src/main/java/com/gentics/mesh/test/context/TestHelper.java
+++ b/tests/common/src/main/java/com/gentics/mesh/test/context/TestHelper.java
@@ -83,6 +83,7 @@
import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NodeFieldSchemaImpl;
@@ -1015,6 +1016,8 @@ static FieldSchema fieldIntoSchema(Field field) {
return new BinaryFieldSchemaImpl();
case BOOLEAN:
return new BooleanFieldSchemaImpl();
+ case JSON:
+ return new JsonFieldSchemaImpl();
case DATE:
return new DateFieldSchemaImpl();
case HTML:
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/AbstractComparatorJsonTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/AbstractComparatorJsonTest.java
new file mode 100644
index 0000000000..79b0031dba
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/AbstractComparatorJsonTest.java
@@ -0,0 +1,72 @@
+package com.gentics.mesh.core.data.fieldhandler;
+
+import static com.gentics.mesh.assertj.MeshAssertions.assertThat;
+import static com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeOperation.UPDATEFIELD;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.gentics.mesh.FieldUtil;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
+import com.gentics.mesh.core.rest.schema.FieldSchemaContainer;
+import com.gentics.mesh.core.rest.schema.change.impl.SchemaChangeModel;
+
+public abstract class AbstractComparatorJsonTest extends AbstractSchemaComparatorTest {
+
+ @Override
+ public JsonFieldSchema createField(String fieldName) {
+ return FieldUtil.createJsonFieldSchema("test");
+ }
+
+ @Test
+ @Override
+ public void testSameField() throws IOException {
+ C containerA = createContainer();
+ containerA.setName("test");
+ C containerB = createContainer();
+ containerB.setName("test");
+
+ JsonFieldSchema fieldA = FieldUtil.createJsonFieldSchema("test");
+ fieldA.setLabel("label1");
+ fieldA.setRequired(true);
+ containerA.addField(fieldA);
+
+ JsonFieldSchema fieldB = FieldUtil.createJsonFieldSchema("test");
+ fieldB.setRequired(true);
+ fieldB.setLabel("label1");
+ containerB.addField(fieldB);
+
+ List changes = getComparator().diff(containerA, containerB);
+ assertThat(changes).hasSize(0);
+ }
+
+ @Test
+ @Override
+ public void testUpdateField() throws IOException {
+ C containerA = createContainer();
+ containerA.setName("test");
+ C containerB = createContainer();
+ containerB.setName("test");
+
+ JsonFieldSchema fieldA = FieldUtil.createJsonFieldSchema("test");
+ fieldA.setLabel("label1");
+ fieldA.setRequired(true);
+ containerA.addField(fieldA);
+
+ JsonFieldSchema fieldB = FieldUtil.createJsonFieldSchema("test");
+ fieldB.setLabel("label2");
+ containerB.addField(fieldB);
+
+ // required flag:
+ fieldB.setRequired(false);
+ List changes = getComparator().diff(containerA, containerB);
+ assertThat(changes).hasSize(1);
+ assertThat(changes.get(0)).is(UPDATEFIELD).forField("test").hasProperty("required", false).hasProperty("label", "label2");
+ assertThat(changes.get(0).getProperties()).hasSize(3);
+
+ }
+
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/microschema/MicroschemaComparatorJsonTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/microschema/MicroschemaComparatorJsonTest.java
new file mode 100644
index 0000000000..89b8ccf968
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/microschema/MicroschemaComparatorJsonTest.java
@@ -0,0 +1,25 @@
+package com.gentics.mesh.core.data.fieldhandler.microschema;
+
+import static com.gentics.mesh.test.TestSize.FULL;
+
+import com.gentics.mesh.FieldUtil;
+import com.gentics.mesh.core.data.fieldhandler.AbstractComparatorJsonTest;
+import com.gentics.mesh.core.data.schema.handler.AbstractFieldSchemaContainerComparator;
+import com.gentics.mesh.core.data.schema.handler.MicroschemaComparatorImpl;
+import com.gentics.mesh.core.rest.schema.MicroschemaModel;
+import com.gentics.mesh.test.MeshTestSetting;
+
+@MeshTestSetting(testSize = FULL, startServer = false)
+public class MicroschemaComparatorJsonTest extends AbstractComparatorJsonTest {
+
+ @Override
+ public AbstractFieldSchemaContainerComparator getComparator() {
+ return new MicroschemaComparatorImpl();
+ }
+
+ @Override
+ public MicroschemaModel createContainer() {
+ return FieldUtil.createMinimalValidMicroschema();
+ }
+
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/schema/SchemaComparatorJsonTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/schema/SchemaComparatorJsonTest.java
new file mode 100644
index 0000000000..7eaf1862bd
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/data/fieldhandler/schema/SchemaComparatorJsonTest.java
@@ -0,0 +1,25 @@
+package com.gentics.mesh.core.data.fieldhandler.schema;
+
+import static com.gentics.mesh.test.TestSize.FULL;
+
+import com.gentics.mesh.FieldUtil;
+import com.gentics.mesh.core.data.fieldhandler.AbstractComparatorJsonTest;
+import com.gentics.mesh.core.data.schema.handler.AbstractFieldSchemaContainerComparator;
+import com.gentics.mesh.core.data.schema.handler.SchemaComparatorImpl;
+import com.gentics.mesh.core.rest.schema.SchemaModel;
+import com.gentics.mesh.test.MeshTestSetting;
+
+@MeshTestSetting(testSize = FULL, startServer = false)
+public class SchemaComparatorJsonTest extends AbstractComparatorJsonTest {
+
+ @Override
+ public AbstractFieldSchemaContainerComparator getComparator() {
+ return new SchemaComparatorImpl();
+ }
+
+ @Override
+ public SchemaModel createContainer() {
+ return FieldUtil.createMinimalValidSchema();
+ }
+
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/FieldSchemaCreator.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/FieldSchemaCreator.java
index 17df40ba5f..d96283721c 100644
--- a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/FieldSchemaCreator.java
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/FieldSchemaCreator.java
@@ -34,6 +34,8 @@ public interface FieldSchemaCreator {
public final static FieldSchemaCreator CREATENUMBERLIST = name -> FieldUtil.createListFieldSchema(name, "number");
public final static FieldSchemaCreator CREATESTRING = name -> FieldUtil.createStringFieldSchema(name);
public final static FieldSchemaCreator CREATESTRINGLIST = name -> FieldUtil.createListFieldSchema(name, "string");
+ public final static FieldSchemaCreator CREATEJSON = name -> FieldUtil.createJsonFieldSchema(name);
+ public final static FieldSchemaCreator CREATEJSONLIST = name -> FieldUtil.createListFieldSchema(name, "json");
FieldSchema create(String name);
}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldEndpointTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldEndpointTest.java
new file mode 100644
index 0000000000..65f1ef6096
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldEndpointTest.java
@@ -0,0 +1,224 @@
+package com.gentics.mesh.core.field.json;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.gentics.mesh.core.data.HibNodeFieldContainer;
+import com.gentics.mesh.core.data.dao.ContentDao;
+import com.gentics.mesh.core.data.node.HibNode;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
+import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.field.AbstractFieldEndpointTest;
+import com.gentics.mesh.core.rest.JsonSchema;
+import com.gentics.mesh.core.rest.node.NodeResponse;
+import com.gentics.mesh.core.rest.node.field.Field;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
+import com.gentics.mesh.core.rest.schema.SchemaVersionModel;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
+import com.gentics.mesh.json.JsonUtil;
+import com.gentics.mesh.test.MeshTestSetting;
+import com.gentics.mesh.test.TestSize;
+import com.gentics.mesh.util.VersionNumber;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.json.JsonObject;
+
+
+@MeshTestSetting(testSize = TestSize.PROJECT_AND_NODE, startServer = true)
+public class JsonFieldEndpointTest extends AbstractFieldEndpointTest {
+
+ private static final String FIELD_NAME = "jsonField";
+
+ /**
+ * Update the schema and add a json field.
+ *
+ * @throws IOException
+ */
+ @Before
+ public void updateSchema() throws IOException {
+ try (Tx tx = tx()) {
+
+ // add non restricted json field
+ JsonFieldSchema jsonFieldSchema = new JsonFieldSchemaImpl();
+ jsonFieldSchema.setName(FIELD_NAME);
+ jsonFieldSchema.setLabel("Some label");
+
+ // add restricted json field
+ JsonFieldSchema restrictedJsonFieldSchema = new JsonFieldSchemaImpl();
+ restrictedJsonFieldSchema.setName("restrictedjsonField");
+ restrictedJsonFieldSchema.setLabel("Some label");
+ restrictedJsonFieldSchema.setAllowedSchemas(new JsonSchema("{\"type\":\"object\",\"properties\":{\"firstName\":{\"type\":\"string\"},\"lastName\":{\"type\":\"string\"}},\"required\":[\"firstName\",\"lastName\"]}"));
+
+ prepareTypedSchema(schemaContainer("folder"), List.of(jsonFieldSchema, restrictedJsonFieldSchema), Optional.empty());
+ tx.success();
+ }
+ }
+
+ @Test
+ @Override
+ public void testCreateNodeWithNoField() {
+ NodeResponse response = createNode(null, (Field) null);
+ JsonFieldImpl jsonField = response.getFields().getJsonField(FIELD_NAME);
+ assertNull(jsonField);
+ }
+
+ @Test
+ @Override
+ public void testUpdateNodeFieldWithField() {
+ for (int i = 0; i < 20; i++) {
+ VersionNumber oldVersion = tx(tx -> { return tx.contentDao().getFieldContainer(folder("2015"), "en").getVersion(); });
+
+ JsonContent newValue = JsonFieldTestHelper.make("content " + i);
+
+ NodeResponse response = updateNode(FIELD_NAME, new JsonFieldImpl().setJson(newValue));
+ JsonFieldImpl field = response.getFields().getJsonField(FIELD_NAME);
+ assertEquals(newValue, field.getJson());
+ assertEquals("Check version number", oldVersion.nextDraft().toString(), response.getVersion());
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateSameValue() {
+ NodeResponse firstResponse = updateNode(FIELD_NAME, new JsonFieldImpl().setJson(JsonFieldTestHelper.make("bla")));
+ String oldNumber = firstResponse.getVersion();
+
+ NodeResponse secondResponse = updateNode(FIELD_NAME, new JsonFieldImpl().setJson(JsonFieldTestHelper.make("bla")));
+ assertThat(secondResponse.getVersion()).as("New version number").isEqualTo(oldNumber);
+ }
+
+ @Test
+ @Override
+ public void testUpdateSetNull() {
+ disableAutoPurge();
+
+ JsonContent old = JsonFieldTestHelper.make("bla");
+ NodeResponse firstResponse = updateNode(FIELD_NAME, new JsonFieldImpl().setJson(old));
+ String oldVersion = firstResponse.getVersion();
+
+ NodeResponse secondResponse = updateNode(FIELD_NAME, null);
+ assertThat(secondResponse.getFields().getJsonField(FIELD_NAME)).as("Updated Field").isNull();
+ assertThat(secondResponse.getVersion()).as("New version number").isNotEqualTo(oldVersion);
+
+ // Assert that the old version was not modified
+ try (Tx tx = tx()) {
+ ContentDao contentDao = tx.contentDao();
+ HibNode node = folder("2015");
+ HibNodeFieldContainer latest = contentDao.getLatestDraftFieldContainer(node, english());
+ assertThat(latest.getVersion().toString()).isEqualTo(secondResponse.getVersion());
+ assertThat(latest.getJson(FIELD_NAME)).isNull();
+ assertThat(latest.getPreviousVersion().getJson(FIELD_NAME)).isNotNull();
+ JsonContent oldValue = latest.getPreviousVersion().getJson(FIELD_NAME).getJson();
+ assertThat(oldValue).isEqualTo(old);
+ }
+ NodeResponse thirdResponse = updateNode(FIELD_NAME, null);
+ assertEquals("The field does not change and thus the version should not be bumped.", thirdResponse.getVersion(),
+ secondResponse.getVersion());
+ }
+
+ @Test
+ @Override
+ public void testUpdateSetEmpty() {
+ JsonContent content = JsonFieldTestHelper.make("bla");
+ NodeResponse firstResponse = updateNode(FIELD_NAME, new JsonFieldImpl().setJson(content));
+ JsonField emptyField = new JsonFieldImpl();
+ emptyField.setJson(null);
+ String oldVersion = firstResponse.getVersion();
+ NodeResponse secondResponse = updateNode(FIELD_NAME, emptyField);
+ assertThat(secondResponse.getFields().getDateField(FIELD_NAME)).as("Field Value").isNull();
+ assertThat(secondResponse.getVersion()).as("New version number").isNotEqualTo(oldVersion);
+ }
+
+ /**
+ * Get the json value
+ *
+ * @param container
+ * container
+ * @param fieldName
+ * field name
+ * @return json value (may be null)
+ */
+ protected JsonContent getJsonValue(HibNodeFieldContainer container, String fieldName) {
+ HibJsonField field = container.getJson(fieldName);
+ return field != null ? field.getJson() : null;
+ }
+
+ @Test
+ @Override
+ public void testCreateNodeWithField() {
+ NodeResponse response = createNodeWithField();
+ JsonFieldImpl field = response.getFields().getJsonField(FIELD_NAME);
+ assertEquals(JsonFieldTestHelper.make("someJson"), field.getJson());
+ }
+
+ @Test
+ @Override
+ public void testReadNodeWithExistingField() {
+ JsonContent someJson = JsonFieldTestHelper.make("someJson");
+ try (Tx tx = tx()) {
+ HibNode node = folder("2015");
+ ContentDao contentDao = tx.contentDao();
+
+ HibNodeFieldContainer container = contentDao.createFieldContainer(node, english(),
+ node.getProject().getLatestBranch(), user(),
+ contentDao.getLatestDraftFieldContainer(node, english()), true);
+ HibJsonField jsonField = container.createJson(FIELD_NAME);
+ jsonField.setJson(someJson);
+ tx.success();
+ }
+ NodeResponse response = readNode(folder("2015"));
+ JsonFieldImpl deserializedJsonField = response.getFields().getJsonField(FIELD_NAME);
+ assertNotNull(deserializedJsonField);
+ assertEquals(someJson, deserializedJsonField.getJson());
+ }
+
+ @Test
+ public void testValueRestrictionValidValue() {
+ JsonContent valid = JsonContent.fromObject(new JsonObject().put("firstName", "Mickey").put("lastName", "Mouse"));
+ NodeResponse response = updateNode("restrictedjsonField", new JsonFieldImpl().setJson(valid));
+ JsonFieldImpl field = response.getFields().getJsonField("restrictedjsonField");
+ assertEquals(valid, field.getJson());
+ }
+
+ @Test
+ public void testValueRestrictionInvalidValue() {
+ JsonContent invalid = JsonFieldTestHelper.make("whatever");
+ updateNodeFailure("restrictedjsonField", new JsonFieldImpl().setJson(invalid), HttpResponseStatus.BAD_REQUEST,
+ "node_error_invalid_json_field_value", "restrictedjsonField", JsonUtil.toJson(invalid));
+ }
+
+ @Test
+ public void testValueRemoveValueRestrictions() {
+ try (Tx tx = tx()) {
+ SchemaVersionModel schema = schemaContainer("folder").getLatestVersion().getSchema();
+
+ // unrestrict json field
+ JsonFieldSchema restrictedJsonFieldSchema = schema.getField("restrictedjsonField", JsonFieldSchema.class);
+ restrictedJsonFieldSchema.setAllowedSchemas();
+
+ schemaContainer("folder").getLatestVersion().setSchema(schema);
+ tx.success();
+ }
+ JsonContent valid = JsonFieldTestHelper.make("whatever");
+ NodeResponse response = updateNode("restrictedjsonField", new JsonFieldImpl().setJson(valid));
+ JsonFieldImpl field = response.getFields().getJsonField("restrictedjsonField");
+ assertEquals(valid, field.getJson());
+ }
+
+ @Override
+ public NodeResponse createNodeWithField() {
+ return createNode(FIELD_NAME, new JsonFieldImpl().setJson(JsonFieldTestHelper.make("someJson")));
+ }
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldListEndpointTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldListEndpointTest.java
new file mode 100644
index 0000000000..6b62e284bb
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldListEndpointTest.java
@@ -0,0 +1,235 @@
+package com.gentics.mesh.core.field.json;
+
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.make;
+import static com.gentics.mesh.test.TestDataProvider.PROJECT_NAME;
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.Test;
+
+import com.gentics.mesh.core.data.HibNodeFieldContainer;
+import com.gentics.mesh.core.data.dao.ContentDao;
+import com.gentics.mesh.core.data.node.HibNode;
+import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.field.AbstractListFieldEndpointTest;
+import com.gentics.mesh.core.rest.node.NodeResponse;
+import com.gentics.mesh.core.rest.node.field.Field;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
+import com.gentics.mesh.test.MeshTestSetting;
+import com.gentics.mesh.test.TestSize;
+
+@MeshTestSetting(testSize = TestSize.PROJECT_AND_NODE, startServer = true)
+public class JsonFieldListEndpointTest extends AbstractListFieldEndpointTest {
+
+ protected static final List TEST_LIST = List.of(make("A"), make("B"), make("C"));
+
+ @Override
+ public String getListFieldType() {
+ return "json";
+ }
+
+ @Test
+ @Override
+ public void testCreateNodeWithField() {
+ NodeResponse response = createNodeWithField();
+ JsonFieldListImpl field = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertThat(field.getItems()).as("Only valid values should be stored").containsExactlyElementsOf(TEST_LIST);
+ }
+
+ @Test
+ @Override
+ public void testNullValueInListOnCreate() {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.add(make("A"));
+ listField.add(make("B"));
+ listField.add(null);
+ createNodeAndExpectFailure(FIELD_NAME, listField, BAD_REQUEST, "field_list_error_null_not_allowed", FIELD_NAME);
+ }
+
+ @Test
+ @Override
+ public void testNullValueInListOnUpdate() {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.add(make("A"));
+ listField.add(make("B"));
+ listField.add(null);
+ updateNodeFailure(FIELD_NAME, listField, BAD_REQUEST, "field_list_error_null_not_allowed", FIELD_NAME);
+ }
+
+ @Test
+ @Override
+ public void testCreateNodeWithNoField() {
+ NodeResponse response = createNode(FIELD_NAME, (Field) null);
+ assertThat(response.getFields().getJsonFieldList(FIELD_NAME)).as("List field in reponse should be null").isNull();
+ }
+
+ @Test
+ @Override
+ public void testUpdateSameValue() {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.setItems(TEST_LIST);
+
+ NodeResponse firstResponse = updateNode(FIELD_NAME, listField);
+ String oldVersion = firstResponse.getVersion();
+
+ NodeResponse secondResponse = updateNode(FIELD_NAME, listField);
+ assertThat(secondResponse.getVersion()).as("New version number").isEqualTo(oldVersion);
+ }
+
+ @Test
+ @Override
+ public void testReadNodeWithExistingField() {
+ // 1. Update an existing node
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.setItems(TEST_LIST);
+ NodeResponse firstResponse = updateNode(FIELD_NAME, listField);
+
+ // 2. Read the node
+ NodeResponse response = readNode(PROJECT_NAME, firstResponse.getUuid());
+ JsonFieldListImpl deserializedField = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertNotNull(deserializedField);
+ assertThat(deserializedField.getItems()).as("List field values from updated node").containsExactlyElementsOf(TEST_LIST);
+ }
+
+ @Test
+ public void testCreateNodeWithNullFieldValue() throws IOException {
+ NodeResponse response = createNode(FIELD_NAME, (Field) null);
+ JsonFieldListImpl nodeField = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertNull("No json field should have been created.", nodeField);
+ }
+
+ @Test
+ public void testCreateEmptyStringList() throws IOException {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ NodeResponse response = createNode(FIELD_NAME, listField);
+ JsonFieldListImpl listFromResponse = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertEquals(0, listFromResponse.getItems().size());
+ }
+
+ @Test
+ public void testCreateNullStringList() throws IOException {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.setItems(null);
+ NodeResponse response = createNode(FIELD_NAME, listField);
+ JsonFieldListImpl listFromResponse = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertNull("The json list should be null since the request was sending null instead of an array.", listFromResponse);
+ }
+
+ @Test
+ public void testJsonList() throws IOException {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.setItems(TEST_LIST);
+
+ NodeResponse response = createNode(FIELD_NAME, listField);
+ JsonFieldListImpl listFromResponse = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertEquals(3, listFromResponse.getItems().size());
+ assertEquals(TEST_LIST.toString(), listFromResponse.getItems().toString());
+ }
+
+ @Test
+ @Override
+ public void testUpdateNodeFieldWithField() throws IOException {
+ disableAutoPurge();
+ HibNode node = folder("2015");
+
+ List> valueCombinations = Arrays.asList(Arrays.asList("A", "B", "C"), Arrays.asList("C", "B", "A"), Collections.emptyList(),
+ Arrays.asList("X", "Y"), Arrays.asList("C"));
+
+ HibNodeFieldContainer container = tx(tx -> { return tx.contentDao().getFieldContainer(node, "en"); });
+ for (int i = 0; i < 20; i++) {
+ JsonFieldListImpl list = new JsonFieldListImpl();
+ List oldValue;
+ List newValue;
+ try (Tx tx = tx()) {
+ oldValue = getListValues(container::getJsonList, FIELD_NAME);
+ newValue = valueCombinations.get(i % valueCombinations.size()).stream().map(JsonFieldTestHelper::make).collect(Collectors.toList());
+
+ for (JsonContent value : newValue) {
+ list.add(value);
+ }
+ }
+ NodeResponse response = updateNode(FIELD_NAME, list);
+ JsonFieldListImpl field = response.getFields().getJsonFieldList(FIELD_NAME);
+ assertThat(field.getItems()).as("Updated field").containsExactlyElementsOf(list.getItems());
+
+ try (Tx tx = tx()) {
+ ContentDao contentDao = tx.contentDao();
+ HibNodeFieldContainer newContainerVersion = contentDao.getNextVersions(container).iterator().next();
+ assertEquals("The old container version did not match", container.getVersion().nextDraft().toString(),
+ response.getVersion().toString());
+ assertEquals("Check version number", newContainerVersion.getVersion().toString(), response.getVersion());
+ assertEquals("Check old value", oldValue, getListValues(container::getJsonList, FIELD_NAME));
+ assertEquals("Check new value", newValue, getListValues(newContainerVersion::getJsonList, FIELD_NAME));
+ container = newContainerVersion;
+ }
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateSetNull() {
+ disableAutoPurge();
+
+ JsonFieldListImpl list = new JsonFieldListImpl();
+ list.setItems(TEST_LIST);
+ NodeResponse firstResponse = updateNode(FIELD_NAME, list);
+ String oldVersion = firstResponse.getVersion();
+
+ NodeResponse secondResponse = updateNode(FIELD_NAME, null);
+ assertThat(secondResponse.getFields().getJsonFieldList(FIELD_NAME)).as("Updated Field").isNull();
+ assertThat(oldVersion).as("Version should be updated").isNotEqualTo(secondResponse.getVersion());
+
+ // Assert that the old version was not modified
+ try (Tx tx = tx()) {
+ ContentDao contentDao = tx.contentDao();
+ HibNode node = folder("2015");
+ HibNodeFieldContainer latest = contentDao.getLatestDraftFieldContainer(node, english());
+ assertThat(latest.getVersion().toString()).isEqualTo(secondResponse.getVersion());
+ assertThat(latest.getJsonList(FIELD_NAME)).isNull();
+ assertThat(latest.getPreviousVersion().getJsonList(FIELD_NAME)).isNotNull();
+ List oldValueList = latest.getPreviousVersion().getJsonList(FIELD_NAME).getList().stream().map(item -> item.getJson())
+ .collect(Collectors.toList());
+ assertThat(oldValueList).containsExactlyElementsOf(TEST_LIST);
+ }
+ NodeResponse thirdResponse = updateNode(FIELD_NAME, null);
+ assertEquals("The field does not change and thus the version should not be bumped.", thirdResponse.getVersion(),
+ secondResponse.getVersion());
+ }
+
+ @Test
+ @Override
+ public void testUpdateSetEmpty() {
+ JsonFieldListImpl list = new JsonFieldListImpl();
+ list.setItems(TEST_LIST);
+ NodeResponse firstResponse = updateNode(FIELD_NAME, list);
+ String oldVersion = firstResponse.getVersion();
+
+ JsonFieldListImpl emptyField = new JsonFieldListImpl();
+ NodeResponse secondResponse = updateNode(FIELD_NAME, emptyField);
+ assertThat(secondResponse.getFields().getJsonFieldList(FIELD_NAME)).as("Updated field list").isNotNull();
+ assertThat(secondResponse.getFields().getJsonFieldList(FIELD_NAME).getItems()).as("Field value should be truncated").isEmpty();
+ assertThat(secondResponse.getVersion()).as("New version number should be generated").isNotEqualTo(oldVersion);
+
+ NodeResponse thirdResponse = updateNode(FIELD_NAME, emptyField);
+ assertEquals("The field does not change and thus the version should not be bumped.", thirdResponse.getVersion(), secondResponse.getVersion());
+ assertThat(secondResponse.getVersion()).as("No new version number should be generated").isEqualTo(secondResponse.getVersion());
+ }
+
+ @Override
+ public NodeResponse createNodeWithField() {
+ JsonFieldListImpl listField = new JsonFieldListImpl();
+ listField.setItems(TEST_LIST);
+
+ return createNode(FIELD_NAME, listField);
+ }
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldTest.java
new file mode 100644
index 0000000000..f65569377b
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldTest.java
@@ -0,0 +1,242 @@
+package com.gentics.mesh.core.field.json;
+
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.CREATE_EMPTY;
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.FETCH;
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.FILLTEXT;
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.make;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.gentics.mesh.context.InternalActionContext;
+import com.gentics.mesh.core.data.HibNodeFieldContainer;
+import com.gentics.mesh.core.data.dao.ContentDao;
+import com.gentics.mesh.core.data.node.HibNode;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
+import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.field.AbstractFieldTest;
+import com.gentics.mesh.core.rest.node.NodeResponse;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
+import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
+import com.gentics.mesh.json.JsonUtil;
+import com.gentics.mesh.test.MeshTestSetting;
+import com.gentics.mesh.test.TestSize;
+import com.gentics.mesh.test.context.NoConsistencyCheck;
+import com.gentics.mesh.util.CoreTestUtils;
+
+import io.vertx.core.json.JsonObject;
+
+@MeshTestSetting(testSize = TestSize.PROJECT_AND_NODE, startServer = false)
+public class JsonFieldTest extends AbstractFieldTest {
+
+ private static final String JSON_FIELD = "jsonField";
+
+ @Override
+ protected JsonFieldSchema createFieldSchema(boolean isRequired) {
+ return createFieldSchema(JSON_FIELD, isRequired);
+ }
+ protected JsonFieldSchema createFieldSchema(String fieldKey, boolean isRequired) {
+ JsonFieldSchema schema = new JsonFieldSchemaImpl();
+ schema.setLabel("Some JSON field");
+ schema.setRequired(isRequired);
+ schema.setName(fieldKey);
+ return schema;
+ }
+
+ @Test
+ @Override
+ public void testFieldTransformation() throws Exception {
+ try (Tx tx = tx()) {
+ ContentDao contentDao = tx.contentDao();
+ HibNode node = folder("2015");
+
+ // Add a new string field to the schema
+ JsonFieldSchemaImpl jsonFieldSchema = new JsonFieldSchemaImpl();
+ jsonFieldSchema.setName(JSON_FIELD);
+ jsonFieldSchema.setLabel("Some string field");
+ jsonFieldSchema.setRequired(true);
+ prepareTypedSchema(node, jsonFieldSchema, false);
+ tx.commit();
+
+ HibNodeFieldContainer container = contentDao.createFieldContainer(node, english(),
+ node.getProject().getLatestBranch(), user(),
+ contentDao.getLatestDraftFieldContainer(node, english()), true);
+ HibJsonField field = container.createJson(JSON_FIELD);
+ field.setJson(make("someString"));
+ tx.success();
+ }
+
+ try (Tx tx = tx()) {
+ HibNode node = folder("2015");
+ String json = getJson(node);
+ assertTrue("The json should contain the string but it did not.{" + json + "}", json.indexOf("someString") > 1);
+ assertNotNull(json);
+ NodeResponse response = JsonUtil.readValue(json, NodeResponse.class);
+ assertNotNull(response);
+
+ com.gentics.mesh.core.rest.node.field.JsonField deserializedNodeField = response.getFields().getJsonField(JSON_FIELD);
+ assertNotNull(deserializedNodeField);
+ assertEquals(make("someString"), deserializedNodeField.getJson());
+ }
+ }
+
+ @Test
+ @NoConsistencyCheck
+ @Override
+ public void testClone() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ HibJsonField testField = container.createJson(JSON_FIELD);
+ testField.setJson(JsonContent.fromObject(new JsonObject()));
+
+ HibNodeFieldContainer otherContainer = CoreTestUtils.createContainer(createFieldSchema(true));
+ testField.cloneTo(otherContainer);
+
+ assertThat(otherContainer.getJson(JSON_FIELD)).as("cloned field").isNotNull().isEqualToIgnoringGivenFields(testField,
+ "parentContainer");
+ }
+ }
+
+ @Test
+ @Override
+ public void testFieldUpdate() throws Exception {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ HibJsonField jsonField = container.createJson(JSON_FIELD);
+ assertEquals(JSON_FIELD, jsonField.getFieldKey());
+ jsonField.setJson(make("dummyString"));
+ assertEquals(make("dummyString"), jsonField.getJson());
+ HibJsonField bogusField1 = container.getJson("bogus");
+ assertNull(bogusField1);
+ HibJsonField reloadedJsonField = container.getJson(JSON_FIELD);
+ assertNotNull(reloadedJsonField);
+ assertEquals(JSON_FIELD, reloadedJsonField.getFieldKey());
+ }
+ }
+
+ @Test
+ @Override
+ public void testEquals() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true), createFieldSchema(JSON_FIELD + "_2", false));
+ String testValue = "test123";
+ HibJsonField fieldA = container.createJson(JSON_FIELD);
+ HibJsonField fieldB = container.createJson(JSON_FIELD + "_2");
+ fieldA.setJson(make(testValue));
+ fieldB.setJson(make(testValue));
+ assertTrue("Both fields should be equal to eachother", fieldA.equals(fieldB));
+ }
+ }
+
+ @Test
+ @Override
+ public void testEqualsNull() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true), createFieldSchema(JSON_FIELD + "_2", false));
+ HibJsonField fieldA = container.createJson(JSON_FIELD);
+ HibJsonField fieldB = container.createJson(JSON_FIELD + "_2");
+ assertTrue("Both fields should be equal to eachother", fieldA.equals(fieldB));
+ }
+ }
+
+ @SuppressWarnings("unlikely-arg-type")
+ @Test
+ @Override
+ public void testEqualsRestField() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ String dummyValue = "test123";
+
+ // rest null - graph null
+ HibJsonField fieldA = container.createJson(JSON_FIELD);
+ JsonFieldImpl restField = new JsonFieldImpl();
+ assertTrue("Both fields should be equal to eachother since both values are null", fieldA.equals(restField));
+
+ // rest set - graph set - different values
+ fieldA.setJson(make(dummyValue));
+ restField.setJson(make(dummyValue + 1L));
+ assertFalse("Both fields should be different since both values are not equal", fieldA.equals(restField));
+
+ // rest set - graph set - same value
+ restField.setJson(make(dummyValue));
+ assertTrue("Both fields should be equal since values are equal", fieldA.equals(restField));
+
+ // rest set - graph set - same value different type
+ assertFalse("Fields should not be equal since the type does not match.", fieldA.equals(new HtmlFieldImpl().setHTML(JsonUtil.toJson(make(dummyValue)))));
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateFromRestNullOnCreate() {
+ try (Tx tx = tx()) {
+ invokeUpdateFromRestTestcase(JSON_FIELD, FETCH, CREATE_EMPTY);
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateFromRestNullOnCreateRequired() {
+ try (Tx tx = tx()) {
+ invokeUpdateFromRestNullOnCreateRequiredTestcase(JSON_FIELD, FETCH);
+ }
+ }
+
+ @Test
+ @Override
+ public void testRemoveFieldViaNull() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeRemoveFieldViaNullTestcase(JSON_FIELD, FETCH, FILLTEXT, (node) -> {
+ updateContainer(ac, node, JSON_FIELD, null);
+ });
+ }
+ }
+
+ @Test
+ @Override
+ public void testRemoveRequiredFieldViaNull() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeRemoveRequiredFieldViaNullTestcase(JSON_FIELD, FETCH, FILLTEXT, (container) -> {
+ updateContainer(ac, container, JSON_FIELD, null);
+ });
+ }
+ }
+
+ @Test
+ public void testRemoveSegmentField() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeRemoveSegmentFieldViaNullTestcase(JSON_FIELD, FETCH, FILLTEXT, (container) -> {
+ updateContainer(ac, container, JSON_FIELD, null);
+ });
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateFromRestValidSimpleValue() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeUpdateFromRestValidSimpleValueTestcase(JSON_FIELD, FILLTEXT, (container) -> {
+ JsonField field = new JsonFieldImpl();
+ field.setJson(make("someValue"));
+ updateContainer(ac, container, JSON_FIELD, field);
+ }, (container) -> {
+ HibJsonField field = container.getJson(JSON_FIELD);
+ assertNotNull("The graph field {" + JSON_FIELD + "} could not be found.", field);
+ assertEquals("The string of the field was not updated.", make("someValue"), field.getJson());
+ });
+ }
+ }
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldTestHelper.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldTestHelper.java
new file mode 100644
index 0000000000..49f86d1350
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonFieldTestHelper.java
@@ -0,0 +1,20 @@
+package com.gentics.mesh.core.field.json;
+
+import com.gentics.mesh.core.field.DataProvider;
+import com.gentics.mesh.core.field.FieldFetcher;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+
+import io.vertx.core.json.JsonObject;
+
+public interface JsonFieldTestHelper {
+
+ static final String EXAMPLE_DATE = "2011-12-03T10:15:30Z";
+
+ static final DataProvider FILLTEXT = (container, name) -> container.createJson(name).setJson(make("whatever"));
+ static final DataProvider CREATE_EMPTY = (container, name) -> container.createJson(name).setJson(null);
+ static final FieldFetcher FETCH = (container, name) -> container.getJson(name);
+
+ public static JsonContent make(String content) {
+ return JsonContent.fromObject(new JsonObject().put("content", content));
+ }
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonListFieldTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonListFieldTest.java
new file mode 100644
index 0000000000..1ea56112f9
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonListFieldTest.java
@@ -0,0 +1,254 @@
+package com.gentics.mesh.core.field.json;
+
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.make;
+import static com.gentics.mesh.core.field.json.JsonListFieldTestHelper.CREATE_EMPTY;
+import static com.gentics.mesh.core.field.json.JsonListFieldTestHelper.FETCH;
+import static com.gentics.mesh.core.field.json.JsonListFieldTestHelper.FILLTEXT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.junit.Test;
+
+import com.gentics.mesh.context.InternalActionContext;
+import com.gentics.mesh.core.data.HibNodeFieldContainer;
+import com.gentics.mesh.core.data.dao.ContentDao;
+import com.gentics.mesh.core.data.node.HibNode;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
+import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.field.AbstractFieldTest;
+import com.gentics.mesh.core.rest.node.NodeResponse;
+import com.gentics.mesh.core.rest.node.field.Field;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
+import com.gentics.mesh.core.rest.schema.ListFieldSchema;
+import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
+import com.gentics.mesh.test.MeshTestSetting;
+import com.gentics.mesh.test.TestSize;
+import com.gentics.mesh.test.context.NoConsistencyCheck;
+import com.gentics.mesh.util.CoreTestUtils;
+
+@MeshTestSetting(testSize = TestSize.PROJECT_AND_NODE, startServer = false)
+public class JsonListFieldTest extends AbstractFieldTest {
+
+ private static final String JSON_LIST = "jsonList";
+
+ @Override
+ protected ListFieldSchema createFieldSchema(boolean isRequired) {
+ return createFieldSchema(JSON_LIST, isRequired);
+ }
+ protected ListFieldSchema createFieldSchema(String fieldKey, boolean isRequired) {
+ ListFieldSchema schema = new ListFieldSchemaImpl();
+ schema.setListType("json");
+ schema.setName(fieldKey);
+ schema.setRequired(isRequired);
+ return schema;
+ }
+
+ @Test
+ @Override
+ public void testFieldTransformation() throws Exception {
+ try (Tx tx = tx()) {
+ HibNode node = folder("2015");
+ ContentDao contentDao = tx.contentDao();
+ prepareNode(node, JSON_LIST, "json");
+ HibNodeFieldContainer container = contentDao.createFieldContainer(node, english(),
+ node.getProject().getLatestBranch(), user(),
+ contentDao.getLatestDraftFieldContainer(node, english()), true);
+ HibJsonFieldList jsonList = container.createJsonList(JSON_LIST);
+ jsonList.createJson(make("dummyString1"));
+ jsonList.createJson(make("dummyString2"));
+ tx.success();
+ }
+
+ try (Tx tx = tx()) {
+ HibNode node = folder("2015");
+ NodeResponse response = transform(node);
+ assertList(2, JSON_LIST, "json", response);
+ }
+ }
+
+ @Test
+ @Override
+ public void testFieldUpdate() throws Exception {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ HibJsonFieldList list = container.createJsonList(JSON_LIST);
+
+ list.createJson(make("1"));
+ assertEquals(JSON_LIST, list.getFieldKey());
+ assertNotNull(list.getList());
+
+ assertEquals(1, list.getList().size());
+ assertEquals(list.getSize(), list.getList().size());
+ list.createJson(make("2"));
+ assertEquals(2, list.getList().size());
+ list.createJson(make("3")).setJson(make("Some json 3"));
+ assertEquals(3, list.getList().size());
+ assertEquals(make("Some json 3"), list.getList().get(2).getJson());
+
+ HibJsonFieldList loadedList = container.getJsonList(JSON_LIST);
+ assertNotNull(loadedList);
+ assertEquals(3, loadedList.getSize());
+ list.removeAll();
+ assertEquals(0, list.getSize());
+ assertEquals(0, list.getList().size());
+ }
+ }
+
+ @Test
+ @Override
+ public void testBulkFieldUpdate() throws Exception {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ HibJsonFieldList list = container.createJsonList(JSON_LIST);
+ List params = List.of("1","2","3","4","whatever").stream().map(JsonFieldTestHelper::make).collect(Collectors.toList());
+ list.createJsons(params);
+ assertEquals(5, list.getSize());
+ assertEquals(5, list.getList().size());
+ assertTrue(CollectionUtils.isEqualCollection(params, list.getValues()));
+ }
+ }
+
+ @Test
+ @NoConsistencyCheck
+ @Override
+ public void testClone() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ HibJsonFieldList testField = container.createJsonList(JSON_LIST);
+ testField.createJson(make("one"));
+ testField.createJson(make("two"));
+ testField.createJson(make("three"));
+
+ HibNodeFieldContainer otherContainer = CoreTestUtils.createContainer(createFieldSchema(true));
+ testField.cloneTo(otherContainer);
+
+ assertThat(otherContainer.getJsonList(JSON_LIST).equals(testField));
+ }
+ }
+
+ @Test
+ @Override
+ public void testEquals() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema("fieldA", true), createFieldSchema("fieldB", true));
+ HibJsonFieldList fieldA = container.createJsonList("fieldA");
+ HibJsonFieldList fieldB = container.createJsonList("fieldB");
+ assertTrue("The field should be equal to itself", fieldA.equals(fieldA));
+ fieldA.addItem(fieldA.createJson(make("testString")));
+ assertTrue("The field should still be equal to itself", fieldA.equals(fieldA));
+
+ assertFalse("The field should not be equal to a non-json field", fieldA.equals("bogus"));
+ assertFalse("The field should not be equal since fieldB has no value", fieldA.equals(fieldB));
+ fieldB.addItem(fieldB.createJson(make("testString")));
+ assertTrue("Both fields have the same value and should be equal", fieldA.equals(fieldB));
+ }
+ }
+
+ @Test
+ @Override
+ public void testEqualsNull() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ HibJsonFieldList fieldA = container.createJsonList(JSON_LIST);
+ assertFalse(fieldA.equals((Field) null));
+ assertFalse(fieldA.equals((HibJsonFieldList) null));
+ }
+ }
+
+ @Test
+ @Override
+ public void testEqualsRestField() {
+ try (Tx tx = tx()) {
+ HibNodeFieldContainer container = CoreTestUtils.createContainer(createFieldSchema(true));
+ String dummyValue = "test123";
+
+ // rest null - graph null
+ HibJsonFieldList fieldA = container.createJsonList(JSON_LIST);
+
+ JsonFieldListImpl restField = new JsonFieldListImpl();
+ assertTrue("Both fields should be equal to eachother since both values are null", fieldA.equals(restField));
+
+ // rest set - graph set - different values
+ fieldA.addItem(fieldA.createJson(make(dummyValue)));
+ restField.add(make(dummyValue + 1L));
+ assertFalse("Both fields should be different since both values are not equal", fieldA.equals(restField));
+
+ // rest set - graph set - same value
+ restField.getItems().clear();
+ restField.add(make(dummyValue));
+ assertTrue("Both fields should be equal since values are equal", fieldA.equals(restField));
+
+ HtmlFieldListImpl otherTypeRestField = new HtmlFieldListImpl();
+ otherTypeRestField.add(dummyValue);
+ // rest set - graph set - same value different type
+ assertFalse("Fields should not be equal since the type does not match.", fieldA.equals(otherTypeRestField));
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateFromRestNullOnCreate() {
+ try (Tx tx = tx()) {
+ invokeUpdateFromRestTestcase(JSON_LIST, FETCH, CREATE_EMPTY);
+ }
+ }
+
+ @Test
+ @Override
+ public void testUpdateFromRestNullOnCreateRequired() {
+ try (Tx tx = tx()) {
+ invokeUpdateFromRestNullOnCreateRequiredTestcase(JSON_LIST, FETCH);
+ }
+ }
+
+ @Test
+ @Override
+ public void testRemoveFieldViaNull() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeRemoveFieldViaNullTestcase(JSON_LIST, FETCH, FILLTEXT, (node) -> {
+ updateContainer(ac, node, JSON_LIST, null);
+ });
+ }
+ }
+
+ @Test
+ @Override
+ public void testRemoveRequiredFieldViaNull() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeRemoveRequiredFieldViaNullTestcase(JSON_LIST, FETCH, FILLTEXT, (container) -> {
+ updateContainer(ac, container, JSON_LIST, null);
+ });
+ }
+ }
+
+ @Override
+ public void testUpdateFromRestValidSimpleValue() {
+ try (Tx tx = tx()) {
+ InternalActionContext ac = mockActionContext();
+ invokeUpdateFromRestValidSimpleValueTestcase(JSON_LIST, FILLTEXT, (container) -> {
+ JsonFieldListImpl field = new JsonFieldListImpl();
+ field.getItems().add(make("someValue"));
+ field.getItems().add(make("someValue2"));
+ updateContainer(ac, container, JSON_LIST, field);
+ }, (container) -> {
+ HibJsonFieldList field = container.getJsonList(JSON_LIST);
+ assertNotNull("The graph field {" + JSON_LIST + "} could not be found.", field);
+ assertEquals("The list of the field was not updated.", 2, field.getList().size());
+ assertEquals("The list item of the field was not updated.", make("someValue"), field.getList().get(0).getJson());
+ assertEquals("The list item of the field was not updated.", make("someValue2"), field.getList().get(1).getJson());
+ });
+ }
+ }
+
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonListFieldTestHelper.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonListFieldTestHelper.java
new file mode 100644
index 0000000000..8320beb79f
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/field/json/JsonListFieldTestHelper.java
@@ -0,0 +1,40 @@
+package com.gentics.mesh.core.field.json;
+
+import static com.gentics.mesh.core.field.json.JsonFieldTestHelper.make;
+
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
+import com.gentics.mesh.core.field.DataProvider;
+import com.gentics.mesh.core.field.FieldFetcher;
+
+public interface JsonListFieldTestHelper {
+
+ static final String TEXT1 = "one";
+
+ static final String TEXT2 = "two";
+
+ static final String TEXT3 = "three";
+
+ static final DataProvider FILLTEXT = (container, name) -> {
+ HibJsonFieldList field = container.createJsonList(name);
+ field.createJson(make(TEXT1));
+ field.createJson(make(TEXT2));
+ field.createJson(make(TEXT3));
+ };
+
+ static final DataProvider FILLNUMBERS = (container, name) -> {
+ HibJsonFieldList field = container.createJsonList(name);
+ field.createJson(make("1"));
+ field.createJson(make("0"));
+ };
+
+ static final DataProvider FILLTRUEFALSE = (container, name) -> {
+ HibJsonFieldList field = container.createJsonList(name);
+ field.createJson(make("true"));
+ field.createJson(make("false"));
+ };
+
+ static final DataProvider CREATE_EMPTY = (container, name) -> container.createJsonList(name);
+
+ static final FieldFetcher FETCH = (container, name) -> container.getJsonList(name);
+
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/GraphQLEndpointTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/GraphQLEndpointTest.java
index 495b112226..1d95324888 100644
--- a/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/GraphQLEndpointTest.java
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/GraphQLEndpointTest.java
@@ -39,6 +39,7 @@
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -60,6 +61,7 @@
import com.gentics.mesh.core.rest.node.NodeResponse;
import com.gentics.mesh.core.rest.node.NodeUpdateRequest;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.node.field.StringField;
import com.gentics.mesh.core.rest.node.field.image.FocalPoint;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
@@ -70,6 +72,7 @@
import com.gentics.mesh.core.rest.schema.BooleanFieldSchema;
import com.gentics.mesh.core.rest.schema.DateFieldSchema;
import com.gentics.mesh.core.rest.schema.HtmlFieldSchema;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.MicronodeFieldSchema;
import com.gentics.mesh.core.rest.schema.NodeFieldSchema;
@@ -80,6 +83,7 @@
import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NodeFieldSchemaImpl;
@@ -220,10 +224,12 @@ protected static Stream> queries() {
Arrays.asList("filtering/nodes-datelist-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-htmllist-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-node-field-native", true, false, "draft"),
+ Arrays.asList("filtering/nodes-json-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-micronode-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-binary-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-s3binary-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-nodelist-field-native", true, false, "draft"),
+ Arrays.asList("filtering/nodes-jsonlist-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-micronodelist-field-native", true, false, "draft"),
Arrays.asList("filtering/nodes-paged", true, false, "draft")
);
@@ -258,6 +264,7 @@ public void testNodeQuery() throws Exception {
microschemaRequest.addField(FieldUtil.createStringFieldSchema("text"));
microschemaRequest.addField(FieldUtil.createNodeFieldSchema("nodeRef").setAllowedSchemas("content"));
microschemaRequest.addField(FieldUtil.createListFieldSchema("nodeList", "node"));
+ microschemaRequest.addField(FieldUtil.createJsonFieldSchema("json"));
MicroschemaResponse microschemaResponse = call(() -> client.createMicroschema(microschemaRequest));
microschemaUuid = microschemaResponse.getUuid();
call(() -> client.assignMicroschemaToProject(PROJECT_NAME, microschemaResponse.getUuid()));
@@ -340,6 +347,10 @@ public void testNodeQuery() throws Exception {
stringLinkFieldSchema.setName("stringLink");
schema.addField(stringLinkFieldSchema);
+ JsonFieldSchema jsonFieldSchema = new JsonFieldSchemaImpl();
+ jsonFieldSchema.setName("json");
+ schema.addField(jsonFieldSchema);
+
BooleanFieldSchema booleanFieldSchema = new BooleanFieldSchemaImpl();
booleanFieldSchema.setName("boolean");
schema.addField(booleanFieldSchema);
@@ -364,6 +375,11 @@ public void testNodeQuery() throws Exception {
htmlListSchema.setName("htmlList");
schema.addField(htmlListSchema);
+ ListFieldSchema jsonListSchema = new ListFieldSchemaImpl();
+ jsonListSchema.setListType("json");
+ jsonListSchema.setName("jsonList");
+ schema.addField(jsonListSchema);
+
ListFieldSchema booleanListSchema = new ListFieldSchemaImpl();
booleanListSchema.setListType("boolean");
booleanListSchema.setName("booleanList");
@@ -419,6 +435,14 @@ public void testNodeQuery() throws Exception {
// stringLink
container.createString("stringLink").setString("Link: {{mesh.link(\"" + CONTENT_UUID + "\", \"en\")}}");
+ // json
+ container.createJson("json").setJson(JsonContent.fromObject(new JsonObject("""
+ {
+ "firstName": "Mickey",
+ "lastName": "Mouse"
+ }
+ """)));
+
// boolean
container.createBoolean("boolean").setBoolean(true);
@@ -439,6 +463,21 @@ public void testNodeQuery() throws Exception {
stringList.createString("C");
stringList.createString("D Link: {{mesh.link(\"" + CONTENT_UUID + "\", \"en\")}}");
+ // jsonList
+ HibJsonFieldList jsonList = container.createJsonList("jsonList");
+ jsonList.createJson(JsonContent.fromObject(new JsonObject("""
+ {
+ "firstName": "Minnie",
+ "lastName": "Mouse"
+ }
+ """)));
+ jsonList.createJson(JsonContent.fromObject(new JsonObject("""
+ {
+ "firstName": "Daisy",
+ "lastName": "Duck"
+ }
+ """)));
+
// htmlList
HibHtmlFieldList htmlList = container.createHTMLList("htmlList");
htmlList.createHTML("A");
@@ -481,6 +520,13 @@ public void testNodeQuery() throws Exception {
HibMicronode secondMicronode = micronodeList.createMicronode(microschemaDao.findByUuid(microschemaUuid).getLatestVersion());
secondMicronode.createString("text").setString("Joe");
secondMicronode.createNode("nodeRef", content());
+ secondMicronode.createJson("json").setJson(JsonContent.fromObject(new JsonObject("""
+ {
+ "firstName":"Donald",
+ "lastName": "Duck"
+ }
+ """)));
+
HibNodeFieldList micrnodeNodeList = secondMicronode.createNodeList("nodeList");
micrnodeNodeList.createNode(0, node2);
micrnodeNodeList.createNode(1, node3);
@@ -505,6 +551,7 @@ public void testNodeQuery() throws Exception {
secondMicronode = micronodeList.createMicronode(microschemaDao.findByUuid(microschemaUuid).getLatestVersion());
secondMicronode.createString("text").setString("Jane");
secondMicronode.createNode("nodeRef", content());
+
micrnodeNodeList = secondMicronode.createNodeList("nodeList");
micrnodeNodeList.createNode(0, node2);
micrnodeNodeList.createNode(1, node3);
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/javafilter/JavaGraphQLEndpointTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/javafilter/JavaGraphQLEndpointTest.java
index 313680627f..d18e7c2857 100644
--- a/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/javafilter/JavaGraphQLEndpointTest.java
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/graphql/javafilter/JavaGraphQLEndpointTest.java
@@ -37,6 +37,7 @@ public static Stream> queries() {
Arrays.asList("filtering/roles-java", true, false, "draft"),
Arrays.asList("filtering/nodes-string-field-java", true, false, "draft"),
Arrays.asList("filtering/nodes-number-field-java", true, false, "draft"),
+ Arrays.asList("filtering/nodes-json-field-java", true, false, "draft"),
Arrays.asList("filtering/nodes-nodereferences-java", true, false, "draft")
);
}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/change/FieldSchemaContainerMutatorTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/change/FieldSchemaContainerMutatorTest.java
index 0d9b7b4fd7..c82317688b 100644
--- a/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/change/FieldSchemaContainerMutatorTest.java
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/change/FieldSchemaContainerMutatorTest.java
@@ -10,6 +10,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.util.Map;
+
import org.junit.Test;
import com.gentics.mesh.core.data.schema.HibFieldTypeChange;
@@ -19,10 +22,13 @@
import com.gentics.mesh.core.data.schema.handler.FieldSchemaContainerMutator;
import com.gentics.mesh.core.db.CommonTx;
import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.rest.JsonSchema;
+import com.gentics.mesh.core.rest.JsonSchemaType;
import com.gentics.mesh.core.rest.schema.BinaryFieldSchema;
import com.gentics.mesh.core.rest.schema.BooleanFieldSchema;
import com.gentics.mesh.core.rest.schema.DateFieldSchema;
import com.gentics.mesh.core.rest.schema.HtmlFieldSchema;
+import com.gentics.mesh.core.rest.schema.JsonFieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
import com.gentics.mesh.core.rest.schema.MicronodeFieldSchema;
import com.gentics.mesh.core.rest.schema.NodeFieldSchema;
@@ -35,6 +41,7 @@
import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.NodeFieldSchemaImpl;
@@ -45,6 +52,7 @@
import com.gentics.mesh.test.MeshTestSetting;
import com.gentics.mesh.test.context.AbstractMeshTest;
import com.gentics.mesh.util.IndexOptionHelper;
+import com.hazelcast.jet.json.JsonUtil;
/**
* Test for common mutator operations on a field containers.
@@ -134,7 +142,7 @@ public void testUpdateLabel() throws MeshSchemaException {
}
@Test
- public void testAUpdateFields() throws MeshSchemaException {
+ public void testAUpdateFields() throws MeshSchemaException, IOException {
try (Tx tx = tx()) {
CommonTx ctx = tx.unwrap();
@@ -160,6 +168,12 @@ public void testAUpdateFields() throws MeshSchemaException {
nodeField.setRequired(true);
schemaModel.addField(nodeField);
+ JsonFieldSchema jsonField = new JsonFieldSchemaImpl();
+ jsonField.setAllowedSchemas(new JsonSchema().setProperties(Map.of("blub", new JsonSchemaType())).setRequired(new String[] {"blub"}));
+ jsonField.setName("jsonField");
+ jsonField.setRequired(true);
+ schemaModel.addField(jsonField);
+
MicronodeFieldSchema micronodeField = new MicronodeFieldSchemaImpl();
micronodeField.setAllowedMicroSchemas("blub");
micronodeField.setName("micronodeField");
@@ -208,12 +222,19 @@ public void testAUpdateFields() throws MeshSchemaException {
nodeFieldUpdate.setRestProperty(SchemaChangeModel.REQUIRED_KEY, false);
binaryFieldUpdate.setNextChange(nodeFieldUpdate);
+ HibUpdateFieldChange jsonFieldUpdate = (HibUpdateFieldChange) ctx.schemaDao().createPersistedChange(version, SchemaChangeOperation.UPDATEFIELD);
+ jsonFieldUpdate.setRestProperty(ALLOW_KEY, new String[] { JsonUtil.toJson(new JsonSchema().setProperties(Map.of("content", new JsonSchemaType())).setRequired(new String[] {"content"})) });
+ jsonFieldUpdate.setFieldName("jsonField");
+ jsonFieldUpdate.setRestProperty(SchemaChangeModel.REQUIRED_KEY, false);
+ jsonFieldUpdate.setIndexOptions(IndexOptionHelper.getRawFieldOption());
+ nodeFieldUpdate.setNextChange(jsonFieldUpdate);
+
HibUpdateFieldChange stringFieldUpdate = (HibUpdateFieldChange) ctx.schemaDao().createPersistedChange(version, SchemaChangeOperation.UPDATEFIELD);
stringFieldUpdate.setRestProperty(ALLOW_KEY, new String[] { "valueA", "valueB" });
stringFieldUpdate.setFieldName("stringField");
stringFieldUpdate.setRestProperty(SchemaChangeModel.REQUIRED_KEY, false);
stringFieldUpdate.setIndexOptions(IndexOptionHelper.getRawFieldOption());
- nodeFieldUpdate.setNextChange(stringFieldUpdate);
+ jsonFieldUpdate.setNextChange(stringFieldUpdate);
HibUpdateFieldChange htmlFieldUpdate = (HibUpdateFieldChange) ctx.schemaDao().createPersistedChange(version, SchemaChangeOperation.UPDATEFIELD);
htmlFieldUpdate.setFieldName("htmlField");
@@ -280,6 +301,14 @@ public void testAUpdateFields() throws MeshSchemaException {
assertTrue("The index option did not contain the raw field. {" + stringFieldSchema.getElasticsearch().encodePrettily() + "}",
stringFieldSchema.getElasticsearch().containsKey("raw"));
+ // JSON
+ JsonFieldSchema jsonFieldSchema = updatedSchema.getField("jsonField", JsonFieldSchemaImpl.class);
+ assertNotNull(jsonFieldSchema);
+ assertArrayEquals(new JsonSchema[] { new JsonSchema().setProperties(Map.of("content", new JsonSchemaType())).setRequired(new String[] {"content"}) }, jsonFieldSchema.getAllowedSchemas());
+ assertFalse("The required flag should now be set to false.", jsonFieldSchema.isRequired());
+ assertTrue("The index option did not contain the raw field. {" + jsonFieldSchema.getElasticsearch().encodePrettily() + "}",
+ jsonFieldSchema.getElasticsearch().containsKey("raw"));
+
// Html
HtmlFieldSchema htmlFieldSchema = updatedSchema.getField("htmlField", HtmlFieldSchemaImpl.class);
assertNotNull(htmlFieldSchema);
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/field/json/JsonFieldEndpointTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/field/json/JsonFieldEndpointTest.java
new file mode 100644
index 0000000000..a095e57d3c
--- /dev/null
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/schema/field/json/JsonFieldEndpointTest.java
@@ -0,0 +1,66 @@
+package com.gentics.mesh.core.schema.field.json;
+
+import static com.gentics.mesh.core.rest.job.JobStatus.COMPLETED;
+import static com.gentics.mesh.test.ClientHelper.call;
+import static com.gentics.mesh.test.ElasticsearchTestMode.TRACKING;
+import static com.gentics.mesh.test.TestSize.FULL;
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+
+import org.junit.Test;
+
+import com.gentics.mesh.FieldUtil;
+import com.gentics.mesh.core.field.json.JsonFieldTestHelper;
+import com.gentics.mesh.core.rest.JsonSchema;
+import com.gentics.mesh.core.rest.node.NodeUpdateRequest;
+import com.gentics.mesh.core.rest.schema.SchemaModel;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.SchemaUpdateRequest;
+import com.gentics.mesh.json.JsonUtil;
+import com.gentics.mesh.test.MeshTestSetting;
+import com.gentics.mesh.test.context.AbstractMeshTest;
+
+@MeshTestSetting(elasticsearch = TRACKING, testSize = FULL, startServer = true)
+public class JsonFieldEndpointTest extends AbstractMeshTest {
+
+ @Test
+ public void testResetAllowField() {
+ grantAdmin();
+ final String schemaUuid = tx(() -> schemaContainer("content").getUuid());
+ final String nodeUuid = tx(() -> contentUuid());
+
+ // 1. Update schema and set allowed property
+ SchemaModel schema = tx(() -> schemaContainer("content").getLatestVersion().getSchema());
+ SchemaUpdateRequest request = JsonUtil.readValue(schema.toJson(), SchemaUpdateRequest.class);
+ request.addField(new JsonFieldSchemaImpl()
+ .setAllowedSchemas(new JsonSchema("{\"type\":\"object\",\"properties\":{\"firstName\":{\"type\":\"string\"},\"lastName\":{\"type\":\"string\"}},\"required\":[\"firstName\",\"lastName\"]}"))
+ .setName("extraJson"));
+
+ waitForJobs(() -> {
+ call(() -> client().updateSchema(schemaUuid, request));
+ }, COMPLETED, 1);
+
+ // 2. Update the node slug and expect failure due to now allowed string
+ NodeUpdateRequest nodeUpdateRequest = new NodeUpdateRequest();
+ nodeUpdateRequest.setVersion("draft");
+ nodeUpdateRequest.setLanguage("en");
+ nodeUpdateRequest.getFields().put("extraJson", FieldUtil.createJsonField(JsonFieldTestHelper.make("someValue")));
+ call(() -> client().updateNode(projectName(), nodeUuid, nodeUpdateRequest), BAD_REQUEST, "node_error_invalid_json_field_value",
+ "extraJson",
+ JsonUtil.toJson(JsonFieldTestHelper.make("someValue")));
+
+ // 3. Update the schema again with empty allowed value
+ request.removeField("extraJson");
+ request.addField(new JsonFieldSchemaImpl()
+ .setAllowedSchemas(new JsonSchema("{\"type\":\"object\",\"properties\":{\"content\":{\"type\":\"string\"},\"extra\":{\"type\":\"string\"}},\"required\":[\"content\"]}"))
+ .setName("extraJson"));
+
+ waitForJobs(() -> {
+ call(() -> client().updateSchema(schemaUuid, request));
+ }, COMPLETED, 1);
+
+ // 4. Update the node again
+ call(() -> client().updateNode(projectName(), nodeUuid, nodeUpdateRequest));
+
+ }
+
+}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/webrootfield/WebRootFieldTypeTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/webrootfield/WebRootFieldTypeTest.java
index e1c9ecc534..529104f796 100644
--- a/tests/tests-core/src/main/java/com/gentics/mesh/core/webrootfield/WebRootFieldTypeTest.java
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/webrootfield/WebRootFieldTypeTest.java
@@ -11,7 +11,13 @@
import java.io.File;
import java.io.IOException;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -32,17 +38,21 @@
import com.gentics.mesh.core.rest.node.field.BooleanField;
import com.gentics.mesh.core.rest.node.field.DateField;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
import com.gentics.mesh.core.rest.node.field.NumberField;
import com.gentics.mesh.core.rest.node.field.StringField;
import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NodeFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.FieldList;
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.MicronodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NumberFieldListImpl;
@@ -53,6 +63,7 @@
import com.gentics.mesh.core.rest.schema.impl.BooleanFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.DateFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.HtmlFieldSchemaImpl;
+import com.gentics.mesh.core.rest.schema.impl.JsonFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.ListFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicronodeFieldSchemaImpl;
import com.gentics.mesh.core.rest.schema.impl.MicroschemaReferenceImpl;
@@ -266,6 +277,53 @@ private void testStringList(boolean fieldShouldExist, boolean contentShouldExist
testList(fieldShouldExist, contentShouldExist, FieldTypes.STRING, "val=1", "2val", "val3", "val 4");
}
+
+ // JSON object field
+
+ @Test
+ public void testJsonExists() throws IOException {
+ testJson(true, true);
+ }
+
+ @Test
+ public void testJsonNotExists() throws IOException {
+ testJson(true, false);
+ }
+
+ @Test
+ public void testJsonFieldNotExists() throws IOException {
+ testJson(false, false);
+ }
+
+ private void testJson(boolean fieldShouldExist, boolean contentShouldExist) throws IOException {
+ JsonContent value = JsonContent.fromArray(new JsonArray("""
+ ["Mickey", "Mouse"]
+ """));
+
+ Optional maybeField = fieldShouldExist
+ ? Optional.of(new JsonFieldSchemaImpl().setName("json_content").setLabel("JSON object content"))
+ : Optional.empty();
+
+ Optional> maybeContentSupplier = contentShouldExist ? Optional.of(node -> {
+ String uuid = tx(() -> node.getUuid());
+ NodeResponse response = call(() -> client().findNodeByUuid(PROJECT_NAME, uuid,
+ new VersioningParametersImpl().draft(), new NodeParametersImpl().setResolveLinks(LinkType.SHORT)));
+ NodeUpdateRequest request = response.toRequest();
+ JsonField field = new JsonFieldImpl();
+ field.setJson(value);
+ request.getFields().put("json_content", field);
+ call(() -> client().updateNode(PROJECT_NAME, node.getUuid(), request));
+ }) : Optional.empty();
+
+ Consumer resultsConsumer = response -> {
+ assertFalse(response.isPlainText());
+ assertFalse(response.isBinary());
+ Assert.assertEquals(JsonContent.fromString(response.getResponseAsJsonString()), value);
+ };
+
+ testField("/News/2015/News_2015.en.html", maybeField, maybeContentSupplier, resultsConsumer, false);
+ }
+
// Boolean field
@Test
@@ -331,6 +389,40 @@ private void testBooleanList(boolean fieldShouldExist, boolean contentShouldExis
testList(fieldShouldExist, contentShouldExist, FieldTypes.BOOLEAN, false, true, false, true, false);
}
+ // JSON object list field
+
+ @Test
+ public void testJsonListExists() throws IOException {
+ testJsonList(true, true);
+ }
+
+ @Test
+ public void testJsonListNotExists() throws IOException {
+ testJsonList(true, false);
+ }
+
+ @Test
+ public void testJsonListFieldNotExists() throws IOException {
+ testJsonList(false, false);
+ }
+
+ private void testJsonList(boolean fieldShouldExist, boolean contentShouldExist) throws IOException {
+ JsonObject[] values = new JsonObject[] {
+ new JsonObject().put("name", "Mickey"),
+ new JsonObject().put("name", "Minnie"),
+ new JsonObject().put("name", "Donald"),
+ new JsonObject().put("name", "Daisy"),
+ new JsonObject().put("name", "Pluto")
+ };
+ testList(fieldShouldExist, contentShouldExist, FieldTypes.JSON,
+ List.of(values), Optional.of(response -> {
+ assertFalse(response.isPlainText());
+ assertFalse(response.isBinary());
+ JsonArray json = JsonUtil.readValue(response.getResponseAsJsonString(), JsonArray.class);
+ assertTrue(Arrays.equals(IntStream.range(0, json.size()).mapToObj(json::getJsonObject).toArray(size -> new JsonObject[size]), values));
+ }));
+ }
+
// Date field
@Test
@@ -378,7 +470,7 @@ private void testDate(boolean fieldShouldExist, boolean contentShouldExist) thro
testField("/News/2015/News_2015.en.html", maybeField, maybeContentSupplier, resultsConsumer, false);
}
- // Boolean list field
+ // Date list field
@Test
public void testDateListExists() throws IOException {
@@ -684,6 +776,7 @@ private void testNodeList(boolean fieldShouldExist, boolean contentShouldExist)
testList(fieldShouldExist, contentShouldExist, FieldTypes.NODE, list, Optional.of(asserter));
}
+ @SuppressWarnings("unchecked")
private void testList(boolean fieldShouldExist, boolean contentShouldExist, FieldTypes type,
T... listValues) throws IOException {
List values = Arrays.asList(listValues);
@@ -729,6 +822,9 @@ private void testList(boolean fieldShouldExist, boolean conte
case MICRONODE:
fieldList = (FieldList) new MicronodeFieldListImpl();
break;
+ case JSON:
+ fieldList = (FieldList) new JsonFieldListImpl();
+ break;
default:
throw new IllegalArgumentException("Unsupported list item type: " + type.name());
}
@@ -745,7 +841,7 @@ private void testList(boolean fieldShouldExist, boolean conte
} else {
assertFalse(response.isPlainText());
assertFalse(response.isBinary());
- JsonArray json = new JsonArray(response.getResponseAsJsonString());
+ JsonArray json = JsonUtil.readValue(response.getResponseAsJsonString(), JsonArray.class);
assertTrue(Arrays.equals(json.getList().toArray(new Object[values.size()]),
values.toArray(new Object[values.size()])));
}
diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/rest/node/FieldMapTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/rest/node/FieldMapTest.java
index a5248374d2..6583f93ba5 100644
--- a/tests/tests-core/src/main/java/com/gentics/mesh/rest/node/FieldMapTest.java
+++ b/tests/tests-core/src/main/java/com/gentics/mesh/rest/node/FieldMapTest.java
@@ -23,6 +23,8 @@
import com.gentics.mesh.core.rest.node.field.BooleanField;
import com.gentics.mesh.core.rest.node.field.DateField;
import com.gentics.mesh.core.rest.node.field.HtmlField;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.core.rest.node.field.JsonField;
import com.gentics.mesh.core.rest.node.field.MicronodeField;
import com.gentics.mesh.core.rest.node.field.NodeField;
import com.gentics.mesh.core.rest.node.field.NumberField;
@@ -31,6 +33,7 @@
import com.gentics.mesh.core.rest.node.field.impl.BooleanFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.DateFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.HtmlFieldImpl;
+import com.gentics.mesh.core.rest.node.field.impl.JsonFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.NumberFieldImpl;
import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.core.rest.node.field.list.FieldList;
@@ -38,6 +41,7 @@
import com.gentics.mesh.core.rest.node.field.list.impl.BooleanFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.DateFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.HtmlFieldListImpl;
+import com.gentics.mesh.core.rest.node.field.list.impl.JsonFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.MicronodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListImpl;
import com.gentics.mesh.core.rest.node.field.list.impl.NodeFieldListItemImpl;
@@ -46,6 +50,7 @@
import com.gentics.mesh.core.rest.schema.impl.StringFieldSchemaImpl;
import com.gentics.mesh.json.JsonUtil;
+import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
public class FieldMapTest {
@@ -81,6 +86,10 @@ public void testJsonMapNullHandling() throws JsonParseException, JsonMappingExce
fieldMap.put("booleanFieldNull", null);
fieldMap.put("booleanFieldNullValue", new BooleanFieldImpl().setValue(null));
+ fieldMap.put("jsonField", new JsonFieldImpl().setJson(JsonContent.fromArray(new JsonArray().add("whatever").add("wherever"))));
+ fieldMap.put("jsonFieldNull", null);
+ fieldMap.put("jsonFieldNullValue", new JsonFieldImpl().setJson(null));
+
fieldMap.put("micronodeField", new MicronodeResponse());
fieldMap.put("micronodeFieldNull", null);
@@ -139,6 +148,12 @@ public void testJsonMapNullHandling() throws JsonParseException, JsonMappingExce
numberList.add(12);
fieldMap.put("numberListField", numberList);
+ JsonFieldListImpl jsonList = new JsonFieldListImpl();
+ jsonList.add(JsonContent.fromObject(new JsonObject().put("content", "A")));
+ jsonList.add(JsonContent.fromObject(new JsonObject().put("content", "B")));
+ jsonList.add(JsonContent.fromObject(new JsonObject().put("content", "C")));
+ fieldMap.put("jsonListField", jsonList);
+
fieldMap.put("nulled", null);
// Assert fieldmap fields
@@ -161,6 +176,7 @@ public void testJsonMapNullHandling() throws JsonParseException, JsonMappingExce
assertThat(jsonObject).hasNullValue("htmlFieldNull");
assertThat(jsonObject).hasNullValue("dateFieldNull");
assertThat(jsonObject).hasNullValue("stringFieldNull");
+ assertThat(jsonObject).hasNullValue("jsonFieldNull");
assertNotNull(json);
FieldMap fieldMapDeserialized = JsonUtil.readValue(json, FieldMap.class);
@@ -199,6 +215,13 @@ private void assertMap(FieldMap fieldMap) {
assertNull(fieldMap.getField("booleanFieldNullValue", FieldTypes.BOOLEAN, null, false));
assertNull("The field was explicitly set to null and should be null but it was not.", fieldMap.getBooleanField("booleanFieldNull"));
+ JsonField jsonField = fieldMap.getField("jsonField", FieldTypes.JSON, null, false);
+ assertNotNull(jsonField);
+ assertNotNull(fieldMap.getJsonField("jsonField"));
+
+ assertNull(fieldMap.getField("jsonFieldNullValue", FieldTypes.JSON, null, false));
+ assertNull("The field was explicitly set to null and should be null but it was not.", fieldMap.getJsonField("jsonFieldNull"));
+
DateField dateField = fieldMap.getField("dateField", FieldTypes.DATE, null, false);
assertNotNull(dateField);
assertNotNull(fieldMap.getDateField("dateField"));
@@ -248,6 +271,10 @@ private void assertMap(FieldMap fieldMap) {
assertNotNull(booleanList);
assertEquals(3, booleanList.getItems().size());
+ JsonFieldListImpl jsonList = fieldMap.getJsonFieldList("jsonListField");
+ assertNotNull(jsonList);
+ assertEquals(3, jsonList.getItems().size());
+
StringFieldListImpl stringList = fieldMap.getStringFieldList("stringListField");
assertNotNull(stringList);
assertEquals(3, stringList.getItems().size());
@@ -260,7 +287,7 @@ private void assertMap(FieldMap fieldMap) {
assertNotNull(micronodeList);
assertEquals(3, micronodeList.getItems().size());
- assertEquals("The map did not contain the expected amount of fields.", 29, fieldMap.size());
+ assertEquals("The map did not contain the expected amount of fields.", 33, fieldMap.size());
assertFalse("The map should not be empty.", fieldMap.isEmpty());
assertTrue("The string field should be within the map.", fieldMap.hasField("stringField"));
diff --git a/tests/tests-core/src/main/resources/graphql/filtering/nodes-json-field-java b/tests/tests-core/src/main/resources/graphql/filtering/nodes-json-field-java
new file mode 100644
index 0000000000..103b064ecc
--- /dev/null
+++ b/tests/tests-core/src/main/resources/graphql/filtering/nodes-json-field-java
@@ -0,0 +1,87 @@
+{
+ jsonSchema: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json:{hasSchema: "{\"type\":\"object\",\"properties\":{\"firstName\":{\"type\":\"string\"},\"lastName\":{\"type\":\"string\"}},\"required\":[\"firstName\",\"lastName\"]}"}
+ }
+ }
+ }) {
+ # [$.data.jsonSchema.elements.length()=1]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+
+ noJsonSchema: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json:{hasSchema: "{\"type\":\"object\",\"properties\":{\"whatever\":{\"type\":\"string\"},\"whenever\":{\"type\":\"string\"}},\"required\":[\"whatever\",\"whenever\"]}"}
+ }
+ }
+ }) {
+ # [$.data.noJsonSchema.elements.length()=0]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+
+ incompleteJsonSchema: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json:{hasSchema: "{\"type\":\"object\",\"properties\":{\"firstName\":{\"type\":\"string\"},\"lastName\":{\"type\":\"string\"},\"middleName\":{\"type\":\"string\"}},\"required\":[\"firstName\",\"lastName\",\"middleName\"]}"}
+ }
+ }
+ }) {
+ # [$.data.incompleteJsonSchema.elements.length()=0]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+
+ jsonPathFound: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json:{jsonPath: "$[?(@.firstName == 'Mickey')]"}
+ }
+ }
+ }) {
+ # [$.data.jsonPathFound.elements.length()=1]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+
+ jsonPathNotFound: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json:{jsonPath: "$[?(@.firstName == 'Donald')]"}
+ }
+ }
+ }) {
+ # [$.data.jsonPathNotFound.elements.length()=0]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+}
+# [$.errors=]
\ No newline at end of file
diff --git a/tests/tests-core/src/main/resources/graphql/filtering/nodes-json-field-native b/tests/tests-core/src/main/resources/graphql/filtering/nodes-json-field-native
new file mode 100644
index 0000000000..48f3119a3c
--- /dev/null
+++ b/tests/tests-core/src/main/resources/graphql/filtering/nodes-json-field-native
@@ -0,0 +1,90 @@
+{
+ jsonContainsMouse: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json: {like: "%Mouse%"}
+ }
+ }
+ }) {
+ # [$.data.jsonContainsMouse.elements.length()=1]
+ elements {
+ ...output
+ }
+ }
+ notJsonContainsMouse: nodes(filter: {
+ schema: {is: folder}
+ not: {
+ fields: {
+ folder: {
+ json: {like: "%Mouse%"}
+ }
+ }
+ }
+ }) {
+ # [$.data.notJsonContainsMouse.elements.length()=8]
+ elements {
+ ...output
+ }
+ }
+
+ jsonIsNotNull: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json: {isNull: false}
+ }
+ }
+ }) {
+ # [$.data.jsonIsNotNull.elements.length()=1]
+ elements {
+ ...output
+ }
+ }
+ notJsonIsNotNull: nodes(filter: {
+ schema: {is: folder}
+ not: {
+ fields: {
+ folder: {
+ json: {isNull: false}
+ }
+ }
+ }
+ }) {
+ # [$.data.notJsonIsNotNull.elements.length()=8]
+ elements {
+ ...output
+ }
+ }
+ jsonIsNull: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json: {isNull: true}
+ }
+ }
+ }) {
+ # [$.data.jsonIsNull.elements.length()=8]
+ elements {
+ ...output
+ }
+ }
+ notJsonIsNull: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ json: {isNull: true}
+ }
+ }
+ }) {
+ # [$.data.notJsonIsNull.elements.length()=8]
+ elements {
+ ...output
+ }
+ }
+}
+# [$.errors=]
+
+fragment output on Node {
+ uuid
+}
\ No newline at end of file
diff --git a/tests/tests-core/src/main/resources/graphql/filtering/nodes-jsonlist-field-native b/tests/tests-core/src/main/resources/graphql/filtering/nodes-jsonlist-field-native
new file mode 100644
index 0000000000..a384f83a00
--- /dev/null
+++ b/tests/tests-core/src/main/resources/graphql/filtering/nodes-jsonlist-field-native
@@ -0,0 +1,93 @@
+{
+ anyMatch: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ jsonList: {
+ anyMatch: {like: "{\"firstName\":\"Minnie\",\"lastName\":\"Mouse\"}"}
+ }
+ }
+ }
+ }) {
+ # [$.data.anyMatch.elements.length()=1]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+ allMatch: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ jsonList: {
+ allMatch: {like: "{\"firstName\":\"Minnie\",\"lastName\":\"Mouse\"}"}
+ }
+ }
+ }
+ }) {
+ # [$.data.allMatch.elements.length()=0]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+ noneMatch: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ jsonList: {
+ noneMatch: {like: "{\"firstName\":\"Darkwing\",\"lastName\":\"Duck\"}"} isNull:false
+ }
+ }
+ }
+ }) {
+ # [$.data.noneMatch.elements.length()=1]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+ anyNotMatch: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ jsonList: {
+ anyNotMatch: {like: "{\"firstName\":\"Darkwing\",\"lastName\":\"Duck\"}"}
+ }
+ }
+ }
+ }) {
+ # [$.data.anyNotMatch.elements.length()=1]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+ count: nodes(filter: {
+ schema: {is: folder}
+ fields: {
+ folder: {
+ jsonList: {
+ count: {gte: 0}
+ }
+ }
+ }
+ }) {
+ # [$.data.count.elements.length()=9]
+ elements {
+ uuid
+ schema {
+ name
+ }
+ }
+ }
+}
+# [$.errors=]
\ No newline at end of file
diff --git a/tests/tests-core/src/main/resources/graphql/node-fields-query b/tests/tests-core/src/main/resources/graphql/node-fields-query
index 03627fef89..04fac4daef 100644
--- a/tests/tests-core/src/main/resources/graphql/node-fields-query
+++ b/tests/tests-core/src/main/resources/graphql/node-fields-query
@@ -59,6 +59,15 @@
nodeList { uuid }
booleanList
numberList
+ jsonList {
+ # [$.data.node.fields.jsonList[0]=]
+ text
+ }
+
+ json {
+ # [$.data.node.fields.json.json.firstName=Mickey]
+ json
+ }
# [$.data.node.fields.boolean=true]
boolean
@@ -137,6 +146,10 @@
# [$.data.node.fields.micronodeList[1].fields.nodeList[0].language=en]
# [$.data.node.fields.micronodeList[1].fields.nodeList[1].language=en]
language
+ }
+ json {
+ # [$.data.node.fields.micronodeList[1].fields.json.json.firstName=Donald]
+ json
}
}
}
diff --git a/tests/tests-core/src/main/resources/graphql/node-fields-query.v1 b/tests/tests-core/src/main/resources/graphql/node-fields-query.v1
index 353803cf70..5daa241152 100644
--- a/tests/tests-core/src/main/resources/graphql/node-fields-query.v1
+++ b/tests/tests-core/src/main/resources/graphql/node-fields-query.v1
@@ -64,6 +64,15 @@
nodeList { uuid }
booleanList
numberList
+ jsonList {
+ # [$.data.node.fields.jsonList[0]=]
+ text
+ }
+
+ json {
+ # [$.data.node.fields.json.json.firstName=Mickey]
+ json
+ }
# [$.data.node.fields.boolean=true]
boolean
@@ -114,6 +123,10 @@
# [$.data.node.fields.micronodeList[1].nodeList[1].language=en]
language
}
+ json {
+ # [$.data.node.fields.micronodeList[1].json.json.firstName=Donald]
+ json
+ }
}
}
}
diff --git a/verticles/graphql/pom.xml b/verticles/graphql/pom.xml
index 97545e2b34..0f7f9b879b 100644
--- a/verticles/graphql/pom.xml
+++ b/verticles/graphql/pom.xml
@@ -45,6 +45,10 @@
com.gentics.graphqlfilter
graphql-java-filter
+
+ com.jayway.jsonpath
+ json-path
+
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/GraphQLHandler.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/GraphQLHandler.java
index d3799f7f4e..3b02c79ad6 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/GraphQLHandler.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/GraphQLHandler.java
@@ -18,6 +18,8 @@
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderOptions;
import org.dataloader.DataLoaderRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.gentics.mesh.core.db.Database;
import com.gentics.mesh.core.rest.error.AbstractUnavailableException;
@@ -27,6 +29,7 @@
import com.gentics.mesh.graphql.dataloader.NodeDataLoader;
import com.gentics.mesh.graphql.type.QueryTypeProvider;
import com.gentics.mesh.graphql.type.field.FieldDefinitionProvider;
+import com.gentics.mesh.json.JsonUtil;
import com.gentics.mesh.metric.MetricsService;
import com.gentics.mesh.metric.SimpleMetric;
@@ -40,8 +43,6 @@
import io.micrometer.core.instrument.Timer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import io.vertx.reactivex.core.Vertx;
/**
@@ -110,6 +111,7 @@ public void handleQuery(GraphQLContext gc, String body) {
dataLoaderRegistry.register(NodeDataLoader.NODE_REFERENCE_LOADER_KEY, DataLoader.newDataLoader(NodeDataLoader.NODE_REFERENCE_LOADER, dlOptions));
dataLoaderRegistry.register(FieldDefinitionProvider.LINK_REPLACER_DATA_LOADER_KEY, DataLoader.newDataLoader(typeProvider.getFieldDefProvider().LINK_REPLACER_LOADER, dlOptions));
dataLoaderRegistry.register(FieldDefinitionProvider.BOOLEAN_LIST_VALUES_DATA_LOADER_KEY, DataLoader.newDataLoader(typeProvider.getFieldDefProvider().BOOLEAN_LIST_VALUE_LOADER, dlOptions));
+ dataLoaderRegistry.register(FieldDefinitionProvider.JSON_LIST_VALUES_DATA_LOADER_KEY, DataLoader.newDataLoader(typeProvider.getFieldDefProvider().JSON_LIST_VALUE_LOADER, dlOptions));
dataLoaderRegistry.register(FieldDefinitionProvider.DATE_LIST_VALUES_DATA_LOADER_KEY, DataLoader.newDataLoader(typeProvider.getFieldDefProvider().DATE_LIST_VALUE_LOADER, dlOptions));
dataLoaderRegistry.register(FieldDefinitionProvider.NUMBER_LIST_VALUES_DATA_LOADER_KEY, DataLoader.newDataLoader(typeProvider.getFieldDefProvider().NUMBER_LIST_VALUE_LOADER, dlOptions));
dataLoaderRegistry.register(FieldDefinitionProvider.HTML_LIST_VALUES_DATA_LOADER_KEY, DataLoader.newDataLoader(typeProvider.getFieldDefProvider().HTML_LIST_VALUE_LOADER, dlOptions));
@@ -152,7 +154,7 @@ public void handleQuery(GraphQLContext gc, String body) {
response.put("data", new JsonObject(data));
}
boolean minify = gc.isMinify(options.getHttpServerOptions());
- gc.send(minify ? response.encode() : response.encodePrettily(), OK);
+ gc.send(JsonUtil.toJson(response, minify), OK);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
// If an error happens while "waiting" for the result, we log the GraphQL query here.
log.error("GraphQL query failed after {} ms with {}:\n{}\nvariables: {}",
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/FieldFilter.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/FieldFilter.java
index 3dcf3900c6..97e78f3e53 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/FieldFilter.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/FieldFilter.java
@@ -20,6 +20,7 @@
import com.gentics.mesh.core.data.node.field.HibBooleanField;
import com.gentics.mesh.core.data.node.field.HibDateField;
import com.gentics.mesh.core.data.node.field.HibHtmlField;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
import com.gentics.mesh.core.data.node.field.HibStringField;
import com.gentics.mesh.core.data.node.field.list.HibListField;
import com.gentics.mesh.core.data.schema.HibFieldSchemaVersionElement;
@@ -88,6 +89,9 @@ private FieldFilter(HibFieldSchemaVersionElement,?,?,?,?> schemaVersion, Graph
case BOOLEAN:
return new FieldMappedFilter<>(type, name, description, BooleanFilter.filter(),
node -> node == null ? null : getOrNull(node.getBoolean(name), HibBooleanField::getBoolean), schema);
+ case JSON:
+ return new FieldMappedFilter<>(type, name, description, JsonFilter.filter(),
+ node -> node == null ? null : getOrNull(node.getJson(name), HibJsonField::getJson), schema);
case NUMBER:
return new FieldMappedFilter<>(type, name, description, NumberFilter.filter(),
node -> node == null ? null : getOrNull(node.getNumber(name), val -> new BigDecimal(val.getNumber().toString())), schema);
@@ -124,6 +128,10 @@ private FieldFilter(HibFieldSchemaVersionElement,?,?,?,?> schemaVersion, Graph
listFilter = ListFilter.booleanListFilter();
listFieldGetter = node -> node.getBooleanList(name);
break;
+ case JSON:
+ listFilter = ListFilter.jsonListFilter();
+ listFieldGetter = node -> node.getJsonList(name);
+ break;
case DATE:
listFilter = ListFilter.dateListFilter();
listFieldGetter = node -> node.getDateList(name);
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/JsonFilter.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/JsonFilter.java
new file mode 100644
index 0000000000..ca57519b41
--- /dev/null
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/JsonFilter.java
@@ -0,0 +1,147 @@
+package com.gentics.mesh.graphql.filter;
+
+import static com.gentics.graphqlfilter.util.FilterUtil.nullablePredicate;
+import static graphql.Scalars.GraphQLString;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gentics.graphqlfilter.filter.FilterField;
+import com.gentics.graphqlfilter.filter.MainFilter;
+import com.gentics.graphqlfilter.filter.operation.Comparison;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
+import com.gentics.mesh.json.JsonUtil;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.ParseContext;
+import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
+import com.jayway.jsonpath.spi.json.JsonProvider;
+
+import io.vertx.core.json.DecodeException;
+import io.vertx.core.json.JsonObject;
+import io.vertx.reactivex.json.schema.JsonSchema;
+import io.vertx.reactivex.json.schema.Validator;
+
+public class JsonFilter extends MainFilter {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonFilter.class);
+
+ private static final String UNDERSCORE_PLACEHOLDER = "\\{UNS}\\";
+ private static final String PERCENT_PLACEHOLDER = "\\{REM}\\";
+ private static final String DOT_PLACEHOLDER = "\\{DOT}\\";
+ private static JsonFilter instance;
+
+ /**
+ * Filters JSON strings by various means
+ */
+ public static synchronized JsonFilter filter() {
+ if (instance == null) {
+ instance = new JsonFilter(null);
+ }
+ return instance;
+ }
+
+ public JsonFilter(String owner) {
+ super("JsonFilter", "Filters Jsons", true, Optional.ofNullable(owner));
+ }
+
+ @Override
+ protected List> getFilters() {
+ return Arrays.asList(
+ FilterField.isNull(),
+ FilterField.create("like", "Checks if the JSON object matches the given SQL LIKE expression.", GraphQLString, likePredicate(),
+ Optional.of((query) -> Comparison.like(query.makeFieldOperand(Optional.empty()), query.makeValueOperand(true), query.getInitiatingFilterName()))),
+ FilterField.create("regex", "Checks if the JSON object representation matches the given regular expression.", GraphQLString, regexPredicate(),
+ Optional.empty()),
+ FilterField.create("hasSchema", "Tests if the object has the given JSON schema.", GraphQLString, objectSchemaPredicate(),
+ Optional.empty()),
+ FilterField.create("jsonPath", "Tests the given JSON schema against JsonPath value.", GraphQLString, jsonPathPredicate(),
+ Optional.empty()));
+ }
+
+ private Function> jsonPathPredicate() {
+ return query -> {
+ JsonPath jsonPath = JsonPath.compile(query);
+ JsonProvider provider = new JacksonJsonProvider(JsonUtil.getMapper());
+ ParseContext context = JsonPath.using(provider);
+ return nullablePredicate(object -> {
+ Object parsed = context.parse(JsonUtil.toJson(object)).read(jsonPath);
+ if (parsed == null) {
+ return false;
+ } else if (parsed instanceof List list) {
+ return !list.isEmpty();
+ } else {
+ return true;
+ }
+ });
+ };
+ }
+
+ /**
+ * Parse a collection of JSON objects
+ *
+ * @param objects
+ * @return
+ */
+ public static Collection parseJsons(Collection objects) {
+ return objects.stream().map(JsonFilter::parseJson).collect(Collectors.toList());
+ }
+
+ /**
+ * Try parse the given string to a JSON object, or return an empty one.
+ *
+ * @param object
+ * @return
+ */
+ public static JsonContent parseJson(String object) {
+ try {
+ Object decoded = JsonUtil.readValue(object, JsonContent.class);
+ if (decoded instanceof JsonContent o) {
+ return o;
+ }
+ } catch (DecodeException e) {
+ log.warn("JSON decode failed for " + object, e);
+ }
+ return null;
+ }
+
+ private String likeToRegex(String likeQuery) {
+ return likeQuery
+ .replace("\\.", DOT_PLACEHOLDER).replace("\\%", PERCENT_PLACEHOLDER).replace("\\_", UNDERSCORE_PLACEHOLDER)
+ .replace(".", "\\.").replace("%", ".*?").replace("_", ".")
+ .replace(DOT_PLACEHOLDER, ".").replace(PERCENT_PLACEHOLDER, "%").replace(UNDERSCORE_PLACEHOLDER, "_");
+ }
+
+ private Function> objectSchemaPredicate() {
+ return query -> {
+ JsonSchema schema = JsonSchema.of(new JsonObject(query));
+ Validator validator = JsonUtil.newJsonSchemaValidator(schema);
+ return nullablePredicate(object -> {
+ Object jsonContent = object.isArray() ? object.getArray() : object.getObject();
+ return validator.validate(jsonContent).getValid() == Boolean.TRUE;
+ });
+ };
+ }
+
+ private Function> likePredicate() {
+ return query -> {
+ Pattern regex = Pattern.compile(likeToRegex(query));
+ return nullablePredicate(object -> regex.matcher(JsonUtil.toJson(object)).find());
+ };
+ }
+
+ private Function> regexPredicate() {
+ return query -> {
+ Pattern regex = Pattern.compile(query);
+ return nullablePredicate(object -> regex.matcher(JsonUtil.toJson(object)).find());
+ };
+ }
+}
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/ListFilter.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/ListFilter.java
index ded24c2de7..f31ada38a6 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/ListFilter.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/filter/ListFilter.java
@@ -30,6 +30,7 @@
import com.gentics.mesh.core.data.s3binary.S3HibBinaryField;
import com.gentics.mesh.core.db.CommonTx;
import com.gentics.mesh.core.db.Tx;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.graphql.context.GraphQLContext;
import com.gentics.mesh.graphql.filter.operation.ListItemOperationOperand;
import com.gentics.mesh.graphql.model.NodeReferenceIn;
@@ -55,6 +56,7 @@ public class ListFilter extends MainFilter> {
private static ListFilter htmlListFilterInstance;
private static ListFilter numberListFilterInstance;
private static ListFilter booleanListFilterInstance;
+ private static ListFilter jsonListFilterInstance;
private static ListFilter dateListFilterInstance;
private static ListFilter nodeListFilterInstance;
private static ListFilter micronodeListFilterInstance;
@@ -172,6 +174,13 @@ public boolean isReferenceItem() {
return booleanListFilterInstance;
}
+ public static final ListFilter jsonListFilter() {
+ if (jsonListFilterInstance == null) {
+ jsonListFilterInstance = new ListFilter<>("JsonListFilter", "Filters JSON object lists", JsonFilter.filter(), Optional.of("JSONLIST"), false);
+ }
+ return jsonListFilterInstance;
+ }
+
public static final ListFilter dateListFilter() {
if (dateListFilterInstance == null) {
dateListFilterInstance = new ListFilter<>("DateListFilter", "Filters date lists", DateFilter.filter(), Optional.of("DATELIST"), false);
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/NodeTypeProvider.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/NodeTypeProvider.java
index eba87db209..b469d86118 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/NodeTypeProvider.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/NodeTypeProvider.java
@@ -859,6 +859,9 @@ private List generateSchemaFieldTypesV1(GraphQLContext contex
case BOOLEAN:
root.field(fields.createBooleanDef(fieldSchema));
break;
+ case JSON:
+ root.field(fields.createJsonDef(fieldSchema));
+ break;
case NODE:
root.field(fields.createNodeDef(fieldSchema));
break;
@@ -924,6 +927,9 @@ private List generateSchemaFieldTypesV2(GraphQLContext contex
case BOOLEAN:
fieldsType.field(fields.createBooleanDef(fieldSchema));
break;
+ case JSON:
+ fieldsType.field(fields.createJsonDef(fieldSchema));
+ break;
case NODE:
fieldsType.field(fields.createNodeDef(fieldSchema));
break;
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/QueryTypeProvider.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/QueryTypeProvider.java
index cb8a4f8459..d880267909 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/QueryTypeProvider.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/QueryTypeProvider.java
@@ -679,6 +679,7 @@ public GraphQLSchema getRootSchema(GraphQLContext context) {
additionalTypes.add(interfaceTypeProvider.createPermInfoType());
additionalTypes.add(fieldDefProvider.createBinaryFieldType());
additionalTypes.add(fieldDefProvider.createS3BinaryFieldType());
+ additionalTypes.add(fieldDefProvider.createJsonFieldType());
Versioned.doSince(2, context, () -> {
List schemaFieldTypes = context.getOrStore(NodeTypeProvider.SCHEMA_FIELD_TYPES, () -> nodeTypeProvider.generateSchemaFieldTypes(context).forVersion(context));
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/FieldDefinitionProvider.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/FieldDefinitionProvider.java
index be494ce868..dcff5c4f10 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/FieldDefinitionProvider.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/FieldDefinitionProvider.java
@@ -14,7 +14,6 @@
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLObjectType.newObject;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -35,7 +34,6 @@
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.apache.commons.collections.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.dataloader.BatchLoaderWithContext;
@@ -54,11 +52,13 @@
import com.gentics.mesh.core.data.node.field.HibBooleanField;
import com.gentics.mesh.core.data.node.field.HibDateField;
import com.gentics.mesh.core.data.node.field.HibHtmlField;
+import com.gentics.mesh.core.data.node.field.HibJsonField;
import com.gentics.mesh.core.data.node.field.HibNumberField;
import com.gentics.mesh.core.data.node.field.HibStringField;
import com.gentics.mesh.core.data.node.field.list.HibBooleanFieldList;
import com.gentics.mesh.core.data.node.field.list.HibDateFieldList;
import com.gentics.mesh.core.data.node.field.list.HibHtmlFieldList;
+import com.gentics.mesh.core.data.node.field.list.HibJsonFieldList;
import com.gentics.mesh.core.data.node.field.list.HibMicronodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNodeFieldList;
import com.gentics.mesh.core.data.node.field.list.HibNumberFieldList;
@@ -71,6 +71,8 @@
import com.gentics.mesh.core.db.Tx;
import com.gentics.mesh.core.link.WebRootLinkReplacerImpl;
import com.gentics.mesh.core.rest.common.ContainerType;
+import com.gentics.mesh.core.rest.common.FieldTypes;
+import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.core.rest.node.field.image.FocalPoint;
import com.gentics.mesh.core.rest.schema.FieldSchema;
import com.gentics.mesh.core.rest.schema.ListFieldSchema;
@@ -83,11 +85,12 @@
import com.gentics.mesh.graphql.filter.NodeFilter;
import com.gentics.mesh.graphql.type.AbstractTypeProvider;
import com.gentics.mesh.graphql.type.NodeTypeProvider;
+import com.gentics.mesh.json.JsonUtil;
import com.gentics.mesh.parameter.LinkType;
-import com.gentics.mesh.parameter.image.CropMode;
import com.gentics.mesh.util.DateUtils;
import com.google.common.base.Functions;
+import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLObjectType;
@@ -96,11 +99,13 @@
import graphql.schema.GraphQLTypeReference;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Promise;
+import io.vertx.core.json.JsonObject;
@Singleton
public class FieldDefinitionProvider extends AbstractTypeProvider {
public static final String BINARY_FIELD_TYPE_NAME = "BinaryField";
+ public static final String JSON_FIELD_TYPE_NAME = "JsonField";
public static final String S3_BINARY_FIELD_TYPE_NAME = "S3BinaryField";
/**
@@ -113,6 +118,11 @@ public class FieldDefinitionProvider extends AbstractTypeProvider {
*/
public static final String BOOLEAN_LIST_VALUES_DATA_LOADER_KEY = "booleanListLoader";
+ /**
+ * Key for the data loader for JSON object list field values
+ */
+ public static final String JSON_LIST_VALUES_DATA_LOADER_KEY = "jsonListLoader";
+
/**
* Key for the data loader for date list field values
*/
@@ -207,6 +217,14 @@ private static CompletionStage>> listValueDataLoader(List> JSON_LIST_VALUE_LOADER = (keys, environment) -> {
+ ContentDao contentDao = Tx.get().contentDao();
+ return listValueDataLoader(keys, contentDao::getJsonListFieldValues, Functions.identity());
+ };
+
/**
* DataLoader implementation for values of date lists
*/
@@ -322,6 +340,21 @@ public FieldDefinitionProvider(MeshOptions options, MicronodeFieldTypeProvider m
};
}
+ public GraphQLObjectType createJsonFieldType() {
+ Builder type = newObject().name(JSON_FIELD_TYPE_NAME).description("JSON object field");
+
+ type.field(newFieldDefinition().name("text").description("Value as JSON string").type(GraphQLString).dataFetcher(fetcher -> {
+ JsonContent json = fetcher.getSource();
+ return json == null ? null : JsonUtil.toJson(json, options.getHttpServerOptions().isMinifyJson());
+ }));
+ type.field(newFieldDefinition().name("json").description("Value as JSON object").type(ExtendedScalars.Json).dataFetcher(fetcher -> {
+ JsonContent json = fetcher.getSource();
+ return json == null ? null : json;
+ }));
+
+ return type.build();
+ }
+
public GraphQLObjectType createBinaryFieldType() {
Builder type = newObject().name(BINARY_FIELD_TYPE_NAME).description("Binary field");
@@ -582,6 +615,17 @@ public GraphQLFieldDefinition createBooleanDef(FieldSchema schema) {
}).build();
}
+ public GraphQLFieldDefinition createJsonDef(FieldSchema schema) {
+ return newFieldDefinition().name(schema.getName()).description(schema.getLabel()).type(new GraphQLTypeReference(JSON_FIELD_TYPE_NAME)).dataFetcher(env -> {
+ HibFieldContainer container = env.getSource();
+ HibJsonField jsonField = container.getJson(schema.getName());
+ if (jsonField != null) {
+ return jsonField.getJson();
+ }
+ return null;
+ }).build();
+ }
+
public GraphQLFieldDefinition createNumberDef(FieldSchema schema) {
return newFieldDefinition().name(schema.getName()).description(schema.getLabel()).type(GraphQLBigDecimal).dataFetcher(env -> {
HibFieldContainer container = env.getSource();
@@ -672,15 +716,18 @@ public Optional createListDef(GraphQLContext context, Li
NodeFilter nodeFilter = NodeFilter.filter(context);
// Add link resolving arg to html and string lists
- switch (schema.getListType()) {
- case "html":
- case "string":
+ switch (FieldTypes.valueByName(schema.getListType())) {
+ case HTML:
+ case STRING:
+ case JSON:
fieldType.argument(createLinkTypeArg());
break;
- case "node":
+ case NODE:
fieldType.argument(createNodeVersionArg());
fieldType.argument(nodeFilter.createFilterArgument());
break;
+ default:
+ break;
}
fieldType.dataFetcher(env -> {
@@ -689,8 +736,8 @@ public Optional createListDef(GraphQLContext context, Li
HibFieldContainer container = env.getSource();
GraphQLContext gc = env.getContext();
- switch (schema.getListType()) {
- case "boolean":
+ switch (FieldTypes.valueByName(schema.getListType())) {
+ case BOOLEAN:
HibBooleanFieldList booleanList = container.getBooleanList(schema.getName());
if (booleanList == null) {
return null;
@@ -703,7 +750,20 @@ public Optional createListDef(GraphQLContext context, Li
} else {
return booleanList.getList().stream().map(item -> item.getBoolean()).collect(Collectors.toList());
}
- case "html":
+ case JSON:
+ HibJsonFieldList jsonList = container.getJsonList(schema.getName());
+ if (jsonList == null) {
+ return null;
+ }
+
+ String jsonListUuid = jsonList.getUuid();
+ if (contentDao.supportsPrefetchingListFieldValues() && !StringUtils.isEmpty(jsonListUuid)) {
+ DataLoader> jsonListValueLoader = env.getDataLoader(FieldDefinitionProvider.JSON_LIST_VALUES_DATA_LOADER_KEY);
+ return jsonListValueLoader.load(jsonListUuid);
+ } else {
+ return jsonList.getList();
+ }
+ case HTML:
HibHtmlFieldList htmlList = container.getHTMLList(schema.getName());
if (htmlList == null) {
return null;
@@ -732,7 +792,7 @@ public Optional createListDef(GraphQLContext context, Li
Arrays.asList(container.getLanguageTag()));
}).collect(Collectors.toList());
}
- case "string":
+ case STRING:
HibStringFieldList stringList = container.getStringList(schema.getName());
if (stringList == null) {
return null;
@@ -761,7 +821,7 @@ public Optional createListDef(GraphQLContext context, Li
Arrays.asList(container.getLanguageTag()));
}).collect(Collectors.toList());
}
- case "number":
+ case NUMBER:
HibNumberFieldList numberList = container.getNumberList(schema.getName());
if (numberList == null) {
return null;
@@ -774,7 +834,7 @@ public Optional createListDef(GraphQLContext context, Li
} else {
return numberList.getList().stream().map(item -> item.getNumber()).collect(Collectors.toList());
}
- case "date":
+ case DATE:
HibDateFieldList dateList = container.getDateList(schema.getName());
if (dateList == null) {
return null;
@@ -787,7 +847,7 @@ public Optional createListDef(GraphQLContext context, Li
} else {
return dateList.getList().stream().map(item -> DateUtils.toISO8601(item.getDate(), 0)).collect(Collectors.toList());
}
- case "node":
+ case NODE:
HibNodeFieldList nodeList = container.getNodeList(schema.getName());
if (nodeList == null) {
return null;
@@ -837,7 +897,7 @@ public Optional createListDef(GraphQLContext context, Li
.filter(content1 -> gc.hasReadPerm(content1, nodeType))
.collect(Collectors.toList());
}
- case "micronode":
+ case MICRONODE:
HibMicronodeFieldList micronodeList = container.getMicronodeList(schema.getName());
if (micronodeList == null) {
return null;
@@ -858,21 +918,21 @@ public Optional createListDef(GraphQLContext context, Li
}
private GraphQLType getElementTypeOfList(ListFieldSchema schema) {
- switch (schema.getListType()) {
- case "boolean":
+ switch (FieldTypes.valueByName(schema.getListType())) {
+ case BOOLEAN:
return GraphQLBoolean;
- case "html":
- return GraphQLString;
- case "string":
+ case DATE:
+ case HTML:
+ case STRING:
return GraphQLString;
- case "number":
+ case NUMBER:
return GraphQLBigDecimal;
- case "date":
- return GraphQLString;
- case "node":
+ case NODE:
return new GraphQLTypeReference(NODE_TYPE_NAME);
- case "micronode":
+ case MICRONODE:
return new GraphQLTypeReference(MICRONODE_TYPE_NAME);
+ case JSON:
+ return new GraphQLTypeReference(JSON_FIELD_TYPE_NAME);
default:
return null;
}
diff --git a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/MicronodeFieldTypeProvider.java b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/MicronodeFieldTypeProvider.java
index 2612471ed4..555b724cbb 100644
--- a/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/MicronodeFieldTypeProvider.java
+++ b/verticles/graphql/src/main/java/com/gentics/mesh/graphql/type/field/MicronodeFieldTypeProvider.java
@@ -19,6 +19,8 @@
import javax.inject.Singleton;
import org.apache.commons.collections.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.gentics.mesh.core.data.HibElement;
import com.gentics.mesh.core.data.dao.MicroschemaDao;
@@ -47,8 +49,6 @@
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.GraphQLUnionType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
@Singleton
public class MicronodeFieldTypeProvider extends AbstractTypeProvider {
@@ -162,6 +162,9 @@ public Versioned> generateMicroschemaFieldTypes(GraphQLC
case BOOLEAN:
microschemaType.field(fields.get().createBooleanDef(fieldSchema).transform(addDeprecation));
break;
+ case JSON:
+ microschemaType.field(fields.get().createJsonDef(fieldSchema).transform(addDeprecation));
+ break;
case NODE:
microschemaType.field(fields.get().createNodeDef(fieldSchema).transform(addDeprecation));
break;
@@ -218,6 +221,9 @@ public Versioned> generateMicroschemaFieldTypes(GraphQLC
case BOOLEAN:
fieldsType.field(fields.get().createBooleanDef(fieldSchema));
break;
+ case JSON:
+ fieldsType.field(fields.get().createJsonDef(fieldSchema));
+ break;
case NODE:
fieldsType.field(fields.get().createNodeDef(fieldSchema));
break;