001package com.identityworksllc.iiq.common;
002
003import sailpoint.api.SailPointContext;
004import sailpoint.api.SailPointFactory;
005import sailpoint.object.CompoundFilter;
006import sailpoint.object.Configuration;
007import sailpoint.object.Custom;
008import sailpoint.object.Filter;
009import sailpoint.object.IdentitySelector;
010import sailpoint.object.Reference;
011import sailpoint.object.SailPointObject;
012import sailpoint.object.Script;
013import sailpoint.plugin.PluginsCache;
014import sailpoint.server.Environment;
015import sailpoint.tools.GeneralException;
016import sailpoint.tools.Util;
017import sailpoint.tools.xml.AbstractXmlObject;
018
019import java.util.Collection;
020import java.util.Date;
021import java.util.Map;
022
023/**
024 * An extension of ObjectMapper to handle SailPointObject types
025 *
026 * @param <T> The mapper output type
027 */
028public class SailpointObjectMapper<T> extends ObjectMapper<T> {
029
030    /**
031     * Overrides the mapping from Class to class name to include the plugin cache
032     * version. When a plugin is redeployed, this value will change, invalidating
033     * the cached objects. This should prevent weirdness where the new version
034     * of a class isn't strictly compatible with the old version.
035     */
036    protected static class SailPointTypeNamer extends DefaultTypeNamer {
037        @Override
038        public String getTypeName(Class<?> type) {
039            return super.getTypeName(type) + ":" + getPluginVersion();
040        }
041    }
042
043    /**
044     * Gets the current plugin version, or "NA" if plugins ar not enabled
045     * @return The plugin version number
046     */
047    private static String getPluginVersion() {
048        String version = "NA";
049        if (Environment.getEnvironment() != null) {
050            PluginsCache cache = Environment.getEnvironment().getPluginsCache();
051            if (cache != null) {
052                version = String.valueOf(cache.getVersion());
053            }
054        }
055        return version;
056    }
057
058    /**
059     * Returns the object mapped from the given Configuration
060     * @param context The context to use to get the Configuration object
061     * @param configName The configuration name
062     * @param type the output type
063     * @param <T> The output type
064     * @return An instance of the mapped configuration object
065     * @throws GeneralException if any failures occur
066     */
067    public static <T> T fromConfiguration(SailPointContext context, String configName, Class<T> type) throws GeneralException, ObjectMapperException {
068        return get(type).decode(context.getObject(Configuration.class, configName));
069    }
070
071    /**
072     * Transform the object mapper exception into a GeneralException as needed
073     * @param e The exception to unwrap or wrap
074     * @return an appropriate GeneralException
075     */
076    public static GeneralException unwrap(ObjectMapper.ObjectMapperException e) {
077        if (e.getCause() != null && e.getCause() instanceof GeneralException) {
078            return (GeneralException) e.getCause();
079        } else {
080            return new GeneralException(e);
081        }
082    }
083
084    /**
085     * Basic constructor. You should prefer {@link ObjectMapper#get(Class)} to this.
086     *
087     * @param targetClass the target class
088     */
089    public SailpointObjectMapper(Class<T> targetClass) {
090        super(targetClass);
091    }
092
093    /**
094     * Converts the given object to the expected type. If the input is null, a null
095     * will be returned. If the input is already compatible with the expected type,
096     * the existing object will be returned. If the input cannot be converted, an
097     * exception will be thrown.
098     *
099     * @param value The input value
100     * @param expectedType The expected type of the input
101     * @return The converted object
102     */
103    public Object convertObject(Object value, Class<?> expectedType) throws ObjectMapperException {
104        if (value == null) {
105            return null;
106        }
107        if (expectedType.isAssignableFrom(value.getClass())) {
108            return value;
109        }
110        if (expectedType.equals(Boolean.TYPE) || expectedType.equals(Boolean.class)) {
111            value = Util.otob(value);
112        } else if (expectedType.equals(String.class)) {
113            value = Util.otoa(value);
114        } else if (expectedType.equals(Long.TYPE) || expectedType.equals(Long.class)) {
115            value = Long.parseLong(Util.otoa(value));
116        } else if (expectedType.equals(Integer.TYPE) || expectedType.equals(Integer.class)) {
117            value = Integer.parseInt(Util.otoa(value));
118        } else if (expectedType.equals(Date.class)) {
119            if (value instanceof Long) {
120                value = new Date((Long) value);
121            } else if (value instanceof java.sql.Date) {
122                value = new Date(((java.sql.Date)value).getTime());
123            } else {
124                throw new IllegalArgumentException("Cannot convert " + value.getClass().getName() + " to " + expectedType.getName());
125            }
126        } else if (Collection.class.isAssignableFrom(expectedType)) {
127            value = Util.otol(value);
128        } else if (IdentitySelector.class.isAssignableFrom(expectedType)) {
129            if (value instanceof String) {
130                IdentitySelector identitySelector = new IdentitySelector();
131                CompoundFilter compoundFilter = new CompoundFilter();
132                compoundFilter.setFilter(Filter.compile(Util.otoa(value)));
133                identitySelector.setFilter(compoundFilter);
134                value = identitySelector;
135            }
136        } else if (Filter.class.isAssignableFrom(expectedType)) {
137            if (value instanceof String) {
138                value = Filter.compile(otoa(value));
139            }
140        } else if (Script.class.isAssignableFrom(expectedType)) {
141            Script script = Utilities.getAsScript(value);
142            if (script == null) {
143                throw new IllegalArgumentException("Cannot convert " + value.getClass().getName() + " to " + expectedType.getName());
144            }
145            value = script;
146        } else if (SailPointObject.class.isAssignableFrom(expectedType)) {
147            try {
148                @SuppressWarnings("unchecked")
149                Class<SailPointObject> spoClass = (Class<SailPointObject>) expectedType;
150                if (value instanceof String) {
151                    String string = (String) value;
152                    if (string.startsWith("<")) {
153                        // Parse the string as XML if it looks like XML
154                        value = AbstractXmlObject.parseXml(SailPointFactory.getCurrentContext(), string);
155                    } else {
156                        // Otherwise assume it's an identifier and look up the object
157                        value = SailPointFactory.getCurrentContext().getObject(spoClass, string);
158                    }
159                } else if (value instanceof Reference) {
160                    Reference ref = (Reference) value;
161                    value = ref.resolve(SailPointFactory.getCurrentContext());
162                } else {
163                    throw new IllegalArgumentException("Cannot convert " + value.getClass().getName() + " to " + expectedType.getName());
164                }
165            } catch(GeneralException e) {
166                throw new ObjectMapperException(e);
167            }
168        } else {
169            throw new IllegalArgumentException("Cannot convert " + value.getClass().getName() + " to " + expectedType.getName());
170        }
171        return value;
172    }
173
174
175    /**
176     * Decodes the given Custom object into an instance of the mapped type.
177     *
178     * @param configuration The Custom object to convert
179     * @return An object of the expected type
180     * @throws GeneralException if any failure occur
181     */
182    public T decode(Custom configuration) throws GeneralException, ObjectMapperException {
183        return decode(configuration, true);
184    }
185
186    /**
187     * Decodes the given Custom object into an instance of the mapped type.
188     *
189     * @param configuration The Custom object to convert
190     * @param cache If true, the cached value will be returned if possible
191     * @return An object of the expected type
192     * @throws GeneralException if any failure occur
193     */
194    public T decode(Custom configuration, boolean cache) throws GeneralException, ObjectMapperException {
195        Map<String, Object> input = null;
196        if (configuration != null) {
197            input = configuration.getAttributes();
198        }
199        return decode(input, cache);
200    }
201
202
203    /**
204     * Decodes the given Configuration object into an instance of the mapped type.
205     *
206     * @param configuration The Configuration object to convert
207     * @return An object of the expected type
208     * @throws GeneralException if any failure occur
209     */
210    public T decode(Configuration configuration) throws GeneralException, ObjectMapperException {
211        return decode(configuration, true);
212    }
213
214    /**
215     * Decodes the given Configuration object into an instance of the mapped type.
216     *
217     * @param configuration The Configuration object to convert
218     * @param cache If true, the cached value will be returned if possible
219     * @return An object of the expected type
220     * @throws GeneralException if any failure occur
221     */
222    public T decode(Configuration configuration, boolean cache) throws GeneralException, ObjectMapperException {
223        Map<String, Object> input = null;
224        if (configuration != null) {
225            input = configuration.getAttributes();
226        }
227        return decode(input, cache);
228    }
229
230
231}