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}