1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.provisioning.java;
20
21 import java.io.File;
22 import java.net.URI;
23 import java.util.List;
24 import java.util.Set;
25 import java.util.concurrent.Future;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicReference;
28 import org.apache.syncope.common.lib.types.ConnectorCapability;
29 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
30 import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
31 import org.apache.syncope.core.provisioning.api.Connector;
32 import org.apache.syncope.core.provisioning.api.TimeoutException;
33 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
34 import org.apache.syncope.core.provisioning.api.utils.ConnPoolConfUtils;
35 import org.apache.syncope.core.spring.ApplicationContextProvider;
36 import org.identityconnectors.common.CollectionUtil;
37 import org.identityconnectors.common.security.GuardedByteArray;
38 import org.identityconnectors.common.security.GuardedString;
39 import org.identityconnectors.framework.api.APIConfiguration;
40 import org.identityconnectors.framework.api.ConfigurationProperties;
41 import org.identityconnectors.framework.api.ConnectorFacade;
42 import org.identityconnectors.framework.api.ConnectorFacadeFactory;
43 import org.identityconnectors.framework.api.ConnectorInfo;
44 import org.identityconnectors.framework.common.objects.Attribute;
45 import org.identityconnectors.framework.common.objects.AttributeDelta;
46 import org.identityconnectors.framework.common.objects.ConnectorObject;
47 import org.identityconnectors.framework.common.objects.ObjectClass;
48 import org.identityconnectors.framework.common.objects.ObjectClassInfo;
49 import org.identityconnectors.framework.common.objects.OperationOptions;
50 import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
51 import org.identityconnectors.framework.common.objects.SearchResult;
52 import org.identityconnectors.framework.common.objects.SyncResultsHandler;
53 import org.identityconnectors.framework.common.objects.SyncToken;
54 import org.identityconnectors.framework.common.objects.Uid;
55 import org.identityconnectors.framework.common.objects.filter.Filter;
56 import org.identityconnectors.framework.spi.SearchResultsHandler;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.util.ClassUtils;
60
61 public class ConnectorFacadeProxy implements Connector {
62
63 private static final Logger LOG = LoggerFactory.getLogger(ConnectorFacadeProxy.class);
64
65 private static final Integer DEFAULT_PAGE_SIZE = 100;
66
67
68
69
70 private final ConnectorFacade connector;
71
72
73
74
75 private final ConnInstance connInstance;
76
77 private final AsyncConnectorFacade asyncFacade;
78
79
80
81
82
83
84
85
86
87
88
89 public ConnectorFacadeProxy(final ConnInstance connInstance, final AsyncConnectorFacade asyncFacade) {
90 this.connInstance = connInstance;
91 this.asyncFacade = asyncFacade;
92
93 ConnIdBundleManager connIdBundleManager =
94 ApplicationContextProvider.getBeanFactory().getBean(ConnIdBundleManager.class);
95 ConnectorInfo info = connIdBundleManager.getConnectorInfo(connInstance).getRight();
96
97
98 APIConfiguration apiConfig = info.createDefaultAPIConfiguration();
99 if (connInstance.getDisplayName() != null) {
100 apiConfig.setInstanceName(connInstance.getDisplayName());
101 }
102
103 apiConfig.getResultsHandlerConfiguration().setFilteredResultsHandlerInValidationMode(true);
104
105
106 ConfigurationProperties properties = apiConfig.getConfigurationProperties();
107 connInstance.getConf().stream().
108 filter(property -> !CollectionUtil.isEmpty(property.getValues())).
109 forEach(property -> properties.setPropertyValue(
110 property.getSchema().getName(),
111 getPropertyValue(property.getSchema().getType(), property.getValues())));
112
113
114 if (connInstance.getPoolConf() != null) {
115 if (apiConfig.isConnectorPoolingSupported()) {
116 ConnPoolConfUtils.updateObjectPoolConfiguration(
117 apiConfig.getConnectorPoolConfiguration(), connInstance.getPoolConf());
118 } else {
119 LOG.warn("Connector pooling not supported for {}", info);
120 }
121 }
122
123
124 connector = ConnectorFacadeFactory.getInstance().newInstance(apiConfig);
125
126
127 connector.validate();
128 }
129
130 @Override
131 public Uid authenticate(final String username, final String password, final OperationOptions options) {
132 Uid result = null;
133
134 if (connInstance.getCapabilities().contains(ConnectorCapability.AUTHENTICATE)) {
135 Future<Uid> future = asyncFacade.authenticate(
136 connector, username, new GuardedString(password.toCharArray()), options);
137 try {
138 result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
139 } catch (java.util.concurrent.TimeoutException e) {
140 future.cancel(true);
141 throw new TimeoutException("Request timeout");
142 } catch (Exception e) {
143 LOG.error("Connector request execution failure", e);
144 if (e.getCause() instanceof RuntimeException) {
145 throw (RuntimeException) e.getCause();
146 } else {
147 throw new RuntimeException(e.getCause());
148 }
149 }
150 } else {
151 LOG.info("Authenticate was attempted, although the connector only has these capabilities: {}. No action.",
152 connInstance.getCapabilities());
153 }
154
155 return result;
156 }
157
158 @Override
159 public Uid create(
160 final ObjectClass objectClass,
161 final Set<Attribute> attrs,
162 final OperationOptions options,
163 final AtomicReference<Boolean> propagationAttempted) {
164
165 Uid result = null;
166
167 if (connInstance.getCapabilities().contains(ConnectorCapability.CREATE)) {
168 propagationAttempted.set(true);
169
170 Future<Uid> future = asyncFacade.create(connector, objectClass, attrs, options);
171 try {
172 result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
173 } catch (java.util.concurrent.TimeoutException e) {
174 future.cancel(true);
175 throw new TimeoutException("Request timeout");
176 } catch (Exception e) {
177 LOG.error("Connector request execution failure", e);
178 if (e.getCause() instanceof RuntimeException) {
179 throw (RuntimeException) e.getCause();
180 } else {
181 throw new RuntimeException(e.getCause());
182 }
183 }
184 } else {
185 LOG.info("Create was attempted, although the connector only has these capabilities: {}. No action.",
186 connInstance.getCapabilities());
187 }
188
189 return result;
190 }
191
192 @Override
193 public Uid update(
194 final ObjectClass objectClass,
195 final Uid uid,
196 final Set<Attribute> attrs,
197 final OperationOptions options,
198 final AtomicReference<Boolean> propagationAttempted) {
199
200 Uid result = null;
201
202 if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
203 propagationAttempted.set(true);
204
205 Future<Uid> future = asyncFacade.update(connector, objectClass, uid, attrs, options);
206
207 try {
208 result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
209 } catch (java.util.concurrent.TimeoutException e) {
210 future.cancel(true);
211 throw new TimeoutException("Request timeout");
212 } catch (Exception e) {
213 LOG.error("Connector request execution failure", e);
214 if (e.getCause() instanceof RuntimeException) {
215 throw (RuntimeException) e.getCause();
216 } else {
217 throw new RuntimeException(e.getCause());
218 }
219 }
220 } else {
221 LOG.info("Update for {} was attempted, although the "
222 + "connector only has these capabilities: {}. No action.",
223 uid.getUidValue(), connInstance.getCapabilities());
224 }
225
226 return result;
227 }
228
229 @Override
230 public Set<AttributeDelta> updateDelta(
231 final ObjectClass objectClass,
232 final Uid uid,
233 final Set<AttributeDelta> modifications,
234 final OperationOptions options,
235 final AtomicReference<Boolean> propagationAttempted) {
236
237 Set<AttributeDelta> result = null;
238
239 if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE_DELTA)) {
240 propagationAttempted.set(true);
241
242 Future<Set<AttributeDelta>> future =
243 asyncFacade.updateDelta(connector, objectClass, uid, modifications, options);
244
245 try {
246 result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
247 } catch (java.util.concurrent.TimeoutException e) {
248 future.cancel(true);
249 throw new TimeoutException("Request timeout");
250 } catch (Exception e) {
251 LOG.error("Connector request execution failure", e);
252 if (e.getCause() instanceof RuntimeException) {
253 throw (RuntimeException) e.getCause();
254 } else {
255 throw new RuntimeException(e.getCause());
256 }
257 }
258 } else {
259 LOG.info("UpdateDelta for {} was attempted, although the "
260 + "connector only has these capabilities: {}. No action.",
261 uid.getUidValue(), connInstance.getCapabilities());
262 }
263
264 return result;
265 }
266
267 @Override
268 public void delete(
269 final ObjectClass objectClass,
270 final Uid uid,
271 final OperationOptions options,
272 final AtomicReference<Boolean> propagationAttempted) {
273
274 if (connInstance.getCapabilities().contains(ConnectorCapability.DELETE)) {
275 propagationAttempted.set(true);
276
277 Future<Uid> future = asyncFacade.delete(connector, objectClass, uid, options);
278
279 try {
280 future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
281 } catch (java.util.concurrent.TimeoutException e) {
282 future.cancel(true);
283 throw new TimeoutException("Request timeout");
284 } catch (Exception e) {
285 LOG.error("Connector request execution failure", e);
286 if (e.getCause() instanceof RuntimeException) {
287 throw (RuntimeException) e.getCause();
288 } else {
289 throw new RuntimeException(e.getCause());
290 }
291 }
292 } else {
293 LOG.info("Delete for {} was attempted, although the connector only has these capabilities: {}. No action.",
294 uid.getUidValue(), connInstance.getCapabilities());
295 }
296 }
297
298 @Override
299 public void sync(final ObjectClass objectClass, final SyncToken token, final SyncResultsHandler handler,
300 final OperationOptions options) {
301
302 if (connInstance.getCapabilities().contains(ConnectorCapability.SYNC)) {
303 connector.sync(objectClass, token, handler, options);
304 } else {
305 LOG.info("Sync was attempted, although the connector only has these capabilities: {}. No action.",
306 connInstance.getCapabilities());
307 }
308 }
309
310 @Override
311 public SyncToken getLatestSyncToken(final ObjectClass objectClass) {
312 SyncToken result = null;
313
314 if (connInstance.getCapabilities().contains(ConnectorCapability.SYNC)) {
315 Future<SyncToken> future = asyncFacade.getLatestSyncToken(connector, objectClass);
316
317 try {
318 result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
319 } catch (java.util.concurrent.TimeoutException e) {
320 future.cancel(true);
321 throw new TimeoutException("Request timeout");
322 } catch (Exception e) {
323 LOG.error("Connector request execution failure", e);
324 if (e.getCause() instanceof RuntimeException) {
325 throw (RuntimeException) e.getCause();
326 } else {
327 throw new RuntimeException(e.getCause());
328 }
329 }
330 } else {
331 LOG.info("getLatestSyncToken was attempted, although the "
332 + "connector only has these capabilities: {}. No action.", connInstance.getCapabilities());
333 }
334
335 return result;
336 }
337
338 @Override
339 public void fullReconciliation(
340 final ObjectClass objectClass,
341 final SyncResultsHandler handler,
342 final OperationOptions options) {
343
344 Connector.super.fullReconciliation(objectClass, handler, options);
345 }
346
347 @Override
348 public void filteredReconciliation(
349 final ObjectClass objectClass,
350 final ReconFilterBuilder filterBuilder,
351 final SyncResultsHandler handler,
352 final OperationOptions options) {
353
354 Connector.super.filteredReconciliation(objectClass, filterBuilder, handler, options);
355 }
356
357 @Override
358 public Set<ObjectClassInfo> getObjectClassInfo() {
359 Future<Set<ObjectClassInfo>> future = asyncFacade.getObjectClassInfo(connector);
360 try {
361 return future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
362 } catch (java.util.concurrent.TimeoutException e) {
363 future.cancel(true);
364 throw new TimeoutException("Request timeout");
365 } catch (Exception e) {
366 LOG.error("Connector request execution failure", e);
367 if (e.getCause() instanceof RuntimeException) {
368 throw (RuntimeException) e.getCause();
369 } else {
370 throw new RuntimeException(e.getCause());
371 }
372 }
373 }
374
375 @Override
376 public void validate() {
377 Future<String> future = asyncFacade.test(connector);
378 try {
379 future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
380 } catch (java.util.concurrent.TimeoutException e) {
381 future.cancel(true);
382 throw new TimeoutException("Request timeout");
383 } catch (Exception e) {
384 LOG.error("Connector request execution failure", e);
385 if (e.getCause() instanceof RuntimeException) {
386 throw (RuntimeException) e.getCause();
387 } else {
388 throw new RuntimeException(e.getCause());
389 }
390 }
391 }
392
393 @Override
394 public void test() {
395 Future<String> future = asyncFacade.test(connector);
396 try {
397 future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
398 } catch (java.util.concurrent.TimeoutException e) {
399 future.cancel(true);
400 throw new TimeoutException("Request timeout");
401 } catch (Exception e) {
402 LOG.error("Connector request execution failure", e);
403 if (e.getCause() instanceof RuntimeException) {
404 throw (RuntimeException) e.getCause();
405 } else {
406 throw new RuntimeException(e.getCause());
407 }
408 }
409 }
410
411 @Override
412 public ConnectorObject getObject(
413 final ObjectClass objectClass,
414 final Attribute connObjectKey,
415 final boolean ignoreCaseMatch,
416 final OperationOptions options) {
417
418 Future<ConnectorObject> future = null;
419
420 if (connInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) {
421 future = asyncFacade.getObject(connector, objectClass, connObjectKey, ignoreCaseMatch, options);
422 } else {
423 LOG.info("Search was attempted, although the connector only has these capabilities: {}. No action.",
424 connInstance.getCapabilities());
425 }
426
427 try {
428 return future == null ? null : future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
429 } catch (java.util.concurrent.TimeoutException e) {
430 future.cancel(true);
431 throw new TimeoutException("Request timeout");
432 } catch (Exception e) {
433 LOG.error("Connector request execution failure", e);
434 if (e.getCause() instanceof RuntimeException) {
435 throw (RuntimeException) e.getCause();
436 } else {
437 throw new RuntimeException(e.getCause());
438 }
439 }
440 }
441
442 @Override
443 public SearchResult search(
444 final ObjectClass objectClass,
445 final Filter filter,
446 final SearchResultsHandler handler,
447 final OperationOptions options) {
448
449 SearchResult result = null;
450
451 if (connInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) {
452 if (options.getPageSize() == null && options.getPagedResultsCookie() == null) {
453 OperationOptionsBuilder builder = new OperationOptionsBuilder(options).
454 setPageSize(DEFAULT_PAGE_SIZE).setPagedResultsOffset(-1);
455
456 final String[] cookies = new String[] { null };
457 do {
458 if (cookies[0] != null) {
459 builder.setPagedResultsCookie(cookies[0]);
460 }
461
462 result = connector.search(objectClass, filter, new SearchResultsHandler() {
463
464 @Override
465 public void handleResult(final SearchResult result) {
466 handler.handleResult(result);
467 cookies[0] = result.getPagedResultsCookie();
468 }
469
470 @Override
471 public boolean handle(final ConnectorObject connectorObject) {
472 return handler.handle(connectorObject);
473 }
474 }, builder.build());
475 } while (cookies[0] != null);
476 } else {
477 result = connector.search(objectClass, filter, handler, options);
478 }
479 } else {
480 LOG.info("Search was attempted, although the connector only has these capabilities: {}. No action.",
481 connInstance.getCapabilities());
482 }
483
484 return result;
485 }
486
487 @Override
488 public void dispose() {
489 connector.dispose();
490 }
491
492 @Override
493 public ConnInstance getConnInstance() {
494 return connInstance;
495 }
496
497 private static Object getPropertyValue(final String propType, final List<?> values) {
498 Object value = null;
499
500 try {
501 Class<?> propertySchemaClass = ClassUtils.forName(propType, ClassUtils.getDefaultClassLoader());
502
503 if (GuardedString.class.equals(propertySchemaClass)) {
504 value = new GuardedString(values.get(0).toString().toCharArray());
505 } else if (GuardedByteArray.class.equals(propertySchemaClass)) {
506 value = new GuardedByteArray((byte[]) values.get(0));
507 } else if (Character.class.equals(propertySchemaClass) || Character.TYPE.equals(propertySchemaClass)) {
508 value = values.get(0) == null || values.get(0).toString().isEmpty()
509 ? null : values.get(0).toString().charAt(0);
510 } else if (Integer.class.equals(propertySchemaClass) || Integer.TYPE.equals(propertySchemaClass)) {
511 value = Integer.parseInt(values.get(0).toString());
512 } else if (Long.class.equals(propertySchemaClass) || Long.TYPE.equals(propertySchemaClass)) {
513 value = Long.parseLong(values.get(0).toString());
514 } else if (Float.class.equals(propertySchemaClass) || Float.TYPE.equals(propertySchemaClass)) {
515 value = Float.parseFloat(values.get(0).toString());
516 } else if (Double.class.equals(propertySchemaClass) || Double.TYPE.equals(propertySchemaClass)) {
517 value = Double.parseDouble(values.get(0).toString());
518 } else if (Boolean.class.equals(propertySchemaClass) || Boolean.TYPE.equals(propertySchemaClass)) {
519 value = Boolean.parseBoolean(values.get(0).toString());
520 } else if (URI.class.equals(propertySchemaClass)) {
521 value = URI.create(values.get(0).toString());
522 } else if (File.class.equals(propertySchemaClass)) {
523 value = new File(values.get(0).toString());
524 } else if (String[].class.equals(propertySchemaClass)) {
525 value = values.toArray(String[]::new);
526 } else {
527 value = values.get(0) == null ? null : values.get(0).toString();
528 }
529 } catch (Exception e) {
530 LOG.error("Invalid ConnConfProperty specified: {} {}", propType, values, e);
531 }
532
533 return value;
534 }
535
536 @Override
537 public String toString() {
538 return "ConnectorFacadeProxy{"
539 + "connector=" + connector + '\n' + "capabitilies=" + connInstance.getCapabilities() + '}';
540 }
541 }