Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5da6ba9
GPU-2187: JSON type
plyhun Apr 21, 2026
b1458c0
Test
plyhun Apr 22, 2026
609b480
Resolvers + tests
plyhun Apr 23, 2026
0b1d2f0
Cleanup
plyhun Apr 24, 2026
ed8808a
Cleanup. Changelog.
plyhun Apr 24, 2026
a5db560
Regression fix. More tests.
plyhun Apr 24, 2026
d719fbe
Missing UI parts
plyhun Apr 24, 2026
e810319
Missing test data
plyhun Apr 27, 2026
f5947a4
Mesh test version
plyhun Apr 27, 2026
5eed3f6
Missing translations
plyhun Apr 27, 2026
0b4982e
UI regression fixes
plyhun Apr 27, 2026
aa8d850
JSON filter / Mesh
plyhun Apr 28, 2026
7a7d690
JSON schema / CMS
plyhun Apr 28, 2026
f79372a
Regression fixes
plyhun Apr 28, 2026
fe307f2
Regression
plyhun Apr 28, 2026
c024568
Missing Mesh Browser parts
plyhun Apr 28, 2026
3d674a1
Extra server side JSON validation
plyhun Apr 28, 2026
3be2062
Mesh JS deps update
plyhun Apr 29, 2026
ebf3655
Package lock update
plyhun Apr 29, 2026
2a14dbc
Uncomment Mesh Browser features
plyhun Apr 29, 2026
1480f7b
Merge branch 'dev' into f-gpu-2187
plyhun Apr 29, 2026
a9e637f
Integrated
plyhun Apr 29, 2026
5318a5f
JSON value validation fix
plyhun Apr 29, 2026
3c77f0e
Mesh Roles from JSON
plyhun Apr 29, 2026
69f04d0
Role resolving works & tested
plyhun Apr 30, 2026
d58474a
Schema limits work
plyhun Apr 30, 2026
2235e81
Minor fixes
plyhun Apr 30, 2026
adaf95c
Merge remote-tracking branch 'origin/dev' into f-gpu-2187
plyhun Jul 22, 2026
73a2392
Merge branch 'dev' into f-gpu-2187
plyhun Jul 22, 2026
3fb1e54
Align field naming with the convention. Mesh image test version 3.3.0.
plyhun Jul 23, 2026
1aa9f69
Missing SQL fixes
plyhun Jul 23, 2026
6937928
UI changes
plyhun Jul 23, 2026
c544da6
SUP-20111: Jetty Security update
plyhun Jul 23, 2026
d774320
Pass the schema limits to the devtools
plyhun Jul 23, 2026
4ea4694
Update saving of json array schema limits
plyhun Jul 24, 2026
75b7e19
Fix for the HTTP 500 over JSON validation
plyhun Jul 27, 2026
1476dc6
Revert "Update saving of json array schema limits"
plyhun Jul 27, 2026
23c772e
Validation error improved
plyhun Jul 28, 2026
a7e980b
Fix for an improper JSON validation
plyhun Jul 28, 2026
6889c98
Docu
plyhun Jul 29, 2026
a200cfa
Correctly process server side validation errors
plyhun Jul 29, 2026
761eb3f
Length resolving. More tests.
plyhun Jul 30, 2026
2b6a4cc
Merge branch 'dev' into f-gpu-2187
plyhun Aug 6, 2026
24a7308
CR fixes
plyhun Aug 7, 2026
52cd089
Use feature Mesh version
plyhun Aug 19, 2026
96b8c6e
Fix translation
npomaroli Sep 7, 2026
ed97b44
Rename helper to gtx_json_path
npomaroli Sep 7, 2026
0ee8cf7
Fix filename in devtool package
npomaroli Sep 7, 2026
610539e
Missing validation + tests
plyhun Sep 8, 2026
62998c2
Refactor usage of JsonContent
npomaroli Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cms-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@
<artifactId>handlebars</artifactId>
</dependency>

<!-- JSON -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@ protected Part transform(PartModel from, Part to) throws NodeException {
to.setExternalEditorUrl(from.getExternalEditorUrl());

switch (to.getPartTypeId()) {
case Part.JSON:
to.setInfoText(from.getHtmlClass());
// fallthrough
case Part.TEXT:
case Part.TEXTHMTL:
case Part.HTML:
Expand All @@ -455,11 +458,6 @@ protected Part transform(PartModel from, Part to) throws NodeException {
case Part.HTMLLONG:
to.setInfoInt(from.getRegexId());
break;
default:
break;
}

switch (to.getPartTypeId()) {
case Part.LIST:
case Part.LISTORDERED:
case Part.LISTUNORDERED:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,10 @@ private static class FactoryTagmapEntry extends TagmapEntry {
@Updateable
protected String micronodeFilter;

@DataField("json_schema_filter")
@Updateable
protected String jsonSchemaFilter;

/**
* Create an empty instance
* @param info info
Expand Down Expand Up @@ -1678,6 +1682,11 @@ public String getMicronodeFilter() {
return micronodeFilter;
}

@Override
public String getJSONSchemaFilter() {
return jsonSchemaFilter;
}

@Override
public void delete(boolean force) throws InsufficientPrivilegesException,
NodeException {
Expand Down Expand Up @@ -1887,6 +1896,14 @@ public void setMicronodeFilter(String micronodeFilter) throws ReadOnlyException
}
}

@Override
public void setJSONSchemaFilter(String jsonSchemaFilter) throws ReadOnlyException {
if (!StringUtils.isEqual(this.jsonSchemaFilter, jsonSchemaFilter)) {
this.jsonSchemaFilter = jsonSchemaFilter;
this.modified = true;
}
}

@Override
public boolean save() throws InsufficientPrivilegesException,
NodeException {
Expand Down Expand Up @@ -2224,7 +2241,7 @@ private static class FactoryCrFragmentEntry extends AbstractContentObject implem
protected int crFragmentId;

@RestModel(update = { "tagname", "mapname", "obj_type", "attribute_type", "multivalue", "optimized", "filesystem", "target_type",
"foreignlink_attribute", "foreignlink_attribute_rule", "category", "displayfield", "segmentfield", "urlfield", "no_index", "elasticsearch", "micronode_filter" })
"foreignlink_attribute", "foreignlink_attribute_rule", "category", "displayfield", "segmentfield", "urlfield", "no_index", "elasticsearch", "micronode_filter", "json_schema_filter" })
protected ContentRepositoryFragmentEntryModel model;

protected AttributeType attributeType;
Expand Down Expand Up @@ -2385,6 +2402,11 @@ public String getMicronodeFilter() {
return model.getMicronodeFilter();
}

@Override
public String getJsonSchemaFilter() {
return model.getJSONSchemaFilter();
}

@Override
public ContentRepositoryFragmentEntryModel getModel() {
return model;
Expand Down Expand Up @@ -2500,6 +2522,14 @@ public void setMicronodeFilter(String micronodeFilter) throws ReadOnlyException
}
}

@Override
public void setJSONSchemaFilter(String jsonSchemaFilter) throws ReadOnlyException {
if (!StringUtils.isEqual(model.getJSONSchemaFilter(), jsonSchemaFilter)) {
model.setJSONSchemaFilter(jsonSchemaFilter);
this.modified = true;
}
}

@Override
public void setFilesystem(boolean filesystem) throws ReadOnlyException {
if (isFilesystem() != filesystem) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -25,6 +26,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.apache.commons.lang3.tuple.Pair;

Expand Down Expand Up @@ -75,9 +77,13 @@
import com.gentics.contentnode.runtime.NodeConfigRuntimeConfiguration;
import com.gentics.lib.db.SQLExecutor;
import com.gentics.lib.etc.StringUtils;
import com.gentics.mesh.core.rest.JsonSchema;
import com.gentics.mesh.core.rest.node.field.JsonContent;
import com.gentics.mesh.json.JsonUtil;

import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import io.vertx.core.json.JsonArray;

/**
* An objectfactory which can create {@link ContentTag}, {@link TemplateTag}
Expand Down Expand Up @@ -156,6 +162,10 @@ public class TagFactory extends AbstractFactory {
*/
public final static String SYNC_RUNNING_ATTRIBUTENAME = "objtag.sync_running";

/**
* Error log
*/
public final static String LOG_JSON_VALIDATION_ERROR = "JSON Validation error";
/**
* Implementation class for a ContentTag
*/
Expand Down Expand Up @@ -1553,6 +1563,7 @@ public boolean save() throws InsufficientPrivilegesException, NodeException {
if (valueIds != null) {
valueIdsToRemove.addAll(valueIds);
}
validateObjectTagObject(this);
for (Value value : values) {
value.setContainer(this);
isModified |= value.save();
Expand Down Expand Up @@ -1931,6 +1942,50 @@ private static void saveTemplateTagObject(EditableFactoryTemplateTag tag) throws
}
}

/**
* Perform the validation of a value a tag, where applicable.
*
* @param tag
* @throws NodeException
*/
private static void validateObjectTagObject(EditableFactoryObjectTag tag) throws NodeException {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only for objecttags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place I was able to find and test upon.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about templatetags and contenttags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need an advice on the test possibilities. The functionality fix is trivial.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing is similar to testing with objecttags. For templatetags you change the tag in a template, for contenttags you change the tag in a content (of a page).

for (Part part: tag.getConstruct().getParts()) {
if (part.getPartTypeId() == Part.JSON) {
Object value = tag.get(part.getKeyname());
if (value == null || !(value instanceof Value val)) {
continue;
}
String stringValue = val.getValueText();
if (StringUtils.isEmpty(stringValue)) {
continue;
}
JsonContent jsonContent = JsonContent.fromString(stringValue);
if (jsonContent == null) {
throw new ObjectModificationException(tag.getName(), LOG_JSON_VALIDATION_ERROR + " for "
+ "tag {" + tag.getId() + " " + tag.getName() + "}"
+ ", part {" + part.getKeyname() + "}."
+ " Reason: not a JSON value", "json_validation_failed");
}
if (!StringUtils.isEmpty(part.getInfoText())) {
JsonContent jsonSchemaContent = JsonContent.fromString(part.getInfoText());
JsonSchema[] allowedSchemas = null;
if (jsonSchemaContent.isArray()) {
JsonArray jsonSchemas = jsonSchemaContent.getArray();
allowedSchemas = IntStream.range(0, jsonSchemas.size()).mapToObj(jsonSchemas::getJsonObject).map(JsonSchema::new).toArray(size -> new JsonSchema[size]);
} else {
allowedSchemas = new JsonSchema[] { new JsonSchema(jsonSchemaContent.getObject()) };
}
if (allowedSchemas != null && Arrays.asList(allowedSchemas).stream().noneMatch(schema1 -> JsonUtil.newJsonSchemaValidator(schema1.getVertxSchema()).validate(jsonContent.getContent()).getValid() == Boolean.TRUE)) {
throw new ObjectModificationException(tag.getName(), LOG_JSON_VALIDATION_ERROR + " for {"
+ "tag {" + tag.getId() + " " + tag.getName() + "}"
+ ", part {" + part.getKeyname() + "}}"
+ " Reason: the JSON contents does not match any of allowed schemas", "json_validation_failed");
}
}
}
}
}

/**
* Save the given objecttag object
* @param tag tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static com.gentics.contentnode.object.Part.HTMLCUSTOMFORM;
import static com.gentics.contentnode.object.Part.HTMLLONG;
import static com.gentics.contentnode.object.Part.JAVAEDITOR;
import static com.gentics.contentnode.object.Part.JSON;
import static com.gentics.contentnode.object.Part.LIST;
import static com.gentics.contentnode.object.Part.LISTORDERED;
import static com.gentics.contentnode.object.Part.LISTUNORDERED;
Expand Down Expand Up @@ -194,6 +195,7 @@ private static boolean hasPartMappableValue(Part part) {
case TEXTHTMLLONG:
case TEXTCUSTOMFORM:
case HTML:
case JSON:
case LIST:
case LISTORDERED:
case LISTUNORDERED:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,8 @@ protected String[] getCheckedAttributes(AttributeType attrType) throws NodeExcep
default:
throw new NodeException();
}
case json:
return new String[] { "tagname", "mapname", "object", "attributeType", "multivalue", "segmentfield", "displayfield", "urlfield", "noIndex", "elasticsearch", "jsonSchemaFilter" };
case micronode:
return new String[] { "tagname", "mapname", "object", "attributeType", "multivalue", "segmentfield", "displayfield", "urlfield", "micronodeFilter", "elasticsearch" };
case link:
Expand Down
25 changes: 15 additions & 10 deletions cms-core/src/main/java/com/gentics/contentnode/object/Part.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import java.net.URI;

import org.apache.commons.lang3.StringUtils;

import com.gentics.api.lib.etc.ObjectTransformer;
import com.gentics.api.lib.exception.NodeException;
import com.gentics.api.lib.exception.ReadOnlyException;
Expand Down Expand Up @@ -70,6 +72,10 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
public static final int FILEUPLOAD = 38;
public static final int FOLDERUPLOAD = 39;
public static final int NODE = 40;
public static final int FORM = 41;
public static final int CMSFORM = 42;
public static final int HANDLEBARS = 43;
public static final int JSON = 44;

/**
* The ttype of the part object.
Expand Down Expand Up @@ -106,6 +112,9 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
to.setExternalEditorUrl(from.getExternalEditorUrl());

switch (to.getPartTypeId()) {
case Part.JSON:
to.setInfoText(from.getJSONSchema());
// fallthrough
case Part.TEXT:
case Part.TEXTHMTL:
case Part.HTML:
Expand All @@ -116,11 +125,6 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
to.setInfoInt(from.getRegex().getId());
}
break;
default:
break;
}

switch (to.getPartTypeId()) {
case Part.LIST:
case Part.LISTORDERED:
case Part.LISTUNORDERED:
Expand Down Expand Up @@ -185,6 +189,9 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
to.setTypeId(from.getPartTypeId());

switch (from.getPartTypeId()) {
case Part.JSON:
to.setJSONSchema(from.getInfoText());
// fallthrough
case Part.TEXT:
case Part.TEXTHMTL:
case Part.HTML:
Expand Down Expand Up @@ -243,6 +250,9 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
to.setExternalEditorUrl(from.getExternalEditorUrl());

switch (from.getPartTypeId()) {
case Part.JSON:
to.setHtmlClass(from.getInfoText());
// fallthrough
case Part.TEXT:
case Part.TEXTHMTL:
case Part.HTML:
Expand All @@ -251,11 +261,6 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
case Part.HTMLLONG:
to.setRegexId(from.getInfoInt());
break;
default:
break;
}

switch (from.getPartTypeId()) {
case Part.LIST:
case Part.LISTORDERED:
case Part.LISTUNORDERED:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public abstract class TagmapEntry extends AbstractContentObject implements Named
}
}
entry.setMicronodeFilter(nodeEntry.getMicronodeFilter());
entry.setJSONSchemaFilter(nodeEntry.getJSONSchemaFilter());
}
CrFragment fragment = nodeEntry.getContentRepositoryFragment();
if (fragment != null) {
Expand Down Expand Up @@ -128,6 +129,9 @@ public abstract class TagmapEntry extends AbstractContentObject implements Named
if (entry.getMicronodeFilter() != null) {
nodeEntry.setMicronodeFilter(entry.getMicronodeFilter());
}
if (entry.getJSONSchemaFilter() != null) {
nodeEntry.setJSONSchemaFilter(entry.getJSONSchemaFilter());
}
if (entry.getNoIndex() != null) {
nodeEntry.setNoIndex(entry.getNoIndex());
}
Expand Down Expand Up @@ -166,6 +170,7 @@ public abstract class TagmapEntry extends AbstractContentObject implements Named
resolvableProperties.put("urlfield", new NodeObjectProperty<>((o, key) -> o.isUrlfield()));
resolvableProperties.put("fragmentName", new NodeObjectProperty<>((o, key) -> o.getContentRepositoryFragmentName()));
resolvableProperties.put("micronodeFilter", new NodeObjectProperty<>((o, key) -> o.getMicronodeFilter()));
resolvableProperties.put("jsonSchemaFilter", new NodeObjectProperty<>((o, key) -> o.getJSONSchemaFilter()));
resolvableProperties.put("elasticsearch", new NodeObjectProperty<>((o, key) -> o.getElasticsearch()));
resolvableProperties.put("noIndex", new NodeObjectProperty<>((o, key) -> o.isNoIndex()));
}
Expand Down Expand Up @@ -549,6 +554,23 @@ public void setMicronodeFilter(String micronodeFilter) throws ReadOnlyException
failReadOnly();
}

/**
* Get the JSON schema filter for entries of type {@link AttributeType#json}
* @return filter
*/
@FieldGetter("json_schema_filter")
public abstract String getJSONSchemaFilter();

/**
* Set the JSON schema filter for entries of type {@link AttributeType#json}
* @param jsonSchemaFilter filter
* @throws ReadOnlyException
*/
@FieldSetter("json_schema_filter")
public void setJSONSchemaFilter(String jsonSchemaFilter) throws ReadOnlyException {
failReadOnly();
}

@Override
public String getName() {
return getMapname();
Expand All @@ -563,6 +585,9 @@ public String toString() {
case foreignlink:
return String.format("%d: %s -> %s (%s from %d.%s, mul: %b, stat: %b, opt: %b, fs: %b, cat: %s)", getObject(), getTagname(), getMapname(),
getAttributetype(), getTargetType(), getForeignlinkAttribute(), isMultivalue(), isStatic(), isOptimized(), isFilesystem(), getCategory());
case json:
return String.format("%d: %s -> %s (mul: %b, stat: %b, opt: %b, fs: %b, cat: %s, schema: %s)", getObject(), getTagname(), getMapname(),
isMultivalue(), isStatic(), isOptimized(), isFilesystem(), getCategory(), getJSONSchemaFilter());
default:
return String.format("%d: %s -> %s (%s, mul: %b, stat: %b, opt: %b, fs: %b, cat: %s)", getObject(), getTagname(), getMapname(), getAttributetype(),
isMultivalue(), isStatic(), isOptimized(), isFilesystem(), getCategory());
Expand Down Expand Up @@ -621,7 +646,12 @@ public static enum AttributeType {
/**
* Micronode
*/
micronode(12, ContentRepositoryModel.Type.mesh);
micronode(12, ContentRepositoryModel.Type.mesh),

/**
* JSON
*/
json(13, ContentRepositoryModel.Type.mesh);

/**
* numerical type
Expand Down
Loading