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}