You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2023/05/26 13:19:09 UTC
[brooklyn-server] 04/08: permit jackson to be used to coerce strings and primitives
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 4660a30ce04a3d168098cee68a5ecef286337938
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri May 26 10:53:28 2023 +0100
permit jackson to be used to coerce strings and primitives
(previously only partially permitted)
---
.../core/resolve/jackson/BeanWithTypeUtils.java | 26 ++++--
.../brooklyn/util/core/flags/TypeCoercions.java | 2 +-
.../BrooklynMiscJacksonSerializationTest.java | 96 +++++++++++++++++++---
.../util/core/internal/TypeCoercionsTest.java | 2 +-
.../util/core/predicates/DslPredicateTest.java | 12 ++-
5 files changed, 111 insertions(+), 27 deletions(-)
diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java
index f04e7fc6c9..c829dfed68 100644
--- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java
@@ -29,10 +29,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.google.common.annotations.Beta;
import com.google.common.reflect.TypeToken;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import org.apache.brooklyn.api.entity.Entity;
@@ -157,9 +154,21 @@ public class BeanWithTypeUtils {
* see in JsonDeserializerForCommonBrooklynThings. See DslSerializationTest.
*/
+ static ThreadLocal<Stack<Object>> activeConversions = new ThreadLocal<>();
public static <T> T convert(ManagementContext mgmt, Object mapOrListToSerializeThenDeserialize, TypeToken<T> type, boolean allowRegisteredTypes, BrooklynClassLoadingContext loader, boolean allowJavaTypes) throws JsonProcessingException {
+ Stack<Object> stack = activeConversions.get();
+ if (stack==null) {
+ stack = new Stack<>();
+ activeConversions.set(stack);
+ }
+ // simple things, like string, might be converted by a JsonDeserializer or by other TypeCoercions;
+ // so the former calls the latter, and the latter calls the former. but stop at some point!
+ if (stack.contains(mapOrListToSerializeThenDeserialize)) throw new IllegalStateException("Aborting recursive attempt to convert '"+mapOrListToSerializeThenDeserialize+"'");
+
try {
+ stack.push(mapOrListToSerializeThenDeserialize);
+
return convertDeeply(mgmt, mapOrListToSerializeThenDeserialize, type, allowRegisteredTypes, loader, allowJavaTypes);
} catch (Exception e) {
@@ -168,6 +177,9 @@ public class BeanWithTypeUtils {
} catch (Exception e2) {
throw Exceptions.propagate(Arrays.asList(e, e2));
}
+ } finally {
+ stack.pop();
+ if (stack.isEmpty()) activeConversions.remove();
}
}
@@ -208,7 +220,7 @@ public class BeanWithTypeUtils {
if (inputMap.isAbsent()) return (Maybe<T>)inputMap;
Object o = inputMap.get();
- if (!(o instanceof Map) && !(o instanceof List) && !Boxing.isPrimitiveOrBoxedObject(o)) {
+ if (!(o instanceof Map) && !(o instanceof List) && !Boxing.isPrimitiveOrBoxedObject(o) && !(o instanceof String)) {
if (type.isSupertypeOf(o.getClass())) {
return (Maybe<T>)inputMap;
} else {
@@ -299,7 +311,7 @@ public class BeanWithTypeUtils {
}
}
- // we want some special object. if we have a map or a string then conversion might sort us out.
- return (t instanceof Map || t instanceof String) ? 1 : -1;
+ // we want some special object. if we have a map or a string or possibly a primitive then conversion might sort us out.
+ return (t instanceof Map || t instanceof String || Boxing.isPrimitiveOrBoxedObject(t)) ? 1 : -1;
}
}
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
index 0e3fa09bd3..d630819a4a 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
@@ -361,7 +361,7 @@ public class TypeCoercions {
@Override
public <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) {
- if (!(input instanceof Map || input instanceof Collection || Boxing.isPrimitiveOrBoxedObject(input))) {
+ if (!(input instanceof Map || input instanceof Collection || Boxing.isPrimitiveOrBoxedObject(input) || input instanceof String)) {
return null;
}
if (BeanWithTypeUtils.isConversionRecommended(Maybe.of(input), type)) {
diff --git a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java
index deee812b4c..5caa8ef19f 100644
--- a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java
@@ -18,36 +18,33 @@
*/
package org.apache.brooklyn.core.resolve.jackson;
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.util.TokenBuffer;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
-import com.google.common.reflect.TypeToken;
+
import java.io.IOException;
import java.time.Instant;
-import java.time.LocalDateTime;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
-import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan;
-import org.apache.brooklyn.core.typereg.RegisteredTypes;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.units.ByteSize;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.text.Secret;
-import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
@@ -253,4 +250,81 @@ public class BrooklynMiscJacksonSerializationTest implements MapperTestFixture {
WrappedValue<Secret<String>> s1;
}
+
+ @JsonDeserialize(using = SampleFromStringDeserializer.class)
+ static class SampleFromStringDeserialized {
+ public Object subtypeWanted;
+ public String x;
+ }
+ @JsonDeserialize(using = JsonDeserializer.None.class)
+ static class SampleFromStringSubtype extends SampleFromStringDeserialized {}
+ @JsonDeserialize(using = JsonDeserializer.None.class)
+ static class SampleFromStringSubtype1 extends SampleFromStringSubtype {}
+ static class SampleFromStringSubtype2 extends SampleFromStringSubtype {}
+
+ static class SampleFromStringDeserializer extends JsonDeserializer {
+ @Override
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
+ TokenBuffer buffer = BrooklynJacksonSerializationUtils.createBufferForParserCurrentObject(p, ctxt);
+ Object raw = new JsonPassThroughDeserializer().deserialize(
+ BrooklynJacksonSerializationUtils.createParserFromTokenBufferAndParser(buffer, p),
+ ctxt);
+
+ Integer rawi = null;
+ if (raw instanceof String) rawi = Integer.parseInt((String)raw);
+ if (raw instanceof Integer) rawi = (Integer)raw;
+ if (rawi!=null) {
+ SampleFromStringSubtype result = null;
+ if (rawi==1) result = new SampleFromStringSubtype1();
+ if (rawi==2) result = new SampleFromStringSubtype2();
+ if (result!=null) {
+ result.subtypeWanted = raw;
+ }
+ return result;
+ }
+
+ if (raw instanceof Map) {
+ Integer stw = TypeCoercions.tryCoerce(((Map) raw).get("subtypeWanted"), Integer.class).orNull();
+ if (stw!=null && stw>=0) {
+ try {
+ return ctxt.findNonContextualValueDeserializer(ctxt.constructType(Class.forName(SampleFromStringSubtype.class.getName()+stw))).deserialize(
+ BrooklynJacksonSerializationUtils.createParserFromTokenBufferAndParser(buffer, p), ctxt);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ return ctxt.findNonContextualValueDeserializer(ctxt.constructType(SampleFromStringSubtype.class)).deserialize(
+ BrooklynJacksonSerializationUtils.createParserFromTokenBufferAndParser(buffer, p), ctxt);
+ }
+ }
+
+ @Test
+ public void testDeserializeFromStringOrMapOrEvenWithMapKey() throws JsonProcessingException {
+ Object s;
+ mapper = BeanWithTypeUtils.newSimpleYamlMapper(); //YAMLMapper.builder().build();
+ s = mapper.readerFor(SampleFromStringDeserialized.class).readValue("1");
+ Asserts.assertInstanceOf(s, SampleFromStringSubtype1.class);
+ Asserts.assertEquals( ((SampleFromStringSubtype)s).subtypeWanted, 1 );
+
+ s = mapper.readerFor(SampleFromStringDeserialized.class).readValue("\"2\"");
+ Asserts.assertInstanceOf(s, SampleFromStringSubtype2.class);
+ Asserts.assertEquals( ((SampleFromStringSubtype)s).subtypeWanted, "2" );
+
+ s = mapper.readerFor(SampleFromStringDeserialized.class).readValue("subtypeWanted: 1");
+ Asserts.assertInstanceOf(s, SampleFromStringSubtype1.class);
+ Asserts.assertEquals( ((SampleFromStringSubtype)s).subtypeWanted, 1 );
+
+ s = mapper.readerFor(SampleFromStringDeserialized.class).readValue("subtypeWanted: \"-1\"");
+ Asserts.assertEquals(s.getClass(), SampleFromStringSubtype.class);
+ Asserts.assertEquals( ((SampleFromStringSubtype)s).subtypeWanted, "-1" );
+
+ s = TypeCoercions.coerce("1", SampleFromStringDeserialized.class);
+ Asserts.assertInstanceOf(s, SampleFromStringSubtype1.class);
+ Asserts.assertEquals( ((SampleFromStringSubtype)s).subtypeWanted, "1" );
+ s = TypeCoercions.coerce(1, SampleFromStringDeserialized.class);
+ Asserts.assertEquals( ((SampleFromStringSubtype)s).subtypeWanted, 1 );
+ }
+
}
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
index 38e40ae234..c9468f1431 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
@@ -386,7 +386,7 @@ public class TypeCoercionsTest {
@Test
public void testCoerceStringToNumber() {
- assertEquals(TypeCoercions.coerce("1", Number.class), Double.valueOf(1));
+ assertEquals(TypeCoercions.coerce("1", Number.class), 1); // since Jackson is permtitted to coerce, this prefers integers over doubles, 2023-05
assertEquals(TypeCoercions.coerce("1.0", Number.class), Double.valueOf(1.0));
}
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java b/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
index 74bc686501..5378589e08 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
@@ -65,13 +65,11 @@ public class DslPredicateTest extends BrooklynMgmtUnitTestSupport {
}
@Test
- public void testImplicitEqualsAsRawPredicateNotSupported() {
- Asserts.assertFailsWith(() -> {
- Predicate p = TypeCoercions.coerce("x", Predicate.class);
- }, e -> {
- Asserts.assertStringContainsIgnoreCase(e.toString(), "cannot coerce", "string", "predicate");
- return true;
- });
+ public void testImplicitEqualsAsRawPredicate() {
+ // now this is supported, 2023-05, since we can coerce strings to DslPredicates
+ Predicate p = TypeCoercions.coerce("x", Predicate.class);
+ Asserts.assertTrue(p.test("x"));
+ Asserts.assertFalse(p.test("y"));
}
DslPredicates.DslPredicate predicate(String key, Object value) {