001package com.identityworksllc.iiq.common.plugin; 002 003import com.fasterxml.jackson.databind.ObjectMapper; 004import com.fasterxml.jackson.databind.type.TypeFactory; 005import org.apache.commons.logging.Log; 006import org.apache.commons.logging.LogFactory; 007import sailpoint.tools.GeneralException; 008import sailpoint.tools.Util; 009 010import javax.ws.rs.WebApplicationException; 011import javax.ws.rs.core.MediaType; 012import javax.ws.rs.core.MultivaluedMap; 013import javax.ws.rs.ext.MessageBodyReader; 014import java.io.IOException; 015import java.io.InputStream; 016import java.lang.annotation.Annotation; 017import java.lang.reflect.Type; 018 019/** 020 * Message body reader that can be reused across plugins. As it turns out, Jersey has a hard 021 * time when more than one message body reader can read the same input. This class will allow 022 * any installed plugin's message body reader to deserialize any plugin's VO classes. 023 */ 024public abstract class PluginJacksonMessageBodyReader implements MessageBodyReader<Object> { 025 /** 026 * Logger 027 */ 028 private static final Log log = LogFactory.getLog(PluginJacksonMessageBodyReader.class); 029 030 /** 031 * The package prefix, used to decide whether this class is decodable 032 */ 033 private final String packagePrefix; 034 035 /** 036 * Sets the package prefix 037 * @param packagePrefix The package prefix for your implementation 038 */ 039 protected PluginJacksonMessageBodyReader(String packagePrefix) { 040 if (Util.isNullOrEmpty(packagePrefix)) { 041 throw new IllegalArgumentException("Package prefix must be non-empty"); 042 } 043 044 // TODO read additional prefixes from a system property 045 046 this.packagePrefix = packagePrefix; 047 } 048 049 /** 050 * Determines whether the input is readable 051 * 052 * @see MessageBodyReader#isReadable(Class, Type, Annotation[], MediaType) 053 */ 054 @Override 055 public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 056 if (log.isTraceEnabled()) { 057 log.trace("Checking whether we can decode into type " + type.getName()); 058 } 059 return type.getName().startsWith(packagePrefix); 060 } 061 062 /** 063 * Reads the input in the context of the requested type's classloader 064 * 065 * @see MessageBodyReader#readFrom(Class, Type, Annotation[], MediaType, MultivaluedMap, InputStream) 066 */ 067 @Override 068 public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { 069 if (log.isTraceEnabled()) { 070 log.trace("Reading message body into type " + type.getName()); 071 } 072 073 try { 074 try { 075 String json = Util.readInputStream(entityStream); 076 077 ObjectMapper om = new ObjectMapper(); 078 TypeFactory tf = TypeFactory.defaultInstance().withClassLoader(type.getClassLoader()); 079 om.setTypeFactory(tf); 080 081 return om.readValue(json, type); 082 } catch(IOException e) { 083 if (log.isDebugEnabled()) { 084 log.debug("Caught an error reading JSON object into type " + type.getName(), e); 085 } 086 throw e; 087 } 088 } catch(GeneralException e) { 089 throw new IOException(e); 090 } 091 } 092}