/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.kie.server.api.marshalling.json; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Member; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.regex.Pattern; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import org.drools.core.xml.jaxb.util.JaxbListAdapter; import org.drools.core.xml.jaxb.util.JaxbListWrapper; import org.drools.core.xml.jaxb.util.JaxbUnknownAdapter; import org.kie.server.api.marshalling.Marshaller; import org.kie.server.api.marshalling.MarshallerFactory; import org.kie.server.api.marshalling.MarshallingException; import org.kie.server.api.marshalling.MarshallingFormat; import org.kie.server.api.model.Wrapped; import org.kie.server.api.model.type.JaxbByteArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JSONMarshaller implements Marshaller { private static final Logger logger = LoggerFactory.getLogger( MarshallerFactory.class ); private static boolean formatDate = Boolean.parseBoolean(System.getProperty("org.kie.server.json.format.date", "false")); private static String dateFormatStr = System.getProperty("org.kie.server.json.date_format", "yyyy-MM-dd'T'hh:mm:ss.SSSZ"); private ThreadLocal<Boolean> stripped = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return false; } }; protected ClassLoader classLoader; protected ObjectMapper objectMapper; protected Set<Class<?>> classesSet; protected ObjectMapper deserializeObjectMapper; protected DateFormat dateFormat = new SimpleDateFormat(dateFormatStr); // Optional Marshaller Extension to handle new types private static final List<JSONMarshallerExtension> EXTENSIONS; // Load Marshaller Extension static { logger.info("Marshaller extensions init"); ServiceLoader<JSONMarshallerExtension> plugins = ServiceLoader.load(JSONMarshallerExtension.class); List<JSONMarshallerExtension> loadedPlugins = new ArrayList<>(); plugins.forEach( plugin -> { logger.info("JSONMarshallerExtension implementation found: {}", plugin.getClass().getName()); loadedPlugins.add(plugin); }); EXTENSIONS = Collections.unmodifiableList(loadedPlugins); } public JSONMarshaller(Set<Class<?>> classes, ClassLoader classLoader) { this.classLoader = classLoader; buildMarshaller(classes, classLoader); configureMarshaller(classes, classLoader); } protected void buildMarshaller( Set<Class<?>> classes, final ClassLoader classLoader ) { objectMapper = new ObjectMapper(); deserializeObjectMapper = new ObjectMapper(); } protected void configureMarshaller( Set<Class<?>> classes, final ClassLoader classLoader ) { ObjectMapper customSerializationMapper = new ObjectMapper(); if (classes == null) { classes = new HashSet<Class<?>>(); } // add byte array handling support to allow byte[] to be send as payload classes.add(JaxbByteArray.class); List<NamedType> customClasses = prepareCustomClasses(classes); // this is needed because we need better control of serialization and deserialization AnnotationIntrospector primary = new ExtendedJaxbAnnotationIntrospector(customClasses, customSerializationMapper); AnnotationIntrospector secondary = new JacksonAnnotationIntrospector(); AnnotationIntrospector introspectorPair = AnnotationIntrospector.pair(primary, secondary); objectMapper.setConfig( objectMapper.getSerializationConfig() .with(introspectorPair) .with(SerializationFeature.INDENT_OUTPUT)); deserializeObjectMapper.setConfig(objectMapper.getDeserializationConfig() .with(introspectorPair) .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); // setup custom serialization mapper with jaxb adapters customSerializationMapper.setConfig(customSerializationMapper.getDeserializationConfig().with(introspectorPair)); customSerializationMapper.setConfig(customSerializationMapper.getSerializationConfig().with(introspectorPair).with(SerializationFeature.INDENT_OUTPUT)); // in case there are custom classes register module to deal with them both for serialization and deserialization // this module makes sure that only custom classes are equipped with type information if (classes != null && !classes.isEmpty()) { ObjectMapper customObjectMapper = new ObjectMapper(); TypeResolverBuilder<?> typer = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL){ @Override public boolean useForType(JavaType t) { if (classesSet.contains(t.getRawClass())) { return true; } return false; } }; typer = typer.init(JsonTypeInfo.Id.CLASS, null); typer = typer.inclusion(JsonTypeInfo.As.WRAPPER_OBJECT); customObjectMapper.setDefaultTyping(typer); SimpleModule mod = new SimpleModule("custom-object-mapper", Version.unknownVersion()); CustomObjectSerializer customObjectSerializer = new CustomObjectSerializer(customObjectMapper); for (Class<?> clazz : classes) { mod.addSerializer(clazz, customObjectSerializer); } objectMapper.registerModule(mod); TypeResolverBuilder<?> typer2 = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL){ @Override public boolean useForType(JavaType t) { if (classesSet.contains(t.getRawClass())) { return true; } return false; } @Override public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) { if (useForType(baseType)) { if (_idType == JsonTypeInfo.Id.NONE) { return null; } TypeIdResolver idRes = idResolver(config, baseType, subtypes, false, true); switch (_includeAs) { case WRAPPER_OBJECT: return new CustomAsWrapperTypeDeserializer(baseType, idRes, _typeProperty, true, _defaultImpl); } } return super.buildTypeDeserializer(config, baseType, subtypes); } }; typer2 = typer2.init(JsonTypeInfo.Id.CLASS, null); typer2 = typer2.inclusion(JsonTypeInfo.As.WRAPPER_OBJECT); deserializeObjectMapper.setDefaultTyping(typer2); SimpleModule modDeser = new SimpleModule("custom-object-unmapper", Version.unknownVersion()); modDeser.addDeserializer(Object.class, new CustomObjectDeserializer(classes)); deserializeObjectMapper.registerModule(modDeser); } if (formatDate) { objectMapper.setDateFormat(dateFormat); customSerializationMapper.setDateFormat(dateFormat); deserializeObjectMapper.setDateFormat(dateFormat); deserializeObjectMapper.getDeserializationConfig().with(dateFormat); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); customSerializationMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); } this.classesSet = classes; // Extend the marshaller with optional extensions for(JSONMarshallerExtension extension : EXTENSIONS){ extension.extend(this, objectMapper, deserializeObjectMapper); } } protected List<NamedType> prepareCustomClasses(Set<Class<?>> classes) { List<NamedType> customClasses = new ArrayList<NamedType>(); if (classes != null) { for (Class<?> clazz : classes) { customClasses.add(new NamedType(clazz, clazz.getSimpleName())); customClasses.add(new NamedType(clazz, clazz.getName())); } } return customClasses; } @Override public String marshall(Object objectInput) { try { return objectMapper.writeValueAsString(wrap(objectInput)); } catch (IOException e) { throw new MarshallingException("Error marshalling input", e); } } @Override public <T> T unmarshall(String serializedInput, Class<T> type) { try { Class actualType = classesSet.contains(type) ? Object.class : type; return (T) unwrap(deserializeObjectMapper.readValue(serializedInput, actualType)); } catch (IOException e) { throw new MarshallingException("Error unmarshalling input", e); } finally { stripped.set(false); } } @Override public void dispose() { } @Override public MarshallingFormat getFormat() { return MarshallingFormat.JSON; } protected Object wrap(Object data) { if (data instanceof byte[]) { return new JaxbByteArray((byte[]) data); } return data; } protected Object unwrap(Object data) { if (data instanceof Wrapped) { return ((Wrapped) data).unwrap(); } return data; } class ExtendedJaxbAnnotationIntrospector extends JaxbAnnotationIntrospector { private List<NamedType> customClasses; private ObjectMapper customObjectMapper; public ExtendedJaxbAnnotationIntrospector(List<NamedType> customClasses, ObjectMapper anotherCustomObjectMapper) { this.customClasses = customClasses; this.customObjectMapper = anotherCustomObjectMapper; } @Override public List<NamedType> findSubtypes(Annotated a) { List<NamedType> base = super.findSubtypes(a); List<NamedType> complete = new ArrayList<NamedType>(); if (base != null) { complete.addAll(base); } if (customClasses != null) { complete.addAll(customClasses); } return complete; } @Override public JsonSerializer<?> findSerializer(Annotated am) { // replace JaxbUnknownAdapter as it breaks JSON marshaller for list and maps with wrapping serializer XmlJavaTypeAdapter adapterInfo = findAnnotation(XmlJavaTypeAdapter.class, am, true, false, false); if (adapterInfo != null && adapterInfo.value().isAssignableFrom(JaxbUnknownAdapter.class)) { if ( findAnnotation(JsonSerialize.class, am, true, false, false) != null ) { // .. unless there is also an explicitly specified serializer, in such case use the specified one: return super.findSerializer(am); } return new WrappingObjectSerializer(customObjectMapper); } return super.findSerializer(am); } @Override public Object findSerializationConverter(Annotated a) { Class<?> serType = _rawSerializationType(a); // Can apply to both container and regular type; no difference yet here XmlAdapter<?,?> adapter = findAdapter(a, true, serType); if (adapter != null) { return _converter(adapter, true); } return null; } private <A extends Annotation> A findAnnotation(Class<A> annotationClass, Annotated annotated, boolean includePackage, boolean includeClass, boolean includeSuperclasses) { Annotation annotation = annotated.getAnnotation(annotationClass); if(annotation != null) { return (A) annotation; } else { Class memberClass = null; if(annotated instanceof AnnotatedParameter) { memberClass = ((AnnotatedParameter)annotated).getDeclaringClass(); } else { AnnotatedElement pkg = annotated.getAnnotated(); if(pkg instanceof Member) { memberClass = ((Member)pkg).getDeclaringClass(); if(includeClass) { annotation = memberClass.getAnnotation(annotationClass); if(annotation != null) { return (A) annotation; } } } else { if(!(pkg instanceof Class)) { throw new IllegalStateException("Unsupported annotated member: " + annotated.getClass().getName()); } memberClass = (Class)pkg; } } if(memberClass != null) { if(includeSuperclasses) { for(Class pkg1 = memberClass.getSuperclass(); pkg1 != null && pkg1 != Object.class; pkg1 = pkg1.getSuperclass()) { annotation = pkg1.getAnnotation(annotationClass); if(annotation != null) { return (A) annotation; } } } if(includePackage) { Package pkg2 = memberClass.getPackage(); if(pkg2 != null) { return memberClass.getPackage().getAnnotation(annotationClass); } } } return null; } } private XmlAdapter<Object,Object> findAdapter(Annotated am, boolean forSerialization, Class<?> type) { // First of all, are we looking for annotations for class? if (am instanceof AnnotatedClass) { return findAdapterForClass((AnnotatedClass) am, forSerialization); } // Otherwise for a member. First, let's figure out type of property XmlJavaTypeAdapter adapterInfo = findAnnotation(XmlJavaTypeAdapter.class, am, true, false, false); if (adapterInfo != null) { XmlAdapter<Object,Object> adapter = checkAdapter(adapterInfo, type, forSerialization); if (adapter != null) { return adapter; } } XmlJavaTypeAdapters adapters = findAnnotation(XmlJavaTypeAdapters.class, am, true, false, false); if (adapters != null) { for (XmlJavaTypeAdapter info : adapters.value()) { XmlAdapter<Object,Object> adapter = checkAdapter(info, type, forSerialization); if (adapter != null) { return adapter; } } } return null; } private final XmlAdapter<Object,Object> checkAdapter(XmlJavaTypeAdapter adapterInfo, Class<?> typeNeeded, boolean forSerialization) { // if annotation has no type, it's applicable; if it has, must match Class<?> adaptedType = adapterInfo.type(); if (adapterInfo.value().isAssignableFrom(JaxbUnknownAdapter.class)) { return null; } if (adaptedType == XmlJavaTypeAdapter.DEFAULT.class) { JavaType[] params = _typeFactory.findTypeParameters(adapterInfo.value(), XmlAdapter.class); adaptedType = params[1].getRawClass(); } if (adaptedType.isAssignableFrom(typeNeeded)) { @SuppressWarnings("rawtypes") Class<? extends XmlAdapter> cls = adapterInfo.value(); return ClassUtil.createInstance(cls, true); } return null; } @SuppressWarnings("unchecked") private XmlAdapter<Object,Object> findAdapterForClass(AnnotatedClass ac, boolean forSerialization) { XmlJavaTypeAdapter adapterInfo = ac.getAnnotated().getAnnotation(XmlJavaTypeAdapter.class); if (adapterInfo != null) { @SuppressWarnings("rawtypes") Class<? extends XmlAdapter> cls = adapterInfo.value(); return ClassUtil.createInstance(cls, true); } return null; } } /** * Simple utility Serializer which can be used to override replacement of JaxbUnknownAdapter with WrappingObjectSerializer */ public static class PassThruSerializer extends JsonSerializer<Object> { @Override public void serialize(Object p0, JsonGenerator p1, SerializerProvider p2) throws IOException, JsonProcessingException { p1.writeObject(p0); } } class CustomObjectSerializer extends JsonSerializer<Object> { private ObjectMapper customObjectMapper; public CustomObjectSerializer(ObjectMapper customObjectMapper) { this.customObjectMapper = customObjectMapper; } @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { String json = customObjectMapper.writeValueAsString(value); jgen.writeRawValue(json); } } class WrappingObjectSerializer extends JsonSerializer<Object> { private ObjectMapper customObjectMapper; public WrappingObjectSerializer(ObjectMapper customObjectMapper) { this.customObjectMapper = customObjectMapper; } @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { String className = value.getClass().getName(); if (value instanceof Collection) { String collectionJson = writeCollection((Collection) value, customObjectMapper); jgen.writeRawValue(collectionJson); } else if (value instanceof Map) { String mapJson = writeMap((Map) value, customObjectMapper); jgen.writeRawValue(mapJson); } else if (value instanceof Object[] || value.getClass().isArray()) { String arrayJson = writeArray((Object[]) value, customObjectMapper); jgen.writeRawValue(arrayJson); } else { String json = customObjectMapper.writeValueAsString(value); // don't wrap java and javax classes as they are always available, in addition avoid double wrapping if (!className.startsWith("java.") && !className.startsWith("javax.") && !json.contains(className)) { json = "{\"" + className + "\":" + json + "}"; } jgen.writeRawValue(json); } } private String writeArray(Object[] value, ObjectMapper customObjectMapper) throws IOException{ StringBuilder builder = new StringBuilder(); builder.append("["); int size = Array.getLength(value); for (Object element : value) { size--; String elementClassName = element.getClass().getName(); String json = customObjectMapper.writeValueAsString(element); // don't wrap java and javax classes as they are always available, in addition avoid double wrapping if (!elementClassName.startsWith("java.") && !elementClassName.startsWith("javax.") && !json.contains(elementClassName)) { json = "{\"" + elementClassName + "\":" + json + "}"; } builder.append(json); if (size > 0) { builder.append(","); } } builder.append("]"); return builder.toString(); } private String writeMap(Map value, ObjectMapper customObjectMapper) throws IOException{ StringBuilder builder = new StringBuilder(); builder.append("{"); int size = ((Map<?, ?>)value).size(); for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet()) { size--; // handle map key Object key = entry.getKey(); String keyClassName = key.getClass().getName(); String json = customObjectMapper.writeValueAsString(key); // don't wrap java and javax classes as they are always available, in addition avoid double wrapping if (!keyClassName.startsWith("java.") && !keyClassName.startsWith("javax.") && !json.contains(keyClassName)) { json = "{\"" + keyClassName + "\":" + json + "}"; } // handle map value Object mValue = entry.getValue(); String mValueClassName = mValue.getClass().getName(); String jsonValue = customObjectMapper.writeValueAsString(mValue); // don't wrap java and javax classes as they are always available, in addition avoid double wrapping if (!mValueClassName.startsWith("java.") && !mValueClassName.startsWith("javax.") && !json.contains(mValueClassName)) { jsonValue = "{\"" + mValueClassName + "\":" + jsonValue + "}"; } // add as JSON map builder.append(json); builder.append(" : "); builder.append(jsonValue); if (size > 0) { builder.append(","); } } builder.append("}"); return builder.toString(); } private String writeCollection(Collection collection, ObjectMapper customObjectMapper) throws IOException { StringBuilder builder = new StringBuilder(); builder.append("["); int size = collection.size(); Iterator it = collection.iterator(); while (it.hasNext()) { size--; Object element = it.next(); String elementClassName = element.getClass().getName(); String json = customObjectMapper.writeValueAsString(element); // don't wrap java and javax classes as they are always available, in addition avoid double wrapping if (!elementClassName.startsWith("java.") && !elementClassName.startsWith("javax.") && !json.contains(elementClassName)) { json = "{\"" + elementClassName + "\":" + json + "}"; } builder.append(json); if (size > 0) { builder.append(","); } } builder.append("]"); return builder.toString(); } } class CustomObjectDeserializer extends UntypedObjectDeserializer { private final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile("(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); private static final long serialVersionUID = 7764405880012867708L; private Map<String, Class<?>> classes = new HashMap<String, Class<?>>(); public CustomObjectDeserializer(Set<Class<?>> classes) { for (Class<?> c : classes) { this.classes.put(c.getSimpleName(), c); this.classes.put(c.getName(), c); } } @Override protected Object mapObject(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } // 1.6: minor optimization; let's handle 1 and 2 entry cases separately if (t != JsonToken.FIELD_NAME) { // and empty one too // empty map might work; but caller may want to modify... so better just give small modifiable return new LinkedHashMap<String, Object>(4); } String field1 = jp.getText(); jp.nextToken(); if (classes.containsKey(field1)) { stripped.set(true); Object value = deserializeObjectMapper.readValue(jp, classes.get(field1)); jp.nextToken(); return value; } else { if (isFullyQualifiedClassname(field1)) { try { Object value = deserializeObjectMapper.readValue(jp, classLoader.loadClass(field1)); jp.nextToken(); return value; } catch (ClassNotFoundException e) { } } Object value1 = deserialize(jp, ctxt); if (jp.nextToken() != JsonToken.FIELD_NAME) { // single entry; but we want modifiable LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4); result.put(field1, value1); return result; } String field2 = jp.getText(); jp.nextToken(); Object value2 = deserialize(jp, ctxt); if (jp.nextToken() != JsonToken.FIELD_NAME) { LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4); result.put(field1, value1); result.put(field2, value2); return result; } // And then the general case; default map size is 16 LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(); result.put(field1, value1); result.put(field2, value2); do { String fieldName = jp.getText(); jp.nextToken(); result.put(fieldName, deserialize(jp, ctxt)); } while (jp.nextToken() != JsonToken.END_OBJECT); // in case nested jaxb list wrapper was not recognized automatically map it manually if (result.containsKey("type") && result.containsKey("componentType") && result.containsKey("element")) { JaxbListWrapper wrapper = new JaxbListWrapper(); wrapper.setType(JaxbListWrapper.JaxbWrapperType.valueOf((String)result.get("type"))); wrapper.setComponentType((String)result.get("componentType")); wrapper.setElements(toArray(result.get("element"))); try { Object data = null; if (wrapper.getType().equals(JaxbListWrapper.JaxbWrapperType.MAP)) { Map<Object, Object> tranformed = new LinkedHashMap<Object, Object>(); // this is mapped to JaxbStringObjectPair for (Object element : wrapper.getElements()) { Map<Object, Object> map = (Map<Object, Object>) element; tranformed.put(map.get("key"), map.get("value")); } data = tranformed; } else { data = new JaxbListAdapter().unmarshal(wrapper); } return data; } catch (Exception e) { } } return result; } } private Object[] toArray(Object element) { if (element != null) { if (element instanceof Collection) { return ((Collection) element).toArray(); } } return new Object[0]; } private boolean isFullyQualifiedClassname(String classname) { if (!classname.contains(".")) { return false; } return VALID_JAVA_IDENTIFIER.matcher(classname).matches(); } } class CustomAsWrapperTypeDeserializer extends AsWrapperTypeDeserializer { public CustomAsWrapperTypeDeserializer(JavaType bt, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible, Class<?> defaultImpl) { super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl); } protected CustomAsWrapperTypeDeserializer(AsWrapperTypeDeserializer src, BeanProperty property) { super(src, property); } @Override public Object deserializeTypedFromArray(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ClassLoader current = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(_baseType.getRawClass().getClassLoader()); JsonDeserializer<Object> deser = _findDeserializer(ctxt, baseTypeName()); Object value = deser.deserialize(jp, ctxt); return value; } finally { Thread.currentThread().setContextClassLoader(current); } } @Override public Object deserializeTypedFromObject(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ClassLoader current = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(_baseType.getRawClass().getClassLoader()); if (classesSet.contains(_baseType.getRawClass()) && !stripped.get()) { try { return super.deserializeTypedFromObject(jp, ctxt); } catch (Exception e) { JsonDeserializer<Object> deser = _findDeserializer(ctxt, baseTypeName()); Object value = deser.deserialize(jp, ctxt); return value; } } stripped.set(false); JsonDeserializer<Object> deser = _findDeserializer(ctxt, baseTypeName()); Object value = deser.deserialize(jp, ctxt); return value; } finally { Thread.currentThread().setContextClassLoader(current); } } @Override public Object deserializeTypedFromScalar(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ClassLoader current = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(_baseType.getRawClass().getClassLoader()); if (classesSet.contains(_baseType.getRawClass())) { return super.deserializeTypedFromScalar(jp, ctxt); } JsonDeserializer<Object> deser = _findDeserializer(ctxt, baseTypeName()); Object value = deser.deserialize(jp, ctxt); return value; } finally { Thread.currentThread().setContextClassLoader(current); } } @Override public Object deserializeTypedFromAny(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ClassLoader current = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(_baseType.getRawClass().getClassLoader()); if (classesSet.contains(_baseType.getRawClass())) { try { return super.deserializeTypedFromAny(jp, ctxt); } catch (Exception e) { JsonDeserializer<Object> deser = _findDeserializer(ctxt, baseTypeName()); Object value = deser.deserialize(jp, ctxt); return value; } } if (_baseType.isMapLikeType() && jp.getCurrentToken() == JsonToken.START_ARRAY) { LinkedHashMap<Object, Object> data = new LinkedHashMap<Object, Object>(); jp.nextToken(); if (jp.getCurrentToken() == JsonToken.END_ARRAY) { return data; } JsonDeserializer<Object> deser = _findDeserializer(ctxt, LinkedHashMap.class.getName()); Map<Object, Object> value = (Map) deser.deserialize(jp, ctxt); jp.nextToken(); if (value != null) { Collection<Object> values = value.values(); if (values.size() == 2) { Iterator<Object> it = values.iterator(); data.put(it.next(), it.next()); return data; } } return value; } else { JsonDeserializer<Object> deser = _findDeserializer(ctxt, baseTypeName()); Object value = deser.deserialize(jp, ctxt); return value; } } finally { Thread.currentThread().setContextClassLoader(current); } } @Override public TypeDeserializer forProperty(BeanProperty prop) { if (prop != null) { if (useForType(prop.getType()) || useForType(prop.getType().getContentType()) ) { return new CustomAsWrapperTypeDeserializer(this, prop); } } return super.forProperty(prop); } boolean useForType(JavaType t) { if (classesSet.contains(t.getRawClass())) { return true; } return false; } } @Override public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public ClassLoader getClassLoader() { return classLoader; } }