blob: 18bf7568ef4c4f67904013f5ed60630625995643 [file] [log] [blame]
/*
* 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.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.DeserializerFactory;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.Set;
import java.util.function.Function;
import org.apache.brooklyn.util.core.units.Range;
import org.apache.brooklyn.util.core.xstream.ImmutableSetConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils.createBeanDeserializer;
public abstract class JsonSymbolDependentDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
private static final Logger log = LoggerFactory.getLogger(JsonSymbolDependentDeserializer.class);
public static final Set<JsonToken> SIMPLE_TOKENS = ImmutableSet.of(
JsonToken.VALUE_STRING,
JsonToken.VALUE_NUMBER_FLOAT,
JsonToken.VALUE_NUMBER_INT,
JsonToken.VALUE_TRUE,
JsonToken.VALUE_FALSE,
JsonToken.VALUE_NULL
);
protected DeserializationContext ctxt;
protected BeanProperty beanProp;
private BeanDescription beanDesc;
protected JavaType type;
public BeanDescription getBeanDescription() {
if (beanDesc!=null) return beanDesc;
if (type!=null) return beanDesc = ctxt.getConfig().introspect(type);
return null;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
this.ctxt = ctxt;
beanProp = property;
if (property!=null) {
type = property.getType();
}
if (type==null) {
// ctxt.getContextualType() is normally set during primary contextualization and first round of secondary (if known)
// but not usually available during deserialization, so do it now;
// however it can be suppressed if in a nested secondary contextualization (eg via DelegatingDeserializer),
// but our JacksonBetterDelegatingDeserializer attempts to avoid this
type = ctxt.getContextualType();
}
if (isTypeReplaceableByDefault()) {
JavaType type2 = getDefaultType();
if (type!=null && !type.getRawClass().isAssignableFrom(type2.getRawClass())) {
// keep old type if it's not compatible; it might fail subsequently, but at least it won't be incompatible
log.warn("Default type for "+type+" reported as "+type2+", in "+this+", which is not compatible; may fail subsequently");
// but change it, as if we leave it as an interface it will almost certainly fail
}
type = type2;
}
return this;
}
protected boolean isTypeReplaceableByDefault() {
if (type==null) return true;
if (type.getRawClass().isInterface()) return true;
return false;
}
public abstract JavaType getDefaultType();
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonDeserializer<?> deser;
Function<Object,Object> post = x -> x;
if (p.getCurrentToken() == JsonToken.START_ARRAY) {
return deserializeArray(p);
} else if (SIMPLE_TOKENS.contains(p.getCurrentToken())) {
// string
return deserializeToken(p);
} else {
// other primitives not yet supported
// assume object
return deserializeObject(p);
}
}
protected JsonDeserializer<?> contextualize(JsonDeserializer<?> deser) throws IOException, JsonProcessingException {
if (deser instanceof ContextualDeserializer) {
// collection requires this initialization (beanProp seems usually to be null / irrelevant, but capture and pass it for good measure)
return ((ContextualDeserializer)deser).createContextual(ctxt, beanProp);
}
return deser;
}
protected Object deserializeArray(JsonParser p) throws IOException, JsonProcessingException {
return contextualize(getArrayDeserializer()).deserialize(p, ctxt);
}
protected JsonDeserializer<?> getArrayDeserializer() throws IOException {
if (type!=null) {
Object handler = type.getTypeHandler();
if (handler==null) {
// drop type info, in case the default type was overly restrictive
type = ctxt.constructType(Object.class);
handler = ctxt.getFactory().findTypeDeserializer(ctxt.getConfig(), type);
}
if (handler instanceof AsPropertyIfAmbiguous.AsPropertyButNotIfFieldConflictTypeDeserializer) {
/** Object.class can be encoded as array ["Class", "Object"] if type is unknown;
* it doesn't want to use { type: Class, value: Object } because it is trying to write a value string.
* this is a cheap-and-cheerful way to support that.
*/
AsPropertyIfAmbiguous.AsPropertyButNotIfFieldConflictTypeDeserializer hf = (AsPropertyIfAmbiguous.AsPropertyButNotIfFieldConflictTypeDeserializer) handler;
return new JsonDeserializer<Object>() {
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return hf.deserializeArrayContainingType(p, ctxt);
}
};
}
}
throw new IllegalStateException("List input not supported for "+type);
}
protected Object deserializeToken(JsonParser p) throws IOException, JsonProcessingException {
return contextualize(getTokenDeserializer()).deserialize(p, ctxt);
}
protected JsonDeserializer<?> getTokenDeserializer() throws IOException, JsonProcessingException {
return getObjectDeserializer();
}
/** deserializes if we know we have an object; if we have a string, it will typically go into deserializeToken */
protected Object deserializeObject(JsonParser p) throws IOException, JsonProcessingException {
return contextualize(getObjectDeserializer()).deserialize(p, ctxt);
}
protected JsonDeserializer<?> getObjectDeserializer() throws IOException, JsonProcessingException {
return createBeanDeserializer(ctxt, type, getBeanDescription(),
/** try to do low level build so we don't recreate ourselves and loop endlessly */ true,
true);
}
}