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}