package de.galan.verjson.core; import java.lang.reflect.Method; import java.time.ZonedDateTime; import java.util.Date; import java.util.List; import java.util.Set; import org.apache.logging.log4j.Logger; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.common.collect.Lists; import de.galan.commons.logging.Logr; import de.galan.commons.util.Pair; import de.galan.verjson.serializer.DateDeserializer; import de.galan.verjson.serializer.DateSerializer; import de.galan.verjson.serializer.ZonedDateTimeDeserializer; import de.galan.verjson.serializer.ZonedDateTimeSerializer; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ClassFile; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.AnnotationMemberValue; import javassist.bytecode.annotation.ArrayMemberValue; import javassist.bytecode.annotation.ClassMemberValue; import javassist.bytecode.annotation.EnumMemberValue; import javassist.bytecode.annotation.MemberValue; import javassist.bytecode.annotation.StringMemberValue; /** * Construction of the Jackson ObjectMapper. Configuring Fieldintrospection, Serializer/Deserializer, Polymorph class * registration. * * @author daniel */ public class ObjectMapperFactory { private static final Logger LOG = Logr.get(); public ObjectMapper create(Versions versions) { ObjectMapper result = new ObjectMapper(); result.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); result.setSerializationInclusion(Include.NON_NULL); SimpleModule module = new SimpleModule("VerjsonModule"); registerSerializer(result, module, versions); result.registerModule(module); for (Class<?> parentClass: versions.getRegisteredSubclasses().keySet()) { Class<?> mixin = generateMixIn(parentClass, versions.getRegisteredSubclasses().get(parentClass)); result.addMixIn(parentClass, mixin); } return result; } @SuppressWarnings({"unchecked", "rawtypes"}) protected void registerSerializer(ObjectMapper result, SimpleModule module, Versions versions) { // Default serializer module.addSerializer(new DateSerializer()); module.addDeserializer(Date.class, new DateDeserializer()); module.addSerializer(new ZonedDateTimeSerializer()); module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer()); // Serializer for (JsonSerializer<?> serializer: versions.getSerializer()) { module.addSerializer(serializer); } // Deserializer for (JsonDeserializer deserializer: versions.getDeserializer()) { Method method = null; try { method = deserializer.getClass().getMethod("deserialize", JsonParser.class, DeserializationContext.class); module.addDeserializer(method.getReturnType(), deserializer); } catch (NullPointerException | NoSuchMethodException | SecurityException ex) { String methodName = (method == null) ? "null" : method.getName(); String returnTypeName = (method == null || method.getReturnType() == null) ? "null" : method.getReturnType().toString(); LOG.error("Unable to register deserializer for Class<" + returnTypeName + ">." + methodName + "(..)", ex); } } } protected static Class<?> generateMixIn(Class<?> parent, Set<Pair<Class<?>, String>> childs) { ClassPool pool = ClassPool.getDefault(); String className = parent.getPackage().getName() + ".Gen" + parent.getSimpleName() + "MixIn"; CtClass ctClass = pool.getOrNull(className); Class<?> result = null; if (ctClass == null) { ctClass = pool.makeClass(className); // TODO check inner classes in parentname ClassFile cf = ctClass.getClassFile(); cf.setMajorVersion(ClassFile.JAVA_7); cf.setMinorVersion(0); ConstPool cp = cf.getConstPool(); // @JsonTypeInfo Annotation annotationInfo = new Annotation(JsonTypeInfo.class.getName(), cp); EnumMemberValue enumId = new EnumMemberValue(cp); enumId.setType(JsonTypeInfo.Id.class.getName()); enumId.setValue(JsonTypeInfo.Id.NAME.toString()); annotationInfo.addMemberValue("use", enumId); EnumMemberValue enumAs = new EnumMemberValue(cp); enumAs.setType(JsonTypeInfo.As.class.getName()); enumAs.setValue(As.PROPERTY.toString()); annotationInfo.addMemberValue("include", enumAs); annotationInfo.addMemberValue("property", new StringMemberValue("$type", cp)); AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); attr.addAnnotation(annotationInfo); ClassMemberValue cmvNone = new ClassMemberValue(Void.class.getName(), cp); annotationInfo.addMemberValue("defaultImpl", cmvNone); // @JsonSubTypes List<AnnotationMemberValue> amvs = Lists.newArrayList(); for (Pair<Class<?>, String> child: childs) { Annotation annotationType = new Annotation(Type.class.getName(), cp); annotationType.addMemberValue("value", new ClassMemberValue(child.getKey().getName(), cp)); annotationType.addMemberValue("name", new StringMemberValue(child.getValue(), cp)); AnnotationMemberValue amv = new AnnotationMemberValue(cp); amv.setValue(annotationType); amvs.add(amv); } Annotation annotationSub = new Annotation(JsonSubTypes.class.getName(), cp); ArrayMemberValue arraymv = new ArrayMemberValue(cp); MemberValue[] valueSubs = amvs.toArray(new MemberValue[] {}); arraymv.setValue(valueSubs); annotationSub.addMemberValue("value", arraymv); attr.addAnnotation(annotationSub); cf.addAttribute(attr); try { result = ctClass.toClass(); } catch (CannotCompileException ex) { throw new RuntimeException("Failed generating MixIn for registered Subclasses (" + className + ")", ex); } } else { try { result = Class.forName(className); } catch (ClassNotFoundException ex) { throw new RuntimeException("Failed loading generated MixIn (" + className + ")", ex); } } return result; } }