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

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit eab2eeb09f2af72e3020b0299aac9fc88199778b
Author: Alex Heneveld <a...@cloudsoft.io>
AuthorDate: Fri Feb 23 10:49:35 2024 +0000

    improve rest output with errors, and reduce max depth
    
    catches issues sooner if there is an object that refers to itself
---
 .../util/core/json/BrooklynObjectsJsonMapper.java  | 18 ++++---
 .../ErrorAndToStringUnknownTypeSerializer.java     | 61 +++++++++++++++++-----
 2 files changed, 58 insertions(+), 21 deletions(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
 
b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
index 5b2837c8b7..4d036ba1c5 100644
--- 
a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
+++ 
b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
@@ -15,26 +15,24 @@
  */
 package org.apache.brooklyn.util.core.json;
 
+import java.io.IOException;
+
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.StreamWriteConstraints;
+import com.fasterxml.jackson.core.Version;
 import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
+import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
 import org.apache.brooklyn.core.resolve.jackson.CommonTypesSerialization;
 import org.apache.brooklyn.util.time.Duration;
-
-import com.fasterxml.jackson.core.Version;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.module.SimpleModule;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-import java.util.Date;
-
 public class BrooklynObjectsJsonMapper {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(BrooklynObjectsJsonMapper.class);
@@ -46,6 +44,10 @@ public class BrooklynObjectsJsonMapper {
         sp.setUnknownTypeSerializer(new 
ErrorAndToStringUnknownTypeSerializer());
 
         ObjectMapper mapper = new ObjectMapper();
+
+        // default of 1000 is much more than we need or want
+        
mapper.getFactory().setStreamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(100).build());
+
         mapper.setSerializerProvider(sp);
         mapper.setVisibility(new 
PossiblyStrictPreferringFieldsVisibilityChecker());
 
diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java
 
b/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java
index 3519882766..5a540cf11f 100644
--- 
a/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java
+++ 
b/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java
@@ -22,20 +22,20 @@ import java.io.IOException;
 import java.io.NotSerializableException;
 import java.util.Collections;
 import java.util.Set;
-
 import javax.annotation.Nullable;
 
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.javalang.Reflections;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonStreamContext;
+import com.fasterxml.jackson.core.json.JsonWriteContext;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * for non-json-serializable classes (quite a lot of them!) simply provide a 
sensible error message and a toString.
@@ -66,16 +66,27 @@ public class ErrorAndToStringUnknownTypeSerializer extends 
UnknownSerializer {
         if (WARNED_CLASSES.add(value.getClass().getCanonicalName())) {
             log.warn("Standard serialization not possible for 
"+value.getClass()+" ("+value+")", error);
         }
-        JsonStreamContext newCtxt = jgen.getOutputContext();
 
-        // very odd, but flush seems necessary when working with large 
objects; presumably a buffer which is allowed to clear itself?
+        // flush seems necessary when working with large objects; presumably a 
buffer which is allowed to clear itself?
         // without this, when serializing the large (1.5M) Server json object 
from BrooklynJacksonSerializerTest creates invalid json,
         // containing:  "foo":false,"{"error":true,...
         jgen.flush();
 
-        boolean createObject = !newCtxt.inObject() || 
newCtxt.getCurrentName()!=null;
+        // if nested very deeply, come out, because there will be errors 
writing deeper things
+        int closed = 0;
+        while (jgen.getOutputContext().getNestingDepth() > 30 && 
writeEndCurrentThing(jgen, closed++)) {}
+        if (jgen.getOutputContext().getNestingDepth() > 30) {
+            throw new IllegalStateException("Cannot recover from serialization 
object; nesting is too deep");
+        }
+
+        boolean createObject = !jgen.getOutputContext().inObject();
         if (createObject) {
+            // create if we're not in an object (ie in an array)
+            // or if we're in an object, but we've just written the field name
             jgen.writeStartObject();
+        } else {
+            // we might need to write a value, and then write the error fields 
next
+            writeErrorValueIfNeeded(jgen);
         }
 
         if (allowEmpty(value.getClass())) {
@@ -108,12 +119,36 @@ public class ErrorAndToStringUnknownTypeSerializer 
extends UnknownSerializer {
             jgen.writeEndObject();
         }
 
-        while (newCtxt!=null && !newCtxt.equals(ctxt)) {
-            if (jgen.getOutputContext().inArray()) { jgen.writeEndArray(); 
continue; }
-            if (jgen.getOutputContext().inObject()) { jgen.writeEndObject(); 
continue; }
-            break;
+        while (jgen.getOutputContext()!=null && 
!jgen.getOutputContext().equals(ctxt) && writeEndCurrentThing(jgen, closed+1)) 
{}
+    }
+
+    private static boolean writeEndCurrentThing(JsonGenerator jgen, int count) 
throws IOException {
+        if (jgen.getOutputContext().inArray()) {
+            jgen.writeEndArray();
+            return true;
         }
+        if (jgen.getOutputContext().inObject()) {
+            if (count==0) writeErrorValueIfNeeded(jgen);
+            jgen.writeEndObject();
+            return true;
+        }
+        return false;
+    }
 
+    private static void writeErrorValueIfNeeded(JsonGenerator jgen) {
+        try {
+            // at count 0, we usually need to write a value
+            // (but there is no way to tell for sure; the internal status on 
JsonWriteContext is protected,
+            // and the jgen methods are closely coupled to their state; and 
any attempt to mutate will insert an extra colon etc)
+            if (jgen.getOutputContext().hasCurrentName()) {
+                // assume it wrote the name, but the output context might not 
have the correct state;
+                // have tried updated output context but it is mostly 
protected; instead just write this, usually good enough.
+                jgen.writeRaw("\"ERROR\"");
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            // if we couldn't write, we're probably at a place where a field 
would be written, so just end
+        }
     }
 
     protected boolean allowEmpty(Class<? extends Object> clazz) {

Reply via email to