001package com.identityworksllc.iiq.common.vo; 002 003import com.fasterxml.jackson.core.JsonParser; 004import com.fasterxml.jackson.core.JsonProcessingException; 005import com.fasterxml.jackson.databind.DeserializationContext; 006import com.fasterxml.jackson.databind.JsonNode; 007import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 008import sailpoint.api.SailPointContext; 009import sailpoint.api.SailPointFactory; 010import sailpoint.tools.GeneralException; 011import sailpoint.tools.xml.XMLObjectFactory; 012 013import java.io.IOException; 014import java.util.Arrays; 015import java.util.HashSet; 016import java.util.Set; 017 018/** 019 * A Jackson de-serializer for an IIQ object. The input is expected to 020 * have a 'type' and an 'xml'. The XML will be parsed using Sailpoint's 021 * default {@link sailpoint.tools.xml.XMLObjectFactory} parser into an 022 * object of the given type. 023 */ 024public class IIQObjectDeserializer extends StdDeserializer<Object> { 025 /** 026 * The list of valid types apart from `sailpoint.object.*` classes 027 */ 028 private static final Set<String> VALID_TYPES = new HashSet<>(Arrays.asList( 029 "java.util.Map", 030 "java.util.List", 031 "java.lang.String", 032 "java.lang.Boolean", 033 "java.util.Date" 034 )); 035 036 /** 037 * The constructor expected by Jackson 038 */ 039 protected IIQObjectDeserializer() { 040 this(null); 041 } 042 043 /** 044 * The constructor expected by Jackson, providing the type of this object 045 * @param t This type 046 */ 047 public IIQObjectDeserializer(Class<Object> t) { 048 super(t); 049 } 050 051 @Override 052 public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 053 JsonNode node = p.getCodec().readTree(p); 054 if (node.isObject()) { 055 JsonNode xml = node.get("xml"); 056 JsonNode type = node.get("type"); 057 058 String typeStr = type.asText(); 059 if (typeStr == null || typeStr.trim().isEmpty()) { 060 throw new IOException("Unable to read a serialized IIQObject without a type"); 061 } 062 if (typeStr.equals("null") || xml.isNull()) { 063 return null; 064 } 065 if (typeStr.startsWith("sailpoint.object") || VALID_TYPES.contains(typeStr)) { 066 String xmlStr = xml.asText(); 067 068 try { 069 SailPointContext context = SailPointFactory.getCurrentContext(); 070 if (context == null) { 071 // TODO create a private context here? 072 throw new IOException("IIQObject JSON must be deserialized in a SailPointContext session"); 073 } 074 return XMLObjectFactory.getInstance().parseXml(context, xmlStr, true); 075 } catch(GeneralException e) { 076 throw new IOException(e); 077 } 078 } else { 079 throw new IOException("Unexpected IIQObject type: " + typeStr); 080 } 081 } else if (node.isNull()) { 082 return null; 083 } else { 084 throw new IOException("Unexpected JsonNode type: " + node.getNodeType()); 085 } 086 } 087 088}