| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.brooklyn.core.resolve.jackson; |
| |
| import com.fasterxml.jackson.annotation.JsonAutoDetect; |
| import com.fasterxml.jackson.core.JsonGenerator; |
| import com.fasterxml.jackson.core.JsonParser; |
| import com.fasterxml.jackson.core.JsonProcessingException; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.deser.ContextualDeserializer; |
| import com.fasterxml.jackson.databind.introspect.VisibilityChecker; |
| import com.fasterxml.jackson.databind.jsontype.TypeSerializer; |
| import com.fasterxml.jackson.databind.module.SimpleDeserializers; |
| import com.fasterxml.jackson.databind.module.SimpleModule; |
| import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; |
| import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; |
| import com.google.common.reflect.TypeToken; |
| import org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.mgmt.EntityManager; |
| import org.apache.brooklyn.api.mgmt.ManagementContext; |
| import org.apache.brooklyn.api.objs.BrooklynObject; |
| import org.apache.brooklyn.api.objs.BrooklynObjectType; |
| import org.apache.brooklyn.config.ConfigKey; |
| import org.apache.brooklyn.core.config.ConfigKeys; |
| import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; |
| import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal; |
| import org.apache.brooklyn.core.mgmt.internal.NonDeploymentManagementContext; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.core.flags.BrooklynTypeNameResolution; |
| import org.apache.brooklyn.util.core.flags.FlagUtils; |
| import org.apache.brooklyn.util.core.predicates.DslPredicates; |
| import org.apache.brooklyn.util.core.task.Tasks; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.javalang.Boxing; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.apache.brooklyn.util.time.Time; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.time.Instant; |
| import java.util.*; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| import static org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils.createBeanDeserializer; |
| |
| public class CommonTypesSerialization { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(CommonTypesSerialization.class); |
| |
| public static void apply(ObjectMapper mapper) { |
| apply(mapper, null); |
| } |
| |
| public static void apply(ObjectMapper mapper, ManagementContext mgmt) { |
| |
| SimpleModule m = new SimpleModule(); |
| InterceptibleDeserializers interceptible = new InterceptibleDeserializers(); |
| m.setDeserializers(interceptible); |
| new DurationSerialization().apply(m); |
| new DateSerialization().apply(m); |
| new ByteArrayObjectStreamSerialization().apply(m); |
| new InstantSerialization().apply(m); |
| new ManagementContextSerialization(mgmt).apply(m); |
| new BrooklynObjectSerialization(mgmt).apply(m, interceptible); |
| new ConfigKeySerialization(mgmt).apply(m); |
| new PredicateSerialization(mgmt).apply(m); |
| new GuavaTypeTokenSerialization().apply(mapper, m, interceptible); |
| //mapper.setAnnotationIntrospector(new CustomAnnotationInspector()); |
| |
| // see also JsonDeserializerForCommonBrooklynThings, and BrooklynDslCommon coercion of Spec |
| |
| mapper.registerModule(m); |
| } |
| |
| public static class InterceptibleDeserializers extends SimpleDeserializers { |
| final List<Function<JavaType,JsonDeserializer<?>>> interceptors = MutableList.of(); |
| @Override |
| public JsonDeserializer<?> findBeanDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { |
| if (type!=null) { |
| for (Function<JavaType,JsonDeserializer<?>> ic: interceptors) { |
| JsonDeserializer<?> interception = ic.apply(type); |
| if (interception != null) return interception; |
| } |
| } |
| return super.findBeanDeserializer(type, config, beanDesc); |
| } |
| |
| public void addInterceptor(Function<JavaType,JsonDeserializer<?>> typeRewriter) { |
| interceptors.add(typeRewriter); |
| } |
| public void addSubtypeInterceptor(Class<?> type, JsonDeserializer<?> deserializer) { |
| interceptors.add(jt -> jt.findSuperType(type)!=null ? deserializer : null); |
| } |
| } |
| |
| public static abstract class ObjectAsStringSerializerAndDeserializer<T> { |
| |
| public abstract Class<T> getType(); |
| public Class<? extends T> getType(Object instance) { |
| return getType(); |
| } |
| public abstract String convertObjectToString(T value, JsonGenerator gen, SerializerProvider provider) throws IOException; |
| public abstract T convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException; |
| |
| public T newEmptyInstance() { |
| return newEmptyInstance(getType()); |
| } |
| |
| public T newEmptyInstance(Class<T> t) { |
| try { |
| Constructor<T> tc = t.getDeclaredConstructor(); |
| tc.setAccessible(true); |
| return tc.newInstance(); |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| throw new IllegalArgumentException("Empty instances of " + getType() + " are not supported; provide a 'value:' indicating the value", e); |
| } |
| } |
| |
| public T convertSpecialMapToObject(Map value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| throw new IllegalStateException(getType()+" should be supplied as map with 'value'; instead had " + value); |
| } |
| |
| protected T doConvertSpecialMapViaNewSimpleMapper(Map value) throws IOException { |
| // a hack to support default bean deserialization as a fallback; we could deprecate, but some tests support eg nanos: xx for duration |
| // TODO - would be better to use createBeanDeserializer ? |
| ObjectMapper m = BeanWithTypeUtils.newSimpleYamlMapper(); |
| m.setVisibility(new VisibilityChecker.Std(JsonAutoDetect.Visibility.ANY, JsonAutoDetect.Visibility.ANY, JsonAutoDetect.Visibility.ANY, JsonAutoDetect.Visibility.ANY, JsonAutoDetect.Visibility.ANY)); |
| return m.readerFor(getType()).readValue(m.writeValueAsString(value)); |
| } |
| |
| protected T copyInto(T src, T target) { |
| throw new IllegalStateException("Not supported to read into "+getType()+", from "+src+" into "+target); |
| } |
| |
| public <T extends SimpleModule> T apply(T module) { |
| module.addSerializer(getType(), new Serializer()); |
| module.addDeserializer(getType(), newDeserializer()); |
| return module; |
| } |
| |
| protected class Serializer extends JsonSerializer<T> { |
| @Override |
| public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| gen.writeString( convertObjectToString(value, gen, provider) ); |
| } |
| @Override |
| public void serializeWithType(T value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { |
| if (value==null) { |
| gen.writeNull(); |
| return; |
| } |
| |
| if (typeSer.getTypeIdResolver() instanceof AsPropertyIfAmbiguous.HasBaseType) { |
| if (((AsPropertyIfAmbiguous.HasBaseType)typeSer.getTypeIdResolver()).getBaseType().findSuperType(getType())!=null) { |
| gen.writeString(convertObjectToString(value, gen, serializers)); |
| return; |
| } |
| } |
| |
| // write as object with type and value if type is ambiguous |
| gen.writeStartObject(); |
| gen.writeStringField(BrooklynJacksonSerializationUtils.TYPE, getType(value).getName()); |
| gen.writeStringField(BrooklynJacksonSerializationUtils.VALUE, convertObjectToString(value, gen, serializers)); |
| gen.writeEndObject(); |
| } |
| } |
| |
| protected JsonDeserializer<T> newDeserializer() { |
| return new Deserializer(); |
| } |
| |
| protected class Deserializer extends JsonDeserializer<T> { |
| |
| @Override |
| public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { |
| try { |
| Object valueO = BrooklynJacksonSerializationUtils.readObject(ctxt, p); |
| Object value = valueO; |
| if (value instanceof Map) { |
| if (((Map)value).size()==1 && ((Map)value).containsKey(BrooklynJacksonSerializationUtils.VALUE)) { |
| value = ((Map) value).get(BrooklynJacksonSerializationUtils.VALUE); |
| } else { |
| return convertSpecialMapToObject((Map)value, p, ctxt); |
| } |
| } |
| |
| if (value==null) { |
| if (valueO==null) return newEmptyInstance(); |
| return null; |
| } else if (value instanceof String || Boxing.isPrimitiveOrBoxedClass(value.getClass())) { |
| return convertStringToObject(value.toString(), p, ctxt); |
| } else if (value instanceof Map) { |
| return convertSpecialMapToObject((Map)value, p, ctxt); |
| } else if (value.getClass().equals(Object.class)) { |
| return newEmptyInstance(); |
| } else { |
| return deserializeOther(value); |
| } |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| public T deserializeOther(Object value) { |
| if (getType()!=null && getType().isInstance(value)) |
| return (T) value; //if using complex types (convertShallow) allow if castable |
| // we could attempt coercion, but no need to go that far, and readObject in deserialize above would probably fail also; |
| // we assume if you have a complex object it is already the right type, or you use coerce |
| throw new IllegalStateException(getType()+" should be supplied as string or map with 'type' and 'value'; instead had " + value); |
| } |
| |
| @Override |
| public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException, JsonProcessingException { |
| return copyInto(deserialize(p, ctxt), intoValue); |
| } |
| |
| protected T newEmptyInstance() { |
| return ObjectAsStringSerializerAndDeserializer.this.newEmptyInstance(); |
| } |
| |
| public T convertSpecialMapToObject(Map value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return ObjectAsStringSerializerAndDeserializer.this.convertSpecialMapToObject(value, p, ctxt); |
| } |
| |
| public T convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return ObjectAsStringSerializerAndDeserializer.this.convertStringToObject(value, p, ctxt); |
| } |
| } |
| } |
| |
| public static class DurationSerialization extends ObjectAsStringSerializerAndDeserializer<Duration> { |
| @Override public Class<Duration> getType() { return Duration.class; } |
| @Override public String convertObjectToString(Duration value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| return value.toString(); |
| } |
| @Override public Duration convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return Time.parseDuration(value); |
| } |
| @Override public Duration convertSpecialMapToObject(Map value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return doConvertSpecialMapViaNewSimpleMapper(value); |
| } |
| } |
| |
| public static class DateSerialization extends ObjectAsStringSerializerAndDeserializer<Date> { |
| @Override public Class<Date> getType() { return Date.class; } |
| @Override public String convertObjectToString(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| return Time.makeIso8601DateString(value); |
| } |
| @Override public Date convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return Time.parseDate(value); |
| } |
| @Override public Date convertSpecialMapToObject(Map value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return doConvertSpecialMapViaNewSimpleMapper(value); |
| } |
| } |
| |
| public static class ByteArrayObjectStreamSerialization extends ObjectAsStringSerializerAndDeserializer<ByteArrayOutputStream> { |
| @Override public Class<ByteArrayOutputStream> getType() { return ByteArrayOutputStream.class; } |
| @Override public String convertObjectToString(ByteArrayOutputStream value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| return provider.getConfig().getBase64Variant().encode(value.toByteArray()); |
| // byte[] array = value.toByteArray(); |
| // gen.writeBinary(provider.getConfig().getBase64Variant(), array, 0, array.length); |
| } |
| @Override public ByteArrayOutputStream convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| out.write(ctxt.getConfig().getBase64Variant().decode(value)); |
| // out.write(p.getBinaryValue()); |
| return out; |
| } |
| |
| @Override |
| protected ByteArrayOutputStream copyInto(ByteArrayOutputStream src, ByteArrayOutputStream target) { |
| try { |
| target.write(src.toByteArray()); |
| } catch (IOException e) { |
| throw Exceptions.propagate(e); |
| } |
| return target; |
| } |
| } |
| |
| public static class InstantSerialization extends ObjectAsStringSerializerAndDeserializer<Instant> { |
| @Override public Class<Instant> getType() { return Instant.class; } |
| @Override public String convertObjectToString(Instant value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| return Time.makeIso8601DateStringZ(value); |
| } |
| @Override public Instant convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return Time.parseInstant(value); |
| } |
| @Override public Instant convertSpecialMapToObject(Map value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| return doConvertSpecialMapViaNewSimpleMapper(value); |
| } |
| } |
| |
| public static class ManagementContextSerialization extends ObjectAsStringSerializerAndDeserializer<ManagementContext> { |
| private final ManagementContext mgmt; |
| |
| public ManagementContextSerialization(ManagementContext mgmt) { this.mgmt = mgmt; } |
| @Override public Class<ManagementContext> getType() { return ManagementContext.class; } |
| |
| @Override public String convertObjectToString(ManagementContext value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| return BrooklynJacksonSerializationUtils.DEFAULT; |
| } |
| @Override public ManagementContext convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| if (BrooklynJacksonSerializationUtils.DEFAULT.equals(value)) { |
| if (mgmt!=null) return mgmt; |
| throw new IllegalArgumentException("ManagementContext cannot be deserialized here"); |
| } |
| throw new IllegalStateException("ManagementContext should be recorded as 'default' to be deserialized correctly"); |
| } |
| } |
| |
| public static class BrooklynObjectSerialization extends ObjectAsStringSerializerAndDeserializer<BrooklynObject> { |
| private final ManagementContext mgmt; |
| |
| public BrooklynObjectSerialization(ManagementContext mgmt) { this.mgmt = mgmt; } |
| |
| public <T extends SimpleModule> T apply(T module, InterceptibleDeserializers interceptable) { |
| // apply to all subtypes of BO |
| interceptable.addSubtypeInterceptor(getType(), newDeserializer()); |
| |
| return apply(module); |
| } |
| |
| @Override public Class<BrooklynObject> getType() { |
| return BrooklynObject.class; |
| } |
| @Override public Class<? extends BrooklynObject> getType(Object instance) { |
| return BrooklynObjectType.of((BrooklynObject) instance).getInterfaceType(); |
| } |
| |
| @Override public String convertObjectToString(BrooklynObject value, JsonGenerator gen, SerializerProvider provider) throws IOException { |
| return value.getId(); |
| } |
| @Override public BrooklynObject convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| if (mgmt==null) { |
| throw new IllegalArgumentException("BrooklynObject cannot be deserialized here"); |
| } |
| // we could support 'current' to use tasks to resolve, which might be handy |
| BrooklynObject result = mgmt.lookup(value); |
| if (result!=null) return result; |
| |
| Entity currentEntity = BrooklynTaskTags.getContextEntity(Tasks.current()); |
| if (currentEntity!=null) { |
| // during initialization, we can look relative to ourselves, since entities aren't available in mgmt.lookup |
| Iterable<Entity> ents = ((EntityManagerInternal) mgmt.getEntityManager()).getAllEntitiesInApplication(currentEntity.getApplication()); |
| for (Entity e : ents) { |
| if (Objects.equals(value, e.getId())) { |
| return e; |
| } |
| } |
| } |
| throw new IllegalStateException("Entity or other BrooklynObject '"+value+"' is not known here"); |
| } |
| |
| @Override |
| protected JsonDeserializer<BrooklynObject> newDeserializer() { |
| return new BODeserializer(); |
| } |
| |
| class BODeserializer extends Deserializer implements ContextualDeserializer, BrooklynJacksonSerializationUtils.RecontextualDeserializer { |
| private DeserializationContext context; |
| private JavaType knownConcreteType; |
| |
| BODeserializer() {} |
| BODeserializer(DeserializationContext context, JavaType knownConcreteType) { |
| this.context = context; |
| this.knownConcreteType = knownConcreteType; |
| } |
| @Override |
| public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { |
| return new BODeserializer(ctxt, null); |
| } |
| @Override |
| public JsonDeserializer<?> recreateContextual() { |
| if (context!=null && context.getContextualType()!=null && context.getContextualType().isConcrete()) { |
| return new BODeserializer(context, context.getContextualType()); |
| } |
| return this; |
| } |
| |
| @Override |
| protected BrooklynObject newEmptyInstance() { |
| /* context buries the contextual types when called via createContextual, because we are a delegate; and has cleared it by the time it gets here */ |
| if (knownConcreteType != null) { |
| return BrooklynObjectSerialization.this.newEmptyInstance((Class<BrooklynObject>) knownConcreteType.getRawClass()); |
| } |
| return super.newEmptyInstance(); |
| } |
| |
| @Override public BrooklynObject convertSpecialMapToObject(Map value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| BrooklynObject result = null; |
| if (value.size()<=2) { |
| Object id = value.get("id"); |
| Object type = value.get("type"); |
| boolean isExistingInstance = false; |
| if (id instanceof String) { |
| // looks like a type+id map, so we should be able to look it up |
| if (type instanceof String) { |
| Optional<BrooklynObjectType> typeO = Arrays.stream(BrooklynObjectType.values()).filter(t -> type.equals(t.getInterfaceType().getName())).findAny(); |
| if (typeO.isPresent()) { |
| isExistingInstance = true; |
| result = mgmt.lookup((String) id, typeO.get().getInterfaceType()); |
| } else { |
| // fall through to below |
| } |
| } else { |
| isExistingInstance = true; |
| result = mgmt.lookup((String) id, null); |
| } |
| } |
| |
| if (result==null) { |
| if (isExistingInstance) { |
| throw new IllegalStateException("Cannot find serialized shorthand reference to entity " + value); |
| } |
| // attempt to instantiate it - to support add-policy workflow step |
| // TODO we should prefer a spec, and coerce to a spec (separate coercer), |
| // moving some of the code from BrooklynYamlTypeInstantiator into here / near. |
| // if using this, we should take care to keep it JsonPassThroughDeserializer until we want it |
| |
| if (type==null && knownConcreteType!=null) { |
| // type should have come from outer deserialier |
| result = newEmptyInstance(); |
| |
| FlagUtils.setFieldsFromFlags(value, result); |
| } |
| } |
| } |
| if (result!=null) return result; |
| |
| throw new IllegalStateException("Entity instances and other Brooklyn objects should be supplied as unique IDs; they cannot be instantiated from YAML. If a spec is desired, the type should be known or use $brooklyn:entitySpec."); |
| } |
| |
| @Override public BrooklynObject convertStringToObject(String value, JsonParser p, DeserializationContext ctxt) throws IOException { |
| try { |
| return super.convertStringToObject(value, p, ctxt); |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| if (BrooklynObjectSerialization.this.mgmt==null) { |
| LOG.warn("Reference to BrooklynObject " + value + " outwith task context or without mgmt set; replacing with 'null': "+e); |
| } else if (BrooklynObjectSerialization.this.mgmt instanceof NonDeploymentManagementContext) { |
| LOG.warn("Reference to BrooklynObject " + value + " which is unknown or not yet known, using NonDeployment context; replacing with 'null': "+e); |
| } else { |
| LOG.warn("Reference to BrooklynObject " + value + " which is unknown or no longer available; replacing with 'null': "+e); |
| } |
| return null; |
| } |
| } |
| } |
| } |
| |
| public static class ConfigKeySerialization { |
| private final ManagementContext mgmt; |
| public ConfigKeySerialization(ManagementContext mgmt) { this.mgmt = mgmt; } |
| public void apply(SimpleModule m) { |
| m.addKeyDeserializer(ConfigKey.class, new CKKeyDeserializer()); |
| } |
| static class CKKeyDeserializer extends KeyDeserializer { |
| @Override |
| public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { |
| // ignores type, but allows us to serialize entity specs etc |
| return ConfigKeys.newConfigKey(Object.class, key); |
| } |
| } |
| } |
| |
| public static class PredicateSerialization { |
| private final ManagementContext mgmt; |
| public PredicateSerialization(ManagementContext mgmt) { this.mgmt = mgmt; } |
| public void apply(SimpleModule m) { |
| DslPredicates.DslPredicateJsonDeserializer.DSL_REGISTERED_CLASSES.forEach(t -> |
| m.addDeserializer(t, new DslPredicates.DslPredicateJsonDeserializer()) ); |
| } |
| } |
| |
| /** Serializing TypeTokens is annoying; basically we wrap the Type, and intercept 3 things specially */ |
| // we've tried jackson-datatype-guava's mapper.registerModule(new GuavaModule()) |
| // but it doesn't support TypeToken (or guava Predicates) |
| public static class GuavaTypeTokenSerialization extends BeanSerializerModifier { |
| |
| public static final String RUNTIME_TYPE = "runtimeType"; |
| |
| public void apply(ObjectMapper m, SimpleModule module, InterceptibleDeserializers interceptible) { |
| m.setSerializerFactory(m.getSerializerFactory().withSerializerModifier(this)); |
| |
| TypeTokenDeserializer ttDeserializer = new TypeTokenDeserializer(); |
| interceptible.addSubtypeInterceptor(TypeToken.class, ttDeserializer); |
| module.addDeserializer(TypeToken.class, (JsonDeserializer) ttDeserializer); |
| |
| ParameterizedTypeDeserializer ptDeserializer = new ParameterizedTypeDeserializer(); |
| interceptible.addSubtypeInterceptor(ParameterizedType.class, ptDeserializer); |
| module.addDeserializer(ParameterizedType.class, (JsonDeserializer) ptDeserializer); |
| |
| module.addDeserializer(Type.class, (JsonDeserializer) new JavaLangTypeDeserializer()); |
| } |
| |
| @Override |
| public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { |
| if (TypeToken.class.isAssignableFrom(beanDesc.getBeanClass())) { |
| // not needed, but kept in case useful in future |
| // Set<String> fields = MutableList.copyOf(Reflections.findFields(beanDesc.getBeanClass(), null, null)) |
| // .stream().map(f -> f.getName()).collect(Collectors.toSet()); |
| beanProperties = beanProperties.stream().filter(p -> RUNTIME_TYPE.equals(p.getName())).collect(Collectors.toList()); |
| } |
| |
| return beanProperties; |
| } |
| |
| static class TypeTokenDeserializer extends JsonSymbolDependentDeserializer { |
| @Override |
| public JavaType getDefaultType() { |
| return ctxt.constructType(RuntimeTypeHolder.class); |
| } |
| |
| @Override |
| protected Object deserializeObject(JsonParser p) throws IOException { |
| Object holder = contextualize(createBeanDeserializer(ctxt, getDefaultType())).deserialize(p, ctxt); |
| return TypeToken.of( ((RuntimeTypeHolder)holder).runtimeType ); |
| } |
| } |
| |
| static class ParameterizedTypeDeserializer extends JsonSymbolDependentDeserializer { |
| @Override |
| public JavaType getDefaultType() { |
| return ctxt.constructType(BrooklynTypeNameResolution.BetterToStringParameterizedTypeImpl.class); |
| } |
| |
| @Override |
| public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { |
| super.createContextual(ctxt, property); |
| // always use this one |
| type = getDefaultType(); |
| return this; |
| } |
| } |
| |
| static class RuntimeTypeHolder { |
| private Type runtimeType; |
| |
| // jackson compatibility |
| private void setType(Object type) { |
| // ignore; always deserialize as SimpleTypeToken |
| } |
| } |
| |
| static class JavaLangTypeDeserializer extends JsonSymbolDependentDeserializer { |
| @Override |
| public JavaType getDefaultType() { |
| return ctxt.constructType(Class.class); |
| } |
| |
| @Override |
| protected JsonDeserializer<?> getTokenDeserializer() throws IOException { |
| return createBeanDeserializer(ctxt, getDefaultType()); |
| } |
| } |
| } |
| |
| // kept for reference, if we did want to customize the mode (but you cannot from here inject parameter names) |
| // public static class CustomAnnotationInspector extends JacksonAnnotationIntrospector { |
| // @Override |
| // public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) { |
| // // does not work |
| //// if (a.getAnnotated() instanceof Constructor && ((Constructor)a.getAnnotated()).getDeclaringClass().getPackage().equals(Predicate.class.getPackage())) { |
| //// return JsonCreator.Mode.DELEGATING; |
| //// } |
| // return super.findCreatorAnnotation(config, a); |
| // } |
| // } |
| |
| } |