001package com.identityworksllc.iiq.common;
002
003import com.fasterxml.jackson.annotation.JsonAutoDetect;
004import com.fasterxml.jackson.annotation.JsonFilter;
005import com.fasterxml.jackson.annotation.JsonInclude;
006import com.fasterxml.jackson.annotation.PropertyAccessor;
007import com.fasterxml.jackson.databind.ObjectMapper;
008import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
009import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
010import com.fasterxml.jackson.databind.type.MapType;
011import sailpoint.tools.GeneralException;
012
013import java.util.Collections;
014import java.util.HashMap;
015import java.util.Map;
016import java.util.Set;
017
018/**
019 * An interface implementing a {@link #toMap()} default method to transform
020 * any object into a Map using Jackson. Simply implement this interface and
021 * enjoy the use of toMap() without any boilerplate code.
022 */
023public interface Mappable {
024
025    /**
026     * Sneaky mixin to enable this class to use its custom filter on classes
027     * that don't support it
028     */
029    @JsonFilter("mappableFilter")
030    @JsonInclude(JsonInclude.Include.NON_NULL)
031    final class FilterMixin {
032        // Deliberately empty because this is only used for the annotations
033    }
034
035    /**
036     * Returns a non-null Map representation of the given object using
037     * Jackson as a transformation engine.
038     *
039     * If the input is null, an empty HashMap will be returned.
040     *
041     * @param whatever The object to transform
042     * @param exclusions A set of fields to exclude from serialization, or null if none
043     * @return The resulting map
044     */
045    static Map<String, Object> toMap(Object whatever, Set<String> exclusions) {
046        if (whatever == null) {
047            return new HashMap<>();
048        }
049
050        ObjectMapper mapper = new ObjectMapper();
051        mapper.addMixIn(whatever.getClass(), FilterMixin.class);
052        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
053        SimpleFilterProvider filterProvider;
054        if (exclusions != null && !exclusions.isEmpty()) {
055            filterProvider = new SimpleFilterProvider().addFilter("mappableFilter", SimpleBeanPropertyFilter.serializeAllExcept(exclusions));
056        } else {
057            filterProvider = new SimpleFilterProvider().addFilter("mappableFilter", SimpleBeanPropertyFilter.serializeAll());
058        }
059        mapper.setFilterProvider(filterProvider);
060        MapType javaType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
061        return mapper.convertValue(whatever, javaType);
062    }
063
064    /**
065     * Returns a non-null Map representation of this object.
066     *
067     * Only non-null values will be included by default.
068     *
069     * @return A Map representation of this object.
070     * @throws GeneralException if Map conversion fails for any reason
071     */
072    default Map<String, Object> toMap() throws GeneralException {
073        return Mappable.toMap(this, toMapFieldExclusions());
074    }
075
076    /**
077     * Optionally returns a list of fields to exclude from serialization. This
078     * will be used in addition to any fields annotated with JsonIgnore.
079     *
080     * @return Returns a list of fields to exclude from serialization
081     */
082    default Set<String> toMapFieldExclusions() {
083        return Collections.emptySet();
084    }
085}