View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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       * Connector facade wrapped instance.
69       */
70      private final ConnectorFacade connector;
71  
72      /**
73       * Active connector instance.
74       */
75      private final ConnInstance connInstance;
76  
77      private final AsyncConnectorFacade asyncFacade;
78  
79      /**
80       * Use the passed connector instance to build a ConnectorFacade that will be used to make all wrapped calls.
81       *
82       * @param connInstance the connector instance
83       * @param asyncFacade the async connectot facade
84       * @see ConnectorInfo
85       * @see APIConfiguration
86       * @see ConfigurationProperties
87       * @see ConnectorFacade
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          // create default configuration
98          APIConfiguration apiConfig = info.createDefaultAPIConfiguration();
99          if (connInstance.getDisplayName() != null) {
100             apiConfig.setInstanceName(connInstance.getDisplayName());
101         }
102         // enable filtered results handler in validation mode
103         apiConfig.getResultsHandlerConfiguration().setFilteredResultsHandlerInValidationMode(true);
104 
105         // set connector configuration according to conninstance's
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         // set pooling configuration (if supported) according to conninstance's
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         // gets new connector, with the given configuration
124         connector = ConnectorFacadeFactory.getInstance().newInstance(apiConfig);
125 
126         // make sure we have set up the Configuration properly
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 }