1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.common.lib;
20
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28 import org.apache.commons.lang3.SerializationUtils;
29 import org.apache.commons.lang3.StringUtils;
30 import org.apache.commons.lang3.tuple.Pair;
31 import org.apache.syncope.common.lib.request.AbstractReplacePatchItem;
32 import org.apache.syncope.common.lib.request.AnyObjectUR;
33 import org.apache.syncope.common.lib.request.AnyUR;
34 import org.apache.syncope.common.lib.request.AttrPatch;
35 import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
36 import org.apache.syncope.common.lib.request.GroupUR;
37 import org.apache.syncope.common.lib.request.LinkedAccountUR;
38 import org.apache.syncope.common.lib.request.MembershipUR;
39 import org.apache.syncope.common.lib.request.PasswordPatch;
40 import org.apache.syncope.common.lib.request.RelationshipUR;
41 import org.apache.syncope.common.lib.request.StringPatchItem;
42 import org.apache.syncope.common.lib.request.StringReplacePatchItem;
43 import org.apache.syncope.common.lib.request.UserUR;
44 import org.apache.syncope.common.lib.to.AnyObjectTO;
45 import org.apache.syncope.common.lib.to.AnyTO;
46 import org.apache.syncope.common.lib.to.GroupTO;
47 import org.apache.syncope.common.lib.to.LinkedAccountTO;
48 import org.apache.syncope.common.lib.to.MembershipTO;
49 import org.apache.syncope.common.lib.to.RelationshipTO;
50 import org.apache.syncope.common.lib.to.UserTO;
51 import org.apache.syncope.common.lib.types.PatchOperation;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55
56
57
58 public final class AnyOperations {
59
60 private static final Logger LOG = LoggerFactory.getLogger(AnyOperations.class);
61
62 private static final List<String> NULL_SINGLETON_LIST = Collections.singletonList(null);
63
64 private AnyOperations() {
65
66 }
67
68 private static <T, K extends AbstractReplacePatchItem<T>> K replacePatchItem(
69 final T updated, final T original, final K proto) {
70
71 if ((original == null && updated == null) || (original != null && original.equals(updated))) {
72 return null;
73 }
74
75 proto.setValue(updated);
76 return proto;
77 }
78
79 private static void diff(
80 final AnyTO updated, final AnyTO original, final AnyUR result, final boolean incremental) {
81
82
83 if (updated.getKey() == null && original.getKey() != null
84 || (updated.getKey() != null && !updated.getKey().equals(original.getKey()))) {
85
86 throw new IllegalArgumentException("AnyTO's key must be the same");
87 }
88 result.setKey(updated.getKey());
89
90
91 result.setRealm(replacePatchItem(updated.getRealm(), original.getRealm(), new StringReplacePatchItem()));
92
93
94 result.getAuxClasses().clear();
95
96 if (!incremental) {
97 original.getAuxClasses().stream().filter(auxClass -> !updated.getAuxClasses().contains(auxClass)).
98 forEach(auxClass -> result.getAuxClasses().add(new StringPatchItem.Builder().
99 operation(PatchOperation.DELETE).value(auxClass).build()));
100 }
101
102 updated.getAuxClasses().stream().filter(auxClass -> !original.getAuxClasses().contains(auxClass)).
103 forEach(auxClass -> result.getAuxClasses().add(new StringPatchItem.Builder().
104 operation(PatchOperation.ADD_REPLACE).value(auxClass).build()));
105
106
107 Map<String, Attr> updatedAttrs = EntityTOUtils.buildAttrMap(updated.getPlainAttrs());
108 Map<String, Attr> originalAttrs = EntityTOUtils.buildAttrMap(original.getPlainAttrs());
109
110 result.getPlainAttrs().clear();
111
112 if (!incremental) {
113 originalAttrs.keySet().stream().
114 filter(attr -> !updatedAttrs.containsKey(attr)).forEach(
115 schema -> result.getPlainAttrs().add(
116 new AttrPatch.Builder(new Attr.Builder(schema).build()).
117 operation(PatchOperation.DELETE).
118 build()));
119 }
120
121 updatedAttrs.values().forEach(attr -> {
122 if (isEmpty(attr)) {
123 if (!incremental) {
124 result.getPlainAttrs().add(
125 new AttrPatch.Builder(new Attr.Builder(attr.getSchema()).build()).
126 operation(PatchOperation.DELETE).
127 build());
128 }
129 } else if (!originalAttrs.containsKey(attr.getSchema())
130 || !originalAttrs.get(attr.getSchema()).getValues().equals(attr.getValues())) {
131
132 AttrPatch patch = new AttrPatch.Builder(attr).operation(PatchOperation.ADD_REPLACE).build();
133 if (!patch.isEmpty()) {
134 result.getPlainAttrs().add(patch);
135 }
136 }
137 });
138
139
140 result.getVirAttrs().clear();
141 result.getVirAttrs().addAll(updated.getVirAttrs());
142
143
144 result.getResources().clear();
145
146 if (!incremental) {
147 original.getResources().stream().filter(resource -> !updated.getResources().contains(resource)).
148 forEach(resource -> result.getResources().add(new StringPatchItem.Builder().
149 operation(PatchOperation.DELETE).value(resource).build()));
150 }
151
152 updated.getResources().stream().filter(resource -> !original.getResources().contains(resource)).
153 forEach(resource -> result.getResources().add(new StringPatchItem.Builder().
154 operation(PatchOperation.ADD_REPLACE).value(resource).build()));
155 }
156
157
158
159
160
161
162
163
164
165 public static AnyObjectUR diff(
166 final AnyObjectTO updated, final AnyObjectTO original, final boolean incremental) {
167
168 AnyObjectUR result = new AnyObjectUR();
169
170 diff(updated, original, result, incremental);
171
172
173 result.setName(replacePatchItem(updated.getName(), original.getName(), new StringReplacePatchItem()));
174
175
176 Map<Pair<String, String>, RelationshipTO> updatedRels =
177 EntityTOUtils.buildRelationshipMap(updated.getRelationships());
178 Map<Pair<String, String>, RelationshipTO> originalRels =
179 EntityTOUtils.buildRelationshipMap(original.getRelationships());
180
181 updatedRels.entrySet().stream().
182 filter(entry -> (!originalRels.containsKey(entry.getKey()))).
183 forEach(entry -> result.getRelationships().add(new RelationshipUR.Builder(entry.getValue()).
184 operation(PatchOperation.ADD_REPLACE).build()));
185
186 if (!incremental) {
187 originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)).
188 forEach(key -> result.getRelationships().add(new RelationshipUR.Builder(originalRels.get(key)).
189 operation(PatchOperation.DELETE).build()));
190 }
191
192
193 Map<String, MembershipTO> updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships());
194 Map<String, MembershipTO> originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships());
195
196 updatedMembs.forEach((key, value) -> {
197 MembershipUR membershipPatch = new MembershipUR.Builder(value.getGroupKey()).
198 operation(PatchOperation.ADD_REPLACE).build();
199
200 diff(value, membershipPatch);
201
202 if (!originalMembs.containsKey(key)
203 || (!membershipPatch.getPlainAttrs().isEmpty() || !membershipPatch.getVirAttrs().isEmpty())) {
204
205 result.getMemberships().add(membershipPatch);
206 }
207 });
208
209 if (!incremental) {
210 originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership)).
211 forEach(key -> result.getMemberships().add(
212 new MembershipUR.Builder(originalMembs.get(key).getGroupKey()).
213 operation(PatchOperation.DELETE).build()));
214 }
215
216 return result;
217 }
218
219 private static void diff(
220 final MembershipTO updated,
221 final MembershipUR result) {
222
223
224 result.getPlainAttrs().addAll(updated.getPlainAttrs().stream().
225 filter(attr -> !isEmpty(attr)).
226 collect(Collectors.toSet()));
227
228
229 result.getVirAttrs().clear();
230 result.getVirAttrs().addAll(updated.getVirAttrs());
231 }
232
233
234
235
236
237
238
239
240
241 public static UserUR diff(final UserTO updated, final UserTO original, final boolean incremental) {
242 UserUR result = new UserUR();
243
244 diff(updated, original, result, incremental);
245
246
247 if (updated.getPassword() != null
248 && (original.getPassword() == null || !original.getPassword().equals(updated.getPassword()))) {
249
250 result.setPassword(new PasswordPatch.Builder().
251 value(updated.getPassword()).
252 resources(updated.getResources()).build());
253 }
254
255
256 result.setUsername(
257 replacePatchItem(updated.getUsername(), original.getUsername(), new StringReplacePatchItem()));
258
259
260 if (updated.getSecurityQuestion() == null) {
261 result.setSecurityQuestion(null);
262 result.setSecurityAnswer(null);
263 } else if (!updated.getSecurityQuestion().equals(original.getSecurityQuestion())
264 || StringUtils.isNotBlank(updated.getSecurityAnswer())) {
265
266 result.setSecurityQuestion(new StringReplacePatchItem.Builder().
267 value(updated.getSecurityQuestion()).build());
268 result.setSecurityAnswer(
269 new StringReplacePatchItem.Builder().value(updated.getSecurityAnswer()).build());
270 }
271
272 result.setMustChangePassword(replacePatchItem(
273 updated.isMustChangePassword(), original.isMustChangePassword(), new BooleanReplacePatchItem()));
274
275
276 if (!incremental) {
277 original.getRoles().stream().filter(role -> !updated.getRoles().contains(role)).
278 forEach(toRemove -> result.getRoles().add(new StringPatchItem.Builder().
279 operation(PatchOperation.DELETE).value(toRemove).build()));
280 }
281
282 updated.getRoles().stream().filter(role -> !original.getRoles().contains(role)).
283 forEach(toAdd -> result.getRoles().add(new StringPatchItem.Builder().
284 operation(PatchOperation.ADD_REPLACE).value(toAdd).build()));
285
286
287 Map<Pair<String, String>, RelationshipTO> updatedRels =
288 EntityTOUtils.buildRelationshipMap(updated.getRelationships());
289 Map<Pair<String, String>, RelationshipTO> originalRels =
290 EntityTOUtils.buildRelationshipMap(original.getRelationships());
291
292 updatedRels.entrySet().stream().
293 filter(entry -> (!originalRels.containsKey(entry.getKey()))).
294 forEach(entry -> result.getRelationships().add(new RelationshipUR.Builder(entry.getValue()).
295 operation(PatchOperation.ADD_REPLACE).build()));
296
297 if (!incremental) {
298 originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)).
299 forEach(key -> result.getRelationships().add(new RelationshipUR.Builder(originalRels.get(key)).
300 operation(PatchOperation.DELETE).build()));
301 }
302
303
304 Map<String, MembershipTO> updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships());
305 Map<String, MembershipTO> originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships());
306
307 updatedMembs.forEach((key, value) -> {
308 MembershipUR membershipPatch = new MembershipUR.Builder(value.getGroupKey()).
309 operation(PatchOperation.ADD_REPLACE).build();
310
311 diff(value, membershipPatch);
312
313 if (!originalMembs.containsKey(key)
314 || (!membershipPatch.getPlainAttrs().isEmpty() || !membershipPatch.getVirAttrs().isEmpty())) {
315
316 result.getMemberships().add(membershipPatch);
317 }
318 });
319
320 if (!incremental) {
321 originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership))
322 .forEach(key -> result.getMemberships()
323 .add(new MembershipUR.Builder(originalMembs.get(key).getGroupKey())
324 .operation(PatchOperation.DELETE).build()));
325 }
326
327
328 Map<Pair<String, String>, LinkedAccountTO> updatedAccounts =
329 EntityTOUtils.buildLinkedAccountMap(updated.getLinkedAccounts());
330 Map<Pair<String, String>, LinkedAccountTO> originalAccounts =
331 EntityTOUtils.buildLinkedAccountMap(original.getLinkedAccounts());
332
333 updatedAccounts.entrySet().stream().
334 forEachOrdered(entry -> {
335 result.getLinkedAccounts().add(new LinkedAccountUR.Builder().
336 operation(PatchOperation.ADD_REPLACE).
337 linkedAccountTO(entry.getValue()).build());
338 });
339
340 if (!incremental) {
341 originalAccounts.keySet().stream().filter(account -> !updatedAccounts.containsKey(account)).
342 forEach(key -> {
343 result.getLinkedAccounts().add(new LinkedAccountUR.Builder().
344 operation(PatchOperation.DELETE).
345 linkedAccountTO(originalAccounts.get(key)).build());
346 });
347 }
348
349 return result;
350 }
351
352
353
354
355
356
357
358
359
360 public static GroupUR diff(final GroupTO updated, final GroupTO original, final boolean incremental) {
361 GroupUR result = new GroupUR();
362
363 diff(updated, original, result, incremental);
364
365
366 result.setName(replacePatchItem(updated.getName(), original.getName(), new StringReplacePatchItem()));
367
368
369 result.setUserOwner(
370 replacePatchItem(updated.getUserOwner(), original.getUserOwner(), new StringReplacePatchItem()));
371 result.setGroupOwner(
372 replacePatchItem(updated.getGroupOwner(), original.getGroupOwner(), new StringReplacePatchItem()));
373
374
375 result.setUDynMembershipCond(updated.getUDynMembershipCond());
376 result.getADynMembershipConds().putAll(updated.getADynMembershipConds());
377
378
379 result.getTypeExtensions().addAll(updated.getTypeExtensions());
380
381 return result;
382 }
383
384 @SuppressWarnings("unchecked")
385 public static <TO extends AnyTO, P extends AnyUR> P diff(
386 final TO updated, final TO original, final boolean incremental) {
387
388 if (updated instanceof UserTO && original instanceof UserTO) {
389 return (P) diff((UserTO) updated, (UserTO) original, incremental);
390 } else if (updated instanceof GroupTO && original instanceof GroupTO) {
391 return (P) diff((GroupTO) updated, (GroupTO) original, incremental);
392 } else if (updated instanceof AnyObjectTO && original instanceof AnyObjectTO) {
393 return (P) diff((AnyObjectTO) updated, (AnyObjectTO) original, incremental);
394 }
395
396 throw new IllegalArgumentException("Unsupported: " + updated.getClass().getName());
397 }
398
399 private static Collection<Attr> patch(final Map<String, Attr> attrs, final Set<AttrPatch> attrPatches) {
400 Map<String, Attr> rwattrs = new HashMap<>(attrs);
401 attrPatches.forEach(patch -> {
402 if (patch.getAttr() == null) {
403 LOG.warn("Invalid {} specified: {}", AttrPatch.class.getName(), patch);
404 } else {
405 rwattrs.remove(patch.getAttr().getSchema());
406 if (patch.getOperation() == PatchOperation.ADD_REPLACE && !patch.getAttr().getValues().isEmpty()) {
407 rwattrs.put(patch.getAttr().getSchema(), patch.getAttr());
408 }
409 }
410 });
411
412 return rwattrs.values();
413 }
414
415 private static <T extends AnyTO, K extends AnyUR> void patch(final T to, final K req, final T result) {
416
417 if (to.getKey() == null || !to.getKey().equals(req.getKey())) {
418 throw new IllegalArgumentException(
419 to.getClass().getSimpleName() + " and "
420 + req.getClass().getSimpleName() + " keys must be the same");
421 }
422
423
424 if (req.getRealm() != null) {
425 result.setRealm(req.getRealm().getValue());
426 }
427
428
429 for (StringPatchItem auxClassPatch : req.getAuxClasses()) {
430 switch (auxClassPatch.getOperation()) {
431 case ADD_REPLACE:
432 result.getAuxClasses().add(auxClassPatch.getValue());
433 break;
434
435 case DELETE:
436 default:
437 result.getAuxClasses().remove(auxClassPatch.getValue());
438 }
439 }
440
441
442 result.getPlainAttrs().clear();
443 result.getPlainAttrs().addAll(patch(EntityTOUtils.buildAttrMap(to.getPlainAttrs()), req.getPlainAttrs()));
444
445
446 result.getVirAttrs().clear();
447 result.getVirAttrs().addAll(req.getVirAttrs());
448
449
450 for (StringPatchItem resourcePatch : req.getResources()) {
451 switch (resourcePatch.getOperation()) {
452 case ADD_REPLACE:
453 result.getResources().add(resourcePatch.getValue());
454 break;
455
456 case DELETE:
457 default:
458 result.getResources().remove(resourcePatch.getValue());
459 }
460 }
461 }
462
463 public static AnyTO patch(final AnyTO anyTO, final AnyUR anyUR) {
464 if (anyTO instanceof UserTO) {
465 return patch((UserTO) anyTO, (UserUR) anyUR);
466 }
467 if (anyTO instanceof GroupTO) {
468 return patch((GroupTO) anyTO, (GroupUR) anyUR);
469 }
470 if (anyTO instanceof AnyObjectTO) {
471 return patch((AnyObjectTO) anyTO, (AnyObjectUR) anyUR);
472 }
473 return null;
474 }
475
476 public static GroupTO patch(final GroupTO groupTO, final GroupUR groupUR) {
477 GroupTO result = SerializationUtils.clone(groupTO);
478 patch(groupTO, groupUR, result);
479
480 if (groupUR.getName() != null) {
481 result.setName(groupUR.getName().getValue());
482 }
483
484 if (groupUR.getUserOwner() != null) {
485 result.setGroupOwner(groupUR.getUserOwner().getValue());
486 }
487 if (groupUR.getGroupOwner() != null) {
488 result.setGroupOwner(groupUR.getGroupOwner().getValue());
489 }
490
491 result.setUDynMembershipCond(groupUR.getUDynMembershipCond());
492 result.getADynMembershipConds().clear();
493 result.getADynMembershipConds().putAll(groupUR.getADynMembershipConds());
494
495 return result;
496 }
497
498 public static AnyObjectTO patch(final AnyObjectTO anyObjectTO, final AnyObjectUR anyObjectUR) {
499 AnyObjectTO result = SerializationUtils.clone(anyObjectTO);
500 patch(anyObjectTO, anyObjectUR, result);
501
502 if (anyObjectUR.getName() != null) {
503 result.setName(anyObjectUR.getName().getValue());
504 }
505
506
507 anyObjectUR.getRelationships().
508 forEach(relPatch -> {
509 if (relPatch.getRelationshipTO() == null) {
510 LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch);
511 } else {
512 result.getRelationships().remove(relPatch.getRelationshipTO());
513 if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) {
514 result.getRelationships().add(relPatch.getRelationshipTO());
515 }
516 }
517 });
518
519
520 anyObjectUR.getMemberships().forEach(membPatch -> {
521 if (membPatch.getGroup() == null) {
522 LOG.warn("Invalid {} specified: {}", MembershipUR.class.getName(), membPatch);
523 } else {
524 result.getMemberships().stream().
525 filter(membership -> membPatch.getGroup().equals(membership.getGroupKey())).
526 findFirst().ifPresent(memb -> result.getMemberships().remove(memb));
527
528 if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
529 MembershipTO newMembershipTO = new MembershipTO.Builder(membPatch.getGroup()).
530
531 plainAttrs(membPatch.getPlainAttrs()).
532
533 virAttrs(membPatch.getVirAttrs()).
534 build();
535
536 result.getMemberships().add(newMembershipTO);
537 }
538 }
539 });
540
541 return result;
542 }
543
544 public static UserTO patch(final UserTO userTO, final UserUR userUR) {
545 UserTO result = SerializationUtils.clone(userTO);
546 patch(userTO, userUR, result);
547
548
549 if (userUR.getPassword() != null) {
550 result.setPassword(userUR.getPassword().getValue());
551 }
552
553
554 if (userUR.getUsername() != null) {
555 result.setUsername(userUR.getUsername().getValue());
556 }
557
558
559 userUR.getRelationships().forEach(relPatch -> {
560 if (relPatch.getRelationshipTO() == null) {
561 LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch);
562 } else {
563 result.getRelationships().remove(relPatch.getRelationshipTO());
564 if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) {
565 result.getRelationships().add(relPatch.getRelationshipTO());
566 }
567 }
568 });
569
570
571 userUR.getMemberships().forEach(membPatch -> {
572 if (membPatch.getGroup() == null) {
573 LOG.warn("Invalid {} specified: {}", MembershipUR.class.getName(), membPatch);
574 } else {
575 result.getMemberships().stream().
576 filter(membership -> membPatch.getGroup().equals(membership.getGroupKey())).
577 findFirst().ifPresent(memb -> result.getMemberships().remove(memb));
578
579 if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
580 MembershipTO newMembershipTO = new MembershipTO.Builder(membPatch.getGroup()).
581
582 plainAttrs(membPatch.getPlainAttrs()).
583
584 virAttrs(membPatch.getVirAttrs()).
585 build();
586
587 result.getMemberships().add(newMembershipTO);
588 }
589 }
590 });
591
592
593 for (StringPatchItem rolePatch : userUR.getRoles()) {
594 switch (rolePatch.getOperation()) {
595 case ADD_REPLACE:
596 result.getRoles().add(rolePatch.getValue());
597 break;
598
599 case DELETE:
600 default:
601 result.getRoles().remove(rolePatch.getValue());
602 }
603 }
604
605
606 userUR.getLinkedAccounts().forEach(accountPatch -> {
607 if (accountPatch.getLinkedAccountTO() == null) {
608 LOG.warn("Invalid {} specified: {}", LinkedAccountUR.class.getName(), accountPatch);
609 } else {
610 result.getLinkedAccounts().remove(accountPatch.getLinkedAccountTO());
611 if (accountPatch.getOperation() == PatchOperation.ADD_REPLACE) {
612 result.getLinkedAccounts().add(accountPatch.getLinkedAccountTO());
613 }
614 }
615 });
616
617 return result;
618 }
619
620
621
622
623
624
625
626 public static void cleanEmptyAttrs(final AnyTO anyTO, final AnyUR anyUR) {
627 anyUR.getPlainAttrs().addAll(anyTO.getPlainAttrs().stream().
628 filter(AnyOperations::isEmpty).
629 map(plainAttr -> new AttrPatch.Builder(new Attr.Builder(plainAttr.getSchema()).build()).
630 operation(PatchOperation.DELETE).
631 build()).collect(Collectors.toSet()));
632 }
633
634 private static boolean isEmpty(final Attr attr) {
635 return attr.getValues().isEmpty() || NULL_SINGLETON_LIST.equals(attr.getValues());
636 }
637 }