001package com.identityworksllc.iiq.common.connector; 002 003import openconnector.ConnectorServices; 004import openconnector.ConnectorStateChangeListener; 005import org.apache.commons.logging.Log; 006import org.apache.commons.logging.LogFactory; 007import sailpoint.connector.*; 008import sailpoint.object.*; 009import sailpoint.tools.CloseableIterator; 010import sailpoint.tools.GeneralException; 011import sailpoint.tools.Util; 012 013import java.util.*; 014 015/** 016 * A connector that will forward 'read' and 'write' operations to different connectors, usually 017 * where reading from the source would be expensive or infeasible. Sort of the opposite of an 018 * IntegrationConfig. 019 * 020 * Optionally, getObject() can be forwarded to the 'write' connector. 021 */ 022public class DualDelegatingConnector extends AbstractConnector implements ConnectorStateChangeListener { 023 024 /** 025 * The log 026 */ 027 private final Log log; 028 029 /** 030 * The Connector to use for reading operations 031 */ 032 private Connector readConnector; 033 034 /** 035 * The Connector to use for writing operations 036 */ 037 private Connector writeConnector; 038 039 /** 040 * Constructs a new BaseDelegatingConnector of the given type 041 * @param application The application to use for the connector 042 */ 043 public DualDelegatingConnector(Application application) { 044 super(application, null); 045 log = LogFactory.getLog(this.getClass()); 046 } 047 048 @Override 049 public ProvisioningResult checkStatus(String id) throws ConnectorException, GeneralException { 050 return getWriteConnector().checkStatus(id); 051 } 052 053 @Override 054 public void destroy(Map<String, Object> optionsMap) throws ConnectorException { 055 getWriteConnector().destroy(optionsMap); 056 getReadConnector().destroy(optionsMap); 057 } 058 059 @Override 060 public Map<String, Object> discoverApplicationAttributes(Map<String, Object> options) throws ConnectorException { 061 return getWriteConnector().discoverApplicationAttributes(options); 062 } 063 064 @Override 065 public Schema discoverSchema(String objectType, Map<String, Object> options) throws ConnectorException { 066 return getReadConnector().discoverSchema(objectType, options); 067 } 068 069 @Override 070 @SuppressWarnings("rawtypes") 071 public Map doHealthCheck(Map<String, Object> options) throws ConnectorException, UnsupportedOperationException { 072 return getReadConnector().doHealthCheck(options); 073 } 074 075 @Override 076 @SuppressWarnings("deprecation") 077 public String getConnectorType() { 078 return getWriteConnector().getConnectorType(); 079 } 080 081 @Override 082 public List<AttributeDefinition> getDefaultAttributes() { 083 if (getWriteConnector() instanceof AbstractConnector) { 084 return ((AbstractConnector)getWriteConnector()).getDefaultAttributes(); 085 } 086 return super.getDefaultAttributes(); 087 } 088 089 @Override 090 public List<Schema> getDefaultSchemas() { 091 if (getWriteConnector() instanceof AbstractConnector) { 092 return ((AbstractConnector)getWriteConnector()).getDefaultSchemas(); 093 } 094 return super.getDefaultSchemas(); 095 } 096 097 @Override 098 public List<Partition> getIteratorPartitions(String objectType, int suggestedPartitionCount, Filter filter, Map<String, Object> ops) throws ConnectorException { 099 String realConnector = getObligatoryStringAttribute("delegate_read_Application"); 100 log.info("Deciding partitions with read connector: " + realConnector); 101 102 return getReadConnector().getIteratorPartitions(objectType, suggestedPartitionCount, filter, ops); 103 } 104 105 @Override 106 public ResourceObject getObject(String s, String s1, Map<String, Object> map) throws ConnectorException { 107 String whichConnectorForGetObject = getStringAttribute("delegate_read_GetObjectConnector"); 108 if (Util.isNullOrEmpty(whichConnectorForGetObject)) { 109 whichConnectorForGetObject = "read"; 110 } 111 112 if ("write".equalsIgnoreCase(whichConnectorForGetObject)) { 113 String realConnector = getObligatoryStringAttribute("delegate_write_ConnectorClass"); 114 log.info("Fetching single object with write connector: " + realConnector); 115 return getWriteConnector().getObject(s, s1, map); 116 } else if ("read".equalsIgnoreCase(whichConnectorForGetObject)) { 117 String realConnector = getObligatoryStringAttribute("delegate_read_Application"); 118 log.info("Fetching single object with read connector: " + realConnector); 119 return getReadConnector().getObject(s, s1, map); 120 } else { 121 throw new ConnectorException("Invalid value for delegate_read_GetObjectConnector: " + whichConnectorForGetObject); 122 } 123 } 124 125 private Connector getReadConnector() { 126 if (this.readConnector != null) { 127 return this.readConnector; 128 } 129 130 try { 131 String readApplicationName = getObligatoryStringAttribute("delegate_read_Application"); 132 Application readApplication = (Application) getConnectorServices().getObject(Application.class, readApplicationName); 133 this.readConnector = ConnectorFactory.getConnector(readApplication, null); 134 this.readConnector.setConnectorServices(getConnectorServices()); 135 } catch (Exception e) { 136 throw new IllegalArgumentException(e); 137 } 138 139 return readConnector; 140 } 141 142 @Override 143 public Schema getSchema(String objectType) throws SchemaNotDefinedException { 144 if (getWriteConnector() instanceof AbstractConnector) { 145 return ((AbstractConnector) getWriteConnector()).getSchema(objectType); 146 } else { 147 return null; 148 } 149 } 150 151 @Override 152 @SuppressWarnings("deprecation") 153 public List<Application.Feature> getSupportedFeatures() { 154 Set<Application.Feature> features = new HashSet<>(); 155 features.addAll(getReadConnector().getSupportedFeatures()); 156 features.addAll(getWriteConnector().getSupportedFeatures()); 157 return new ArrayList<>(features); 158 } 159 160 @Override 161 public String getSystemIdentity() { 162 return getWriteConnector().getSystemIdentity(); 163 } 164 165 private Connector getWriteConnector() { 166 if (this.writeConnector != null) { 167 return this.writeConnector; 168 } 169 170 try { 171 @SuppressWarnings("unchecked") 172 List<String> connectorClasspath = getStringListAttribute("connector-classpath"); 173 this.writeConnector = ConnectorClassLoaderWorkaround.getConnector(getObligatoryStringAttribute("delegate_write_ConnectorClass"), connectorClasspath); 174 if (this.writeConnector instanceof CollectorServices) { 175 CollectorServices collectorServices = (CollectorServices) writeConnector; 176 collectorServices.setAttributes(getAttributes()); 177 } 178 this.writeConnector.setApplication(this.getApplication()); 179 this.writeConnector.setTargetApplication(this.getTargetApplication()); 180 this.writeConnector.setConnectorServices(getConnectorServices()); 181 } catch (ConnectorException | GeneralException e) { 182 throw new IllegalArgumentException(e); 183 } 184 185 return this.writeConnector; 186 } 187 188 @Override 189 public CloseableIterator<ResourceObject> iterateObjects(String s, Filter filter, Map<String, Object> map) throws ConnectorException { 190 String realConnector = getObligatoryStringAttribute("delegate_read_Application"); 191 log.info("Iterating objects with read connector: " + realConnector); 192 return getReadConnector().iterateObjects(s, filter, map); 193 } 194 195 @Override 196 public CloseableIterator<ResourceObject> iterateObjects(Partition partition) throws ConnectorException { 197 String realConnector = getObligatoryStringAttribute("delegate_read_Application"); 198 log.info("Iterating objects with read connector: " + realConnector); 199 return getReadConnector().iterateObjects(partition); 200 } 201 202 @Override 203 public ProvisioningResult provision(ProvisioningPlan plan) throws ConnectorException, GeneralException { 204 String realConnector = getObligatoryStringAttribute("delegate_write_ConnectorClass"); 205 log.info("Provisioning with write connector: " + realConnector); 206 207 return getWriteConnector().provision(plan); 208 } 209 210 @Override 211 public void saveConnectorState() { 212 Connector rc = getWriteConnector(); 213 if (rc instanceof AbstractConnector) { 214 ((AbstractConnector) rc).saveConnectorState(); 215 } 216 } 217 218 @Override 219 public void saveConnectorState(Map<String, Object> stateMap) { 220 Connector rc = getWriteConnector(); 221 if (rc instanceof AbstractConnector) { 222 ((AbstractConnector) rc).saveConnectorState(stateMap); 223 } 224 } 225 226 @Override 227 public void setApplication(Application application) { 228 super.setApplication(application); 229 getWriteConnector().setApplication(application); 230 } 231 232 @Override 233 public void setConnectorServices(ConnectorServices connServices) { 234 super.setConnectorServices(connServices); 235 getReadConnector().setConnectorServices(connServices); 236 getWriteConnector().setConnectorServices(connServices); 237 } 238 239 @Override 240 public boolean shouldRetry(Exception ex, String error, ProvisioningResult result) { 241 boolean shouldRetry = super.shouldRetry(ex, error, result); 242 if (shouldRetry) { 243 return true; 244 } 245 Connector realConnector = getWriteConnector(); 246 if (realConnector instanceof AbstractConnector) { 247 return ((AbstractConnector)realConnector).shouldRetry(ex, error, result); 248 } 249 return false; 250 } 251 252 @Override 253 public boolean supportsFeature(Application.Feature feature) { 254 return getSupportedFeatures().contains(feature); 255 } 256 257 @Override 258 public boolean supportsPartitionedDeltaAggregation() { 259 return getReadConnector().supportsPartitionedDeltaAggregation(); 260 } 261 262 @Override 263 public void testConfiguration() throws ConnectorException { 264 if (getReadConnector() == null) { 265 throw new ConnectorException("Read connector is not set"); 266 } 267 if (getWriteConnector() == null) { 268 throw new ConnectorException("Write connector is not set"); 269 } 270 271 getReadConnector().testConfiguration(); 272 getWriteConnector().testConfiguration(); 273 } 274 275 @Override 276 public void updateConnectorState(Map<String, Object> map) { 277 if (getWriteConnector() instanceof ConnectorStateChangeListener) { 278 ((ConnectorStateChangeListener) getWriteConnector()).updateConnectorState(map); 279 } 280 if (getReadConnector() instanceof ConnectorStateChangeListener) { 281 ((ConnectorStateChangeListener) getReadConnector()).updateConnectorState(map); 282 } 283 } 284}