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.time.OffsetDateTime;
22  import java.util.List;
23  import java.util.Set;
24  import java.util.stream.Collectors;
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.commons.lang3.exception.ExceptionUtils;
27  import org.apache.syncope.common.lib.AnyOperations;
28  import org.apache.syncope.common.lib.request.AnyCR;
29  import org.apache.syncope.common.lib.request.AnyUR;
30  import org.apache.syncope.common.lib.request.StringPatchItem;
31  import org.apache.syncope.common.lib.to.AnyTO;
32  import org.apache.syncope.common.lib.to.Provision;
33  import org.apache.syncope.common.lib.to.ProvisioningReport;
34  import org.apache.syncope.common.lib.types.AnyTypeKind;
35  import org.apache.syncope.common.lib.types.AuditElements;
36  import org.apache.syncope.common.lib.types.AuditElements.Result;
37  import org.apache.syncope.common.lib.types.MatchType;
38  import org.apache.syncope.common.lib.types.MatchingRule;
39  import org.apache.syncope.common.lib.types.PatchOperation;
40  import org.apache.syncope.common.lib.types.PullMode;
41  import org.apache.syncope.common.lib.types.ResourceOperation;
42  import org.apache.syncope.common.lib.types.TaskType;
43  import org.apache.syncope.common.lib.types.UnmatchingRule;
44  import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
45  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
46  import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
47  import org.apache.syncope.core.persistence.api.dao.TaskDAO;
48  import org.apache.syncope.core.persistence.api.dao.UserDAO;
49  import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
50  import org.apache.syncope.core.persistence.api.entity.EntityFactory;
51  import org.apache.syncope.core.persistence.api.entity.Remediation;
52  import org.apache.syncope.core.persistence.api.entity.task.PullTask;
53  import org.apache.syncope.core.provisioning.api.AuditManager;
54  import org.apache.syncope.core.provisioning.api.PropagationByResource;
55  import org.apache.syncope.core.provisioning.api.ProvisioningManager;
56  import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
57  import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheKey;
58  import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue;
59  import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
60  import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
61  import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
62  import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
63  import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
64  import org.apache.syncope.core.provisioning.api.rules.PullMatch;
65  import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
66  import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
67  import org.identityconnectors.framework.common.objects.Attribute;
68  import org.identityconnectors.framework.common.objects.SyncDelta;
69  import org.quartz.JobExecutionException;
70  import org.springframework.beans.factory.annotation.Autowired;
71  import org.springframework.transaction.annotation.Propagation;
72  import org.springframework.transaction.annotation.Transactional;
73  
74  public abstract class AbstractPullResultHandler
75          extends AbstractSyncopeResultHandler<PullTask, PullActions>
76          implements SyncopePullResultHandler {
77  
78      protected static Result and(final Result left, final Result right) {
79          return left == Result.SUCCESS && right == Result.SUCCESS
80                  ? Result.SUCCESS
81                  : Result.FAILURE;
82      }
83  
84      @Autowired
85      protected InboundMatcher inboundMatcher;
86  
87      @Autowired
88      protected NotificationManager notificationManager;
89  
90      @Autowired
91      protected AuditManager auditManager;
92  
93      @Autowired
94      protected ConnObjectUtils connObjectUtils;
95  
96      @Autowired
97      protected UserDAO userDAO;
98  
99      @Autowired
100     protected AnyTypeDAO anyTypeDAO;
101 
102     @Autowired
103     protected TaskDAO taskDAO;
104 
105     @Autowired
106     protected RemediationDAO remediationDAO;
107 
108     @Autowired
109     protected VirSchemaDAO virSchemaDAO;
110 
111     @Autowired
112     protected VirAttrCache virAttrCache;
113 
114     @Autowired
115     protected EntityFactory entityFactory;
116 
117     protected abstract String getName(AnyTO anyTO);
118 
119     protected abstract String getName(AnyCR anyCR);
120 
121     protected abstract ProvisioningManager<?, ?> getProvisioningManager();
122 
123     protected abstract AnyTO doCreate(AnyCR anyCR, SyncDelta delta);
124 
125     protected abstract AnyUR doUpdate(AnyTO before, AnyUR anyUR, SyncDelta delta, ProvisioningReport result);
126 
127     @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW)
128     @Override
129     public boolean handle(final SyncDelta delta) {
130         Provision provision = null;
131         try {
132             provision = profile.getTask().getResource().
133                     getProvisionByObjectClass(delta.getObject().getObjectClass().getObjectClassValue()).
134                     orElseThrow(() -> new JobExecutionException(
135                     "No provision found on " + profile.getTask().getResource()
136                     + " for " + delta.getObject().getObjectClass()));
137 
138             Result latestResult = doHandle(delta, provision, anyTypeDAO.find(provision.getAnyType()).getKind());
139 
140             LOG.debug("Successfully handled {}", delta);
141 
142             if (profile.getTask().getPullMode() != PullMode.INCREMENTAL) {
143                 return true;
144             }
145             return latestResult == Result.SUCCESS;
146         } catch (IgnoreProvisionException e) {
147             ProvisioningReport ignoreResult = new ProvisioningReport();
148             ignoreResult.setOperation(ResourceOperation.NONE);
149             ignoreResult.setAnyType(provision == null
150                     ? getAnyUtils().anyTypeKind().name() : provision.getAnyType());
151             ignoreResult.setStatus(ProvisioningReport.Status.IGNORE);
152             ignoreResult.setMessage(e.getMessage());
153             ignoreResult.setKey(null);
154             ignoreResult.setUidValue(delta.getUid().getUidValue());
155             ignoreResult.setName(delta.getObject().getName().getNameValue());
156             profile.getResults().add(ignoreResult);
157 
158             LOG.warn("Ignoring during pull", e);
159 
160             return true;
161         } catch (JobExecutionException e) {
162             LOG.error("Pull failed", e);
163 
164             return false;
165         }
166     }
167 
168     protected void throwIgnoreProvisionException(final SyncDelta delta, final Exception exception)
169             throws JobExecutionException {
170 
171         if (exception instanceof IgnoreProvisionException) {
172             throw IgnoreProvisionException.class.cast(exception);
173         }
174 
175         IgnoreProvisionException ipe = null;
176         for (PullActions action : profile.getActions()) {
177             if (ipe == null) {
178                 ipe = action.onError(profile, delta, exception);
179             }
180         }
181         if (ipe != null) {
182             throw ipe;
183         }
184     }
185 
186     protected Result provision(
187             final UnmatchingRule rule,
188             final SyncDelta delta,
189             final AnyTypeKind anyTypeKind,
190             final Provision provision) throws JobExecutionException {
191 
192         if (!profile.getTask().isPerformCreate()) {
193             LOG.debug("PullTask not configured for create");
194             end(provision.getAnyType(), UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
195             return Result.SUCCESS;
196         }
197 
198         AnyCR anyCR = connObjectUtils.getAnyCR(
199                 delta.getObject(),
200                 profile.getTask(),
201                 anyTypeKind,
202                 provision,
203                 true);
204         if (rule == UnmatchingRule.ASSIGN) {
205             anyCR.getResources().add(profile.getTask().getResource().getKey());
206         }
207 
208         ProvisioningReport result = new ProvisioningReport();
209         result.setOperation(ResourceOperation.CREATE);
210         result.setAnyType(provision.getAnyType());
211         result.setStatus(ProvisioningReport.Status.SUCCESS);
212         result.setName(getName(anyCR));
213         result.setUidValue(delta.getUid().getUidValue());
214 
215         if (profile.isDryRun()) {
216             result.setKey(null);
217             end(provision.getAnyType(), UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
218             return Result.SUCCESS;
219         }
220 
221         Object output;
222         Result resultStatus;
223         try {
224             for (PullActions action : profile.getActions()) {
225                 if (rule == UnmatchingRule.ASSIGN) {
226                     action.beforeAssign(profile, delta, anyCR);
227                 } else if (rule == UnmatchingRule.PROVISION) {
228                     action.beforeProvision(profile, delta, anyCR);
229                 }
230             }
231             result.setName(getName(anyCR));
232 
233             AnyTO created = doCreate(anyCR, delta);
234             output = created;
235             result.setKey(created.getKey());
236             result.setName(getName(created));
237             resultStatus = Result.SUCCESS;
238 
239             for (PullActions action : profile.getActions()) {
240                 action.after(profile, delta, created, result);
241             }
242 
243             LOG.debug("{} {} successfully created", created.getType(), created.getKey());
244         } catch (PropagationException e) {
245             // A propagation failure doesn't imply a pull failure.
246             // The propagation exception status will be reported into the propagation task execution.
247             LOG.error("Could not propagate {} {}",
248                     provision.getAnyType(), delta.getUid().getUidValue(), e);
249             output = e;
250             resultStatus = Result.FAILURE;
251         } catch (Exception e) {
252             throwIgnoreProvisionException(delta, e);
253 
254             result.setStatus(ProvisioningReport.Status.FAILURE);
255             result.setMessage(ExceptionUtils.getRootCauseMessage(e));
256             LOG.error("Could not create {} {} ", provision.getAnyType(), delta.getUid().getUidValue(), e);
257             output = e;
258 
259             if (profile.getTask().isRemediation()) {
260                 // set to SUCCESS to let the incremental flow go on in case of errors
261                 resultStatus = Result.SUCCESS;
262                 createRemediation(provision.getAnyType(), null, anyCR, null, result, delta);
263             } else {
264                 resultStatus = Result.FAILURE;
265             }
266         }
267 
268         end(provision.getAnyType(), UnmatchingRule.toEventName(rule), resultStatus, null, output, delta);
269         profile.getResults().add(result);
270         return resultStatus;
271     }
272 
273     protected Result update(
274             final SyncDelta delta,
275             final List<PullMatch> matches,
276             final Provision provision) throws JobExecutionException {
277 
278         if (!profile.getTask().isPerformUpdate()) {
279             LOG.debug("PullTask not configured for update");
280             end(provision.getAnyType(),
281                     MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
282             return Result.SUCCESS;
283         }
284 
285         LOG.debug("About to update {}", matches);
286 
287         Result global = Result.SUCCESS;
288         for (PullMatch match : matches) {
289             LOG.debug("About to update {}", match);
290 
291             ProvisioningReport result = new ProvisioningReport();
292             result.setOperation(ResourceOperation.UPDATE);
293             result.setAnyType(provision.getAnyType());
294             result.setStatus(ProvisioningReport.Status.SUCCESS);
295             result.setKey(match.getAny().getKey());
296             result.setUidValue(delta.getUid().getUidValue());
297 
298             AnyTO before = getAnyTO(match.getAny());
299             if (before == null) {
300                 result.setStatus(ProvisioningReport.Status.FAILURE);
301                 result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType(), match));
302             } else {
303                 result.setName(getName(before));
304             }
305 
306             if (!profile.isDryRun()) {
307                 Result resultStatus;
308                 Object output;
309                 AnyUR effectiveReq = null;
310 
311                 if (before == null) {
312                     resultStatus = Result.FAILURE;
313                     output = null;
314                 } else {
315                     AnyUR anyUR = null;
316                     try {
317                         anyUR = connObjectUtils.getAnyUR(
318                                 before.getKey(),
319                                 delta.getObject(),
320                                 before,
321                                 profile.getTask(),
322                                 match.getAny().getType().getKind(),
323                                 provision);
324 
325                         for (PullActions action : profile.getActions()) {
326                             action.beforeUpdate(profile, delta, before, anyUR);
327                         }
328 
329                         effectiveReq = doUpdate(before, anyUR, delta, result);
330                         AnyTO updated = AnyOperations.patch(before, effectiveReq);
331 
332                         for (PullActions action : profile.getActions()) {
333                             action.after(profile, delta, updated, result);
334                         }
335 
336                         output = updated;
337                         resultStatus = Result.SUCCESS;
338                         result.setName(getName(updated));
339 
340                         LOG.debug("{} {} successfully updated", provision.getAnyType(), match);
341                     } catch (PropagationException e) {
342                         // A propagation failure doesn't imply a pull failure.
343                         // The propagation exception status will be reported into the propagation task execution.
344                         LOG.error("Could not propagate {} {}",
345                                 provision.getAnyType(), delta.getUid().getUidValue(), e);
346                         output = e;
347                         resultStatus = Result.FAILURE;
348                     } catch (Exception e) {
349                         throwIgnoreProvisionException(delta, e);
350 
351                         result.setStatus(ProvisioningReport.Status.FAILURE);
352                         result.setMessage(ExceptionUtils.getRootCauseMessage(e));
353                         LOG.error("Could not update {} {}",
354                                 provision.getAnyType(), delta.getUid().getUidValue(), e);
355                         output = e;
356 
357                         if (profile.getTask().isRemediation()) {
358                             // set to SUCCESS to let the incremental flow go on in case of errors
359                             resultStatus = Result.SUCCESS;
360                             createRemediation(provision.getAnyType(), null, null, anyUR, result, delta);
361                         } else {
362                             resultStatus = Result.FAILURE;
363                         }
364                     }
365                 }
366                 end(provision.getAnyType(),
367                         MatchingRule.toEventName(MatchingRule.UPDATE),
368                         resultStatus, before, output, delta, effectiveReq);
369                 global = and(global, resultStatus);
370             }
371 
372             profile.getResults().add(result);
373         }
374 
375         return global;
376     }
377 
378     protected Result deprovision(
379             final MatchingRule matchingRule,
380             final SyncDelta delta,
381             final List<PullMatch> matches,
382             final Provision provision)
383             throws JobExecutionException {
384 
385         if (!profile.getTask().isPerformUpdate()) {
386             LOG.debug("PullTask not configured for update");
387             end(provision.getAnyType(),
388                     MatchingRule.toEventName(matchingRule), Result.SUCCESS, null, null, delta);
389             return Result.SUCCESS;
390         }
391 
392         LOG.debug("About to deprovision {}", matches);
393 
394         Result global = Result.SUCCESS;
395         for (PullMatch match : matches) {
396             LOG.debug("About to unassign resource {}", match);
397 
398             ProvisioningReport result = new ProvisioningReport();
399             result.setOperation(ResourceOperation.DELETE);
400             result.setAnyType(provision.getAnyType());
401             result.setStatus(ProvisioningReport.Status.SUCCESS);
402             result.setKey(match.getAny().getKey());
403             result.setUidValue(delta.getUid().getUidValue());
404 
405             AnyTO before = getAnyTO(match.getAny());
406 
407             if (before == null) {
408                 result.setStatus(ProvisioningReport.Status.FAILURE);
409                 result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType(), match));
410             }
411 
412             if (!profile.isDryRun()) {
413                 Object output;
414                 Result resultStatus;
415 
416                 if (before == null) {
417                     resultStatus = Result.FAILURE;
418                     output = null;
419                 } else {
420                     result.setName(getName(before));
421 
422                     try {
423                         if (matchingRule == MatchingRule.UNASSIGN) {
424                             for (PullActions action : profile.getActions()) {
425                                 action.beforeUnassign(profile, delta, before);
426                             }
427                         } else if (matchingRule == MatchingRule.DEPROVISION) {
428                             for (PullActions action : profile.getActions()) {
429                                 action.beforeDeprovision(profile, delta, before);
430                             }
431                         }
432 
433                         PropagationByResource<String> propByRes = new PropagationByResource<>();
434                         propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
435 
436                         taskExecutor.execute(propagationManager.getDeleteTasks(
437                                 match.getAny().getType().getKind(),
438                                 match.getAny().getKey(),
439                                 propByRes,
440                                 null,
441                                 null),
442                                 false,
443                                 securityProperties.getAdminUser());
444 
445                         AnyUR anyUR = null;
446                         if (matchingRule == MatchingRule.UNASSIGN) {
447                             anyUR = getAnyUtils().newAnyUR(match.getAny().getKey());
448                             anyUR.getResources().add(new StringPatchItem.Builder().
449                                     operation(PatchOperation.DELETE).
450                                     value(profile.getTask().getResource().getKey()).build());
451                         }
452                         if (anyUR == null) {
453                             output = getAnyTO(match.getAny());
454                         } else {
455                             output = doUpdate(before, anyUR, delta, result);
456                         }
457 
458                         for (PullActions action : profile.getActions()) {
459                             action.after(profile, delta, AnyTO.class.cast(output), result);
460                         }
461 
462                         resultStatus = Result.SUCCESS;
463 
464                         LOG.debug("{} {} successfully updated", provision.getAnyType(), match);
465                     } catch (PropagationException e) {
466                         // A propagation failure doesn't imply a pull failure.
467                         // The propagation exception status will be reported into the propagation task execution.
468                         LOG.error("Could not propagate {} {}",
469                                 provision.getAnyType(), delta.getUid().getUidValue(), e);
470                         output = e;
471                         resultStatus = Result.FAILURE;
472                     } catch (Exception e) {
473                         throwIgnoreProvisionException(delta, e);
474 
475                         result.setStatus(ProvisioningReport.Status.FAILURE);
476                         result.setMessage(ExceptionUtils.getRootCauseMessage(e));
477                         LOG.error("Could not update {} {}",
478                                 provision.getAnyType(), delta.getUid().getUidValue(), e);
479                         output = e;
480                         resultStatus = Result.FAILURE;
481                     }
482                 }
483                 end(provision.getAnyType(),
484                         MatchingRule.toEventName(matchingRule),
485                         resultStatus, before, output, delta);
486                 global = and(global, resultStatus);
487             }
488 
489             profile.getResults().add(result);
490         }
491 
492         return global;
493     }
494 
495     protected Result link(
496             final SyncDelta delta,
497             final List<PullMatch> matches,
498             final Provision provision,
499             final boolean unlink)
500             throws JobExecutionException {
501 
502         if (!profile.getTask().isPerformUpdate()) {
503             LOG.debug("PullTask not configured for update");
504             end(provision.getAnyType(),
505                     unlink
506                             ? MatchingRule.toEventName(MatchingRule.UNLINK)
507                             : MatchingRule.toEventName(MatchingRule.LINK),
508                     Result.SUCCESS, null, null, delta);
509             return Result.SUCCESS;
510         }
511 
512         LOG.debug("About to update {}", matches);
513 
514         Result global = Result.SUCCESS;
515         for (PullMatch match : matches) {
516             LOG.debug("About to unassign resource {}", match);
517 
518             ProvisioningReport result = new ProvisioningReport();
519             result.setOperation(ResourceOperation.NONE);
520             result.setAnyType(provision.getAnyType());
521             result.setStatus(ProvisioningReport.Status.SUCCESS);
522             result.setKey(match.getAny().getKey());
523             result.setUidValue(delta.getUid().getUidValue());
524 
525             AnyTO before = getAnyTO(match.getAny());
526 
527             if (before == null) {
528                 result.setStatus(ProvisioningReport.Status.FAILURE);
529                 result.setMessage(String.format("Any '%s(%s)' not found", provision.getAnyType(), match));
530             }
531 
532             if (!profile.isDryRun()) {
533                 Result resultStatus;
534                 Object output;
535                 AnyUR effectiveReq = null;
536 
537                 if (before == null) {
538                     resultStatus = Result.FAILURE;
539                     output = null;
540                 } else {
541                     result.setName(getName(before));
542 
543                     try {
544                         if (unlink) {
545                             for (PullActions action : profile.getActions()) {
546                                 action.beforeUnlink(profile, delta, before);
547                             }
548                         } else {
549                             for (PullActions action : profile.getActions()) {
550                                 action.beforeLink(profile, delta, before);
551                             }
552                         }
553 
554                         AnyUR anyUR = getAnyUtils().newAnyUR(before.getKey());
555                         anyUR.getResources().add(new StringPatchItem.Builder().
556                                 operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
557                                 value(profile.getTask().getResource().getKey()).build());
558 
559                         effectiveReq = update(anyUR).getResult();
560                         output = AnyOperations.patch(before, effectiveReq);
561 
562                         for (PullActions action : profile.getActions()) {
563                             action.after(profile, delta, AnyTO.class.cast(output), result);
564                         }
565 
566                         resultStatus = Result.SUCCESS;
567 
568                         LOG.debug("{} {} successfully updated", provision.getAnyType(), match);
569                     } catch (PropagationException e) {
570                         // A propagation failure doesn't imply a pull failure.
571                         // The propagation exception status will be reported into the propagation task execution.
572                         LOG.error("Could not propagate {} {}",
573                                 provision.getAnyType(), delta.getUid().getUidValue(), e);
574                         output = e;
575                         resultStatus = Result.FAILURE;
576                     } catch (Exception e) {
577                         throwIgnoreProvisionException(delta, e);
578 
579                         result.setStatus(ProvisioningReport.Status.FAILURE);
580                         result.setMessage(ExceptionUtils.getRootCauseMessage(e));
581                         LOG.error("Could not update {} {}",
582                                 provision.getAnyType(), delta.getUid().getUidValue(), e);
583                         output = e;
584                         resultStatus = Result.FAILURE;
585                     }
586                 }
587                 end(provision.getAnyType(),
588                         unlink
589                                 ? MatchingRule.toEventName(MatchingRule.UNLINK)
590                                 : MatchingRule.toEventName(MatchingRule.LINK),
591                         resultStatus, before, output, delta, effectiveReq);
592                 global = and(global, resultStatus);
593             }
594 
595             profile.getResults().add(result);
596         }
597 
598         return global;
599     }
600 
601     protected Result delete(
602             final SyncDelta delta,
603             final List<PullMatch> matches,
604             final Provision provision)
605             throws JobExecutionException {
606 
607         if (!profile.getTask().isPerformDelete()) {
608             LOG.debug("PullTask not configured for delete");
609             end(provision.getAnyType(),
610                     ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, delta);
611             return Result.SUCCESS;
612         }
613 
614         LOG.debug("About to delete {}", matches);
615 
616         Result global = Result.SUCCESS;
617         for (PullMatch match : matches) {
618             Object output;
619             Result resultStatus = Result.FAILURE;
620 
621             ProvisioningReport result = new ProvisioningReport();
622 
623             try {
624                 AnyTO before = getAnyTO(match.getAny());
625 
626                 result.setKey(match.getAny().getKey());
627                 result.setName(getName(before));
628                 result.setOperation(ResourceOperation.DELETE);
629                 result.setAnyType(provision.getAnyType());
630                 result.setStatus(ProvisioningReport.Status.SUCCESS);
631                 result.setUidValue(delta.getUid().getUidValue());
632 
633                 if (!profile.isDryRun()) {
634                     for (PullActions action : profile.getActions()) {
635                         action.beforeDelete(profile, delta, before);
636                     }
637 
638                     try {
639                         getProvisioningManager().delete(
640                                 match.getAny().getKey(),
641                                 Set.of(profile.getTask().getResource().getKey()),
642                                 true,
643                                 profile.getExecutor(),
644                                 getContext());
645                         output = null;
646                         resultStatus = Result.SUCCESS;
647 
648                         for (PullActions action : profile.getActions()) {
649                             action.after(profile, delta, before, result);
650                         }
651                     } catch (Exception e) {
652                         throwIgnoreProvisionException(delta, e);
653 
654                         result.setStatus(ProvisioningReport.Status.FAILURE);
655                         result.setMessage(ExceptionUtils.getRootCauseMessage(e));
656                         LOG.error("Could not delete {} {}", provision.getAnyType(), match, e);
657                         output = e;
658 
659                         if (profile.getTask().isRemediation()) {
660                             // set to SUCCESS to let the incremental flow go on in case of errors
661                             resultStatus = Result.SUCCESS;
662                             createRemediation(
663                                     provision.getAnyType(), match.getAny().getKey(), null, null, result, delta);
664                         }
665                     }
666 
667                     end(provision.getAnyType(),
668                             ResourceOperation.DELETE.name().toLowerCase(),
669                             resultStatus, before, output, delta);
670                     global = and(global, resultStatus);
671                 }
672 
673                 profile.getResults().add(result);
674             } catch (NotFoundException e) {
675                 LOG.error("Could not find {} {}", provision.getAnyType(), match, e);
676             } catch (DelegatedAdministrationException e) {
677                 LOG.error("Not allowed to read {} {}", provision.getAnyType(), match, e);
678             } catch (Exception e) {
679                 LOG.error("Could not delete {} {}", provision.getAnyType(), match, e);
680             }
681         }
682 
683         return global;
684     }
685 
686     protected Result ignore(
687             final SyncDelta delta,
688             final List<PullMatch> matches,
689             final Provision provision,
690             final boolean matching,
691             final String... message)
692             throws JobExecutionException {
693 
694         LOG.debug("Any to ignore {}", delta.getObject().getUid().getUidValue());
695 
696         if (matches == null) {
697             ProvisioningReport report = new ProvisioningReport();
698             report.setKey(null);
699             report.setName(delta.getObject().getUid().getUidValue());
700             report.setOperation(ResourceOperation.NONE);
701             report.setAnyType(provision.getAnyType());
702             report.setStatus(ProvisioningReport.Status.SUCCESS);
703             report.setUidValue(delta.getUid().getUidValue());
704             if (message != null && message.length >= 1) {
705                 report.setMessage(message[0]);
706             }
707 
708             profile.getResults().add(report);
709         } else {
710             matches.forEach(match -> {
711                 ProvisioningReport report = new ProvisioningReport();
712                 report.setKey(match.getAny().getKey());
713                 report.setName(delta.getObject().getUid().getUidValue());
714                 report.setOperation(ResourceOperation.NONE);
715                 report.setAnyType(provision.getAnyType());
716                 report.setStatus(ProvisioningReport.Status.SUCCESS);
717                 report.setUidValue(delta.getUid().getUidValue());
718                 if (message != null && message.length >= 1) {
719                     report.setMessage(message[0]);
720                 }
721 
722                 profile.getResults().add(report);
723             });
724         }
725 
726         end(provision.getAnyType(),
727                 matching
728                         ? MatchingRule.toEventName(MatchingRule.IGNORE)
729                         : UnmatchingRule.toEventName(UnmatchingRule.IGNORE),
730                 Result.SUCCESS, null, null, delta);
731 
732         return Result.SUCCESS;
733     }
734 
735     protected Result handleAnys(
736             final SyncDelta delta,
737             final List<PullMatch> matches,
738             final AnyTypeKind anyTypeKind,
739             final Provision provision) throws JobExecutionException {
740 
741         if (matches.isEmpty()) {
742             LOG.debug("Nothing to do");
743             return Result.SUCCESS;
744         }
745 
746         Result result = Result.SUCCESS;
747         switch (delta.getDeltaType()) {
748             case CREATE:
749             case UPDATE:
750             case CREATE_OR_UPDATE:
751                 if (matches.get(0).getAny() == null) {
752                     switch (profile.getTask().getUnmatchingRule()) {
753                         case ASSIGN:
754                         case PROVISION:
755                             result = provision(profile.getTask().getUnmatchingRule(), delta, anyTypeKind, provision);
756                             break;
757 
758                         case IGNORE:
759                             result = ignore(delta, null, provision, false);
760                             break;
761 
762                         default:
763                         // do nothing
764                     }
765                 } else {
766                     // update VirAttrCache
767                     virSchemaDAO.find(
768                             profile.getTask().getResource().getKey(),
769                             matches.get(0).getAny().getType().getKey()).
770                             forEach(vs -> {
771                                 Attribute attr = delta.getObject().getAttributeByName(vs.getExtAttrName());
772                                 matches.forEach(match -> {
773                                     VirAttrCacheKey cacheKey = new VirAttrCacheKey(
774                                             provision.getAnyType(), match.getAny().getKey(),
775                                             vs.getKey());
776                                     if (attr == null) {
777                                         virAttrCache.expire(cacheKey);
778                                     } else {
779                                         virAttrCache.put(cacheKey, new VirAttrCacheValue(attr.getValue()));
780                                     }
781                                 });
782                             });
783 
784                     switch (profile.getTask().getMatchingRule()) {
785                         case UPDATE:
786                             result = update(delta, matches, provision);
787                             break;
788 
789                         case DEPROVISION:
790                         case UNASSIGN:
791                             result = deprovision(profile.getTask().getMatchingRule(), delta, matches, provision);
792                             break;
793 
794                         case LINK:
795                             result = link(delta, matches, provision, false);
796                             break;
797 
798                         case UNLINK:
799                             result = link(delta, matches, provision, true);
800                             break;
801 
802                         case IGNORE:
803                             result = ignore(delta, matches, provision, true);
804                             break;
805 
806                         default:
807                         // do nothing
808                     }
809                 }
810                 break;
811 
812             case DELETE:
813                 // Skip DELETE in case of PullCorrelationRule.NO_MATCH
814                 result = matches.get(0).getAny() == null ? Result.SUCCESS : delete(delta, matches, provision);
815                 break;
816 
817             default:
818         }
819 
820         return result;
821     }
822 
823     protected Result handleLinkedAccounts(
824             final SyncDelta delta,
825             final List<PullMatch> matches,
826             final Provision provision) throws JobExecutionException {
827 
828         if (matches.isEmpty()) {
829             LOG.debug("Nothing to do");
830             return Result.SUCCESS;
831         }
832 
833         // nothing to do in the general case
834         LOG.warn("Unexpected linked accounts found for {}: {}", provision.getAnyType(), matches);
835         return Result.SUCCESS;
836     }
837 
838     /**
839      * Look into SyncDelta and take necessary profile.getActions() (create / update / delete) on any object(s).
840      *
841      * @param delta returned by the underlying profile.getConnector()
842      * @param provision provisioning info
843      * @param anyTypeKind any type kind
844      * @return if handle was successful or not
845      * @throws JobExecutionException in case of pull failure.
846      */
847     protected Result doHandle(
848             final SyncDelta delta,
849             final Provision provision,
850             final AnyTypeKind anyTypeKind) throws JobExecutionException {
851 
852         LOG.debug("Process {} for {} as {}",
853                 delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
854 
855         SyncDelta finalDelta = delta;
856         for (PullActions action : profile.getActions()) {
857             finalDelta = action.preprocess(profile, finalDelta);
858         }
859 
860         LOG.debug("Transformed {} for {} as {}",
861                 finalDelta.getDeltaType(), finalDelta.getUid().getUidValue(), finalDelta.getObject().getObjectClass());
862 
863         Result result = Result.SUCCESS;
864         try {
865             List<PullMatch> matches = inboundMatcher.match(
866                     finalDelta,
867                     profile.getTask().getResource(),
868                     provision,
869                     anyTypeKind);
870             LOG.debug("Match(es) found for {} as {}: {}",
871                     finalDelta.getUid().getUidValue(), finalDelta.getObject().getObjectClass(), matches);
872 
873             if (matches.size() > 1) {
874                 switch (profile.getConflictResolutionAction()) {
875                     case IGNORE:
876                         throw new IgnoreProvisionException("More than one match found for "
877                                 + finalDelta.getObject().getUid().getUidValue() + ": " + matches);
878 
879                     case FIRSTMATCH:
880                         matches = matches.subList(0, 1);
881                         break;
882 
883                     case LASTMATCH:
884                         matches = matches.subList(matches.size() - 1, matches.size());
885                         break;
886 
887                     default:
888                     // keep matches unmodified
889                 }
890             }
891 
892             // users, groups and any objects
893             Result anys = handleAnys(
894                     finalDelta,
895                     matches.stream().
896                             filter(match -> match.getMatchTarget() == MatchType.ANY).
897                             collect(Collectors.toList()),
898                     anyTypeKind,
899                     provision);
900 
901             // linked accounts
902             Result linkedAccounts = handleLinkedAccounts(
903                     finalDelta,
904                     matches.stream().
905                             filter(match -> match.getMatchTarget() == MatchType.LINKED_ACCOUNT).
906                             collect(Collectors.toList()), provision);
907 
908             result = and(anys, linkedAccounts);
909         } catch (IllegalStateException | IllegalArgumentException e) {
910             LOG.warn(e.getMessage());
911         }
912 
913         return result;
914     }
915 
916     protected void end(
917             final String anyType,
918             final String event,
919             final Result result,
920             final Object before,
921             final Object output,
922             final SyncDelta delta,
923             final Object... furtherInput) {
924 
925         notificationManager.createTasks(
926                 profile.getExecutor(),
927                 AuditElements.EventCategoryType.PULL,
928                 anyType,
929                 profile.getTask().getResource().getKey(),
930                 event,
931                 result,
932                 before,
933                 output,
934                 delta,
935                 furtherInput);
936 
937         auditManager.audit(
938                 profile.getExecutor(),
939                 AuditElements.EventCategoryType.PULL,
940                 anyType,
941                 profile.getTask().getResource().getKey(),
942                 event,
943                 result,
944                 before,
945                 output,
946                 delta,
947                 furtherInput);
948     }
949 
950     protected void createRemediationIfNeeded(
951             final AnyUR anyUR,
952             final SyncDelta delta,
953             final ProvisioningReport result) {
954 
955         if (ProvisioningReport.Status.FAILURE == result.getStatus() && profile.getTask().isRemediation()) {
956             createRemediation(result.getAnyType(), null, null, anyUR, result, delta);
957         }
958     }
959 
960     protected void createRemediation(
961             final String anyType,
962             final String anyKey,
963             final AnyCR anyCR,
964             final AnyUR anyUR,
965             final ProvisioningReport result,
966             final SyncDelta delta) {
967 
968         Remediation remediation = entityFactory.newEntity(Remediation.class);
969 
970         remediation.setAnyType(anyTypeDAO.find(anyType));
971         remediation.setOperation(anyUR == null ? ResourceOperation.CREATE : ResourceOperation.UPDATE);
972         if (StringUtils.isNotBlank(anyKey)) {
973             remediation.setPayload(anyKey);
974         } else if (anyCR != null) {
975             remediation.setPayload(anyCR);
976         } else if (anyUR != null) {
977             remediation.setPayload(anyUR);
978         }
979         remediation.setError(result.getMessage());
980         remediation.setInstant(OffsetDateTime.now());
981         remediation.setRemoteName(delta.getObject().getName().getNameValue());
982         if (taskDAO.exists(TaskType.PULL, profile.getTask().getKey())) {
983             remediation.setPullTask((PullTask) taskDAO.find(TaskType.PULL, profile.getTask().getKey()));
984         }
985 
986         remediation = remediationDAO.save(remediation);
987 
988         ProvisioningReport remediationResult = new ProvisioningReport();
989         remediationResult.setOperation(remediation.getOperation());
990         remediationResult.setAnyType(anyType);
991         remediationResult.setStatus(ProvisioningReport.Status.FAILURE);
992         remediationResult.setMessage(remediation.getError());
993         if (StringUtils.isNotBlank(anyKey)) {
994             remediationResult.setKey(anyKey);
995         } else if (anyUR != null) {
996             remediationResult.setKey(anyUR.getKey());
997         }
998         remediationResult.setUidValue(delta.getUid().getUidValue());
999         remediationResult.setName(remediation.getRemoteName());
1000         profile.getResults().add(remediationResult);
1001     }
1002 }