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.pushpull;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Optional;
27  import java.util.Set;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.stream.Stream;
30  import org.apache.commons.lang3.ArrayUtils;
31  import org.apache.syncope.common.lib.to.Item;
32  import org.apache.syncope.common.lib.to.Provision;
33  import org.apache.syncope.common.lib.types.AnyTypeKind;
34  import org.apache.syncope.core.persistence.api.dao.UserDAO;
35  import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
36  import org.apache.syncope.core.persistence.api.entity.Any;
37  import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
38  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
39  import org.apache.syncope.core.persistence.api.entity.Implementation;
40  import org.apache.syncope.core.persistence.api.entity.VirSchema;
41  import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
42  import org.apache.syncope.core.provisioning.api.Connector;
43  import org.apache.syncope.core.provisioning.api.MappingManager;
44  import org.apache.syncope.core.provisioning.api.TimeoutException;
45  import org.apache.syncope.core.provisioning.api.VirAttrHandler;
46  import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
47  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
48  import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRule;
49  import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
50  import org.apache.syncope.core.spring.implementation.ImplementationManager;
51  import org.identityconnectors.framework.common.objects.AttributeBuilder;
52  import org.identityconnectors.framework.common.objects.ConnectorObject;
53  import org.identityconnectors.framework.common.objects.ObjectClass;
54  import org.identityconnectors.framework.common.objects.SearchResult;
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.transaction.annotation.Transactional;
60  
61  public class OutboundMatcher {
62  
63      protected static final Logger LOG = LoggerFactory.getLogger(OutboundMatcher.class);
64  
65      protected final MappingManager mappingManager;
66  
67      protected final UserDAO userDAO;
68  
69      protected final AnyUtilsFactory anyUtilsFactory;
70  
71      protected final VirSchemaDAO virSchemaDAO;
72  
73      protected final VirAttrHandler virAttrHandler;
74  
75      protected final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
76  
77      protected final Map<String, PushCorrelationRule> perContextPushCorrelationRules = new ConcurrentHashMap<>();
78  
79      public OutboundMatcher(
80              final MappingManager mappingManager,
81              final UserDAO userDAO,
82              final AnyUtilsFactory anyUtilsFactory,
83              final VirSchemaDAO virSchemaDAO,
84              final VirAttrHandler virAttrHandler) {
85  
86          this.mappingManager = mappingManager;
87          this.userDAO = userDAO;
88          this.anyUtilsFactory = anyUtilsFactory;
89          this.virSchemaDAO = virSchemaDAO;
90          this.virAttrHandler = virAttrHandler;
91      }
92  
93      protected Optional<PushCorrelationRule> rule(final ExternalResource resource, final Provision provision) {
94          Optional<? extends PushCorrelationRuleEntity> correlationRule = resource.getPushPolicy() == null
95                  ? Optional.empty()
96                  : resource.getPushPolicy().getCorrelationRule(provision.getAnyType());
97  
98          Optional<PushCorrelationRule> rule = Optional.empty();
99          if (correlationRule.isPresent()) {
100             Implementation impl = correlationRule.get().getImplementation();
101             try {
102                 rule = ImplementationManager.buildPushCorrelationRule(
103                         impl,
104                         () -> perContextPushCorrelationRules.get(impl.getKey()),
105                         instance -> perContextPushCorrelationRules.put(impl.getKey(), instance));
106             } catch (Exception e) {
107                 LOG.error("While building {}", impl, e);
108             }
109         }
110 
111         return rule;
112     }
113 
114     public String getFIQL(
115             final ConnectorObject connectorObject,
116             final ExternalResource resource,
117             final Provision provision) {
118 
119         return rule(resource, provision).
120                 map(rule -> rule.getFIQL(connectorObject, provision)).
121                 orElseGet(() -> PushCorrelationRule.DEFAULT_FIQL_BUILDER.apply(connectorObject, provision));
122     }
123 
124     public List<ConnectorObject> match(
125             final PropagationTaskInfo taskInfo,
126             final Connector connector,
127             final Provision provision,
128             final List<PropagationActions> actions,
129             final String connObjectKeyValue) {
130 
131         Optional<PushCorrelationRule> rule = rule(taskInfo.getResource(), provision);
132 
133         boolean isLinkedAccount = taskInfo.getAnyTypeKind() == AnyTypeKind.USER
134                 && userDAO.linkedAccountExists(taskInfo.getEntityKey(), connObjectKeyValue);
135         Any<?> any = null;
136         if (!isLinkedAccount) {
137             any = anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind()).dao().find(taskInfo.getEntityKey());
138         }
139 
140         Set<String> moreAttrsToGet = new HashSet<>();
141         actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(Optional.of(taskInfo), provision)));
142 
143         List<ConnectorObject> result = new ArrayList<>();
144         try {
145             if (any != null && rule.isPresent()) {
146                 result.addAll(matchByCorrelationRule(
147                         connector,
148                         rule.get().getFilter(any, taskInfo.getResource(), provision),
149                         taskInfo.getResource(),
150                         provision,
151                         Optional.of(moreAttrsToGet.toArray(String[]::new)),
152                         Optional.empty()));
153             } else {
154                 MappingUtils.getConnObjectKeyItem(provision).flatMap(connObjectKeyItem -> matchByConnObjectKeyValue(
155                         connector,
156                         connObjectKeyItem,
157                         connObjectKeyValue,
158                         taskInfo.getResource(),
159                         provision,
160                         Optional.of(moreAttrsToGet.toArray(String[]::new)),
161                         Optional.empty())).ifPresent(result::add);
162             }
163         } catch (RuntimeException e) {
164             LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
165         }
166 
167         if (any != null && result.size() == 1) {
168             virAttrHandler.setValues(any, result.get(0));
169         }
170 
171         return result;
172     }
173 
174     protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
175         List<PropagationActions> result = new ArrayList<>();
176 
177         resource.getPropagationActions().forEach(impl -> {
178             try {
179                 result.add(ImplementationManager.build(
180                         impl,
181                         () -> perContextActions.get(impl.getKey()),
182                         instance -> perContextActions.put(impl.getKey(), instance)));
183             } catch (Exception e) {
184                 LOG.error("While building {}", impl, e);
185             }
186         });
187 
188         return result;
189     }
190 
191     @Transactional(readOnly = true)
192     public List<ConnectorObject> match(
193             final Connector connector,
194             final Any<?> any,
195             final ExternalResource resource,
196             final Provision provision,
197             final Optional<String[]> moreAttrsToGet,
198             final Item... linkingItems) {
199 
200         Stream<String> matgFromPropagationActions = getPropagationActions(resource).stream().
201                 flatMap(a -> a.moreAttrsToGet(Optional.empty(), provision).stream());
202         Optional<String[]> effectiveMATG = Optional.of(Stream.concat(
203                 moreAttrsToGet.stream().flatMap(Stream::of),
204                 matgFromPropagationActions).toArray(String[]::new));
205 
206         Optional<PushCorrelationRule> rule = rule(resource, provision);
207 
208         List<ConnectorObject> result = new ArrayList<>();
209         try {
210             if (rule.isPresent()) {
211                 result.addAll(matchByCorrelationRule(
212                         connector,
213                         rule.get().getFilter(any, resource, provision),
214                         resource,
215                         provision,
216                         effectiveMATG,
217                         ArrayUtils.isEmpty(linkingItems)
218                         ? Optional.empty() : Optional.of(List.of(linkingItems))));
219             } else {
220                 Optional<Item> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
221                 Optional<String> connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, resource, provision);
222 
223                 if (connObjectKeyItem.isPresent() && connObjectKeyValue.isPresent()) {
224                     matchByConnObjectKeyValue(
225                             connector,
226                             connObjectKeyItem.get(),
227                             connObjectKeyValue.get(),
228                             resource,
229                             provision,
230                             effectiveMATG,
231                             ArrayUtils.isEmpty(linkingItems)
232                             ? Optional.empty() : Optional.of(List.of(linkingItems))).
233                             ifPresent(result::add);
234                 }
235             }
236         } catch (RuntimeException e) {
237             LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
238         }
239 
240         if (any != null && result.size() == 1) {
241             virAttrHandler.setValues(any, result.get(0));
242         }
243 
244         return result;
245     }
246 
247     protected List<ConnectorObject> matchByCorrelationRule(
248             final Connector connector,
249             final Filter filter,
250             final ExternalResource resource,
251             final Provision provision,
252             final Optional<String[]> moreAttrsToGet,
253             final Optional<Collection<Item>> linkingItems) {
254 
255         Stream<Item> items = Stream.concat(
256                 provision.getMapping().getItems().stream(),
257                 linkingItems.isPresent()
258                 ? linkingItems.get().stream()
259                 : virSchemaDAO.find(resource.getKey(), provision.getAnyType()).stream().
260                         map(VirSchema::asLinkingMappingItem));
261 
262         List<ConnectorObject> objs = new ArrayList<>();
263         try {
264             connector.search(new ObjectClass(provision.getObjectClass()), filter, new SearchResultsHandler() {
265 
266                 @Override
267                 public void handleResult(final SearchResult result) {
268                     // nothing to do
269                 }
270 
271                 @Override
272                 public boolean handle(final ConnectorObject connectorObject) {
273                     objs.add(connectorObject);
274                     return true;
275                 }
276             }, MappingUtils.buildOperationOptions(items, moreAttrsToGet.orElse(null)));
277         } catch (TimeoutException toe) {
278             LOG.debug("Request timeout", toe);
279             throw toe;
280         } catch (RuntimeException ignore) {
281             LOG.debug("Unexpected exception", ignore);
282         }
283 
284         return objs;
285     }
286 
287     @Transactional(readOnly = true)
288     public Optional<ConnectorObject> matchByConnObjectKeyValue(
289             final Connector connector,
290             final Item connObjectKeyItem,
291             final String connObjectKeyValue,
292             final ExternalResource resource,
293             final Provision provision,
294             final Optional<String[]> moreAttrsToGet,
295             final Optional<Collection<Item>> linkingItems) {
296 
297         Stream<Item> items = Stream.concat(
298                 provision.getMapping().getItems().stream(),
299                 linkingItems.isPresent()
300                 ? linkingItems.get().stream()
301                 : virSchemaDAO.find(resource.getKey(), provision.getAnyType()).stream().
302                         map(VirSchema::asLinkingMappingItem));
303 
304         ConnectorObject obj = null;
305         try {
306             obj = connector.getObject(
307                     new ObjectClass(provision.getObjectClass()),
308                     AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue),
309                     provision.isIgnoreCaseMatch(),
310                     MappingUtils.buildOperationOptions(items, moreAttrsToGet.orElse(null)));
311         } catch (TimeoutException toe) {
312             LOG.debug("Request timeout", toe);
313             throw toe;
314         } catch (RuntimeException ignore) {
315             LOG.debug("While resolving {}", connObjectKeyValue, ignore);
316         }
317 
318         return Optional.ofNullable(obj);
319     }
320 }