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 result.getMemberships().add(membershipPatch);
202 });
203
204 if (!incremental) {
205 originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership)).
206 forEach(key -> result.getMemberships().add(
207 new MembershipUR.Builder(originalMembs.get(key).getGroupKey()).
208 operation(PatchOperation.DELETE).build()));
209 }
210
211 return result;
212 }
213
214 private static void diff(
215 final MembershipTO updated,
216 final MembershipUR result) {
217
218
219 result.getPlainAttrs().addAll(updated.getPlainAttrs().stream().
220 filter(attr -> !isEmpty(attr)).
221 collect(Collectors.toSet()));
222
223
224 result.getVirAttrs().clear();
225 result.getVirAttrs().addAll(updated.getVirAttrs());
226 }
227
228
229
230
231
232
233
234
235
236 public static UserUR diff(final UserTO updated, final UserTO original, final boolean incremental) {
237 UserUR result = new UserUR();
238
239 diff(updated, original, result, incremental);
240
241
242 if (updated.getPassword() != null
243 && (original.getPassword() == null || !original.getPassword().equals(updated.getPassword()))) {
244
245 result.setPassword(new PasswordPatch.Builder().
246 value(updated.getPassword()).
247 resources(updated.getResources()).build());
248 }
249
250
251 result.setUsername(
252 replacePatchItem(updated.getUsername(), original.getUsername(), new StringReplacePatchItem()));
253
254
255 if (updated.getSecurityQuestion() == null) {
256 result.setSecurityQuestion(null);
257 result.setSecurityAnswer(null);
258 } else if (!updated.getSecurityQuestion().equals(original.getSecurityQuestion())
259 || StringUtils.isNotBlank(updated.getSecurityAnswer())) {
260
261 result.setSecurityQuestion(new StringReplacePatchItem.Builder().
262 value(updated.getSecurityQuestion()).build());
263 result.setSecurityAnswer(
264 new StringReplacePatchItem.Builder().value(updated.getSecurityAnswer()).build());
265 }
266
267 result.setMustChangePassword(replacePatchItem(
268 updated.isMustChangePassword(), original.isMustChangePassword(), new BooleanReplacePatchItem()));
269
270
271 if (!incremental) {
272 original.getRoles().stream().filter(role -> !updated.getRoles().contains(role)).
273 forEach(toRemove -> result.getRoles().add(new StringPatchItem.Builder().
274 operation(PatchOperation.DELETE).value(toRemove).build()));
275 }
276
277 updated.getRoles().stream().filter(role -> !original.getRoles().contains(role)).
278 forEach(toAdd -> result.getRoles().add(new StringPatchItem.Builder().
279 operation(PatchOperation.ADD_REPLACE).value(toAdd).build()));
280
281
282 Map<Pair<String, String>, RelationshipTO> updatedRels =
283 EntityTOUtils.buildRelationshipMap(updated.getRelationships());
284 Map<Pair<String, String>, RelationshipTO> originalRels =
285 EntityTOUtils.buildRelationshipMap(original.getRelationships());
286
287 updatedRels.entrySet().stream().
288 filter(entry -> (!originalRels.containsKey(entry.getKey()))).
289 forEach(entry -> result.getRelationships().add(new RelationshipUR.Builder(entry.getValue()).
290 operation(PatchOperation.ADD_REPLACE).build()));
291
292 if (!incremental) {
293 originalRels.keySet().stream().filter(relationship -> !updatedRels.containsKey(relationship)).
294 forEach(key -> result.getRelationships().add(new RelationshipUR.Builder(originalRels.get(key)).
295 operation(PatchOperation.DELETE).build()));
296 }
297
298
299 Map<String, MembershipTO> updatedMembs = EntityTOUtils.buildMembershipMap(updated.getMemberships());
300 Map<String, MembershipTO> originalMembs = EntityTOUtils.buildMembershipMap(original.getMemberships());
301
302 updatedMembs.forEach((key, value) -> {
303 MembershipUR membershipPatch = new MembershipUR.Builder(value.getGroupKey()).
304 operation(PatchOperation.ADD_REPLACE).build();
305
306 diff(value, membershipPatch);
307 result.getMemberships().add(membershipPatch);
308 });
309
310 if (!incremental) {
311 originalMembs.keySet().stream().filter(membership -> !updatedMembs.containsKey(membership))
312 .forEach(key -> result.getMemberships()
313 .add(new MembershipUR.Builder(originalMembs.get(key).getGroupKey())
314 .operation(PatchOperation.DELETE).build()));
315 }
316
317
318 Map<Pair<String, String>, LinkedAccountTO> updatedAccounts =
319 EntityTOUtils.buildLinkedAccountMap(updated.getLinkedAccounts());
320 Map<Pair<String, String>, LinkedAccountTO> originalAccounts =
321 EntityTOUtils.buildLinkedAccountMap(original.getLinkedAccounts());
322
323 updatedAccounts.entrySet().stream().
324 forEachOrdered(entry -> {
325 result.getLinkedAccounts().add(new LinkedAccountUR.Builder().
326 operation(PatchOperation.ADD_REPLACE).
327 linkedAccountTO(entry.getValue()).build());
328 });
329
330 if (!incremental) {
331 originalAccounts.keySet().stream().filter(account -> !updatedAccounts.containsKey(account)).
332 forEach(key -> {
333 result.getLinkedAccounts().add(new LinkedAccountUR.Builder().
334 operation(PatchOperation.DELETE).
335 linkedAccountTO(originalAccounts.get(key)).build());
336 });
337 }
338
339 return result;
340 }
341
342
343
344
345
346
347
348
349
350 public static GroupUR diff(final GroupTO updated, final GroupTO original, final boolean incremental) {
351 GroupUR result = new GroupUR();
352
353 diff(updated, original, result, incremental);
354
355
356 result.setName(replacePatchItem(updated.getName(), original.getName(), new StringReplacePatchItem()));
357
358
359 result.setUserOwner(
360 replacePatchItem(updated.getUserOwner(), original.getUserOwner(), new StringReplacePatchItem()));
361 result.setGroupOwner(
362 replacePatchItem(updated.getGroupOwner(), original.getGroupOwner(), new StringReplacePatchItem()));
363
364
365 result.setUDynMembershipCond(updated.getUDynMembershipCond());
366 result.getADynMembershipConds().putAll(updated.getADynMembershipConds());
367
368
369 result.getTypeExtensions().addAll(updated.getTypeExtensions());
370
371 return result;
372 }
373
374 @SuppressWarnings("unchecked")
375 public static <TO extends AnyTO, P extends AnyUR> P diff(
376 final TO updated, final TO original, final boolean incremental) {
377
378 if (updated instanceof UserTO && original instanceof UserTO) {
379 return (P) diff((UserTO) updated, (UserTO) original, incremental);
380 } else if (updated instanceof GroupTO && original instanceof GroupTO) {
381 return (P) diff((GroupTO) updated, (GroupTO) original, incremental);
382 } else if (updated instanceof AnyObjectTO && original instanceof AnyObjectTO) {
383 return (P) diff((AnyObjectTO) updated, (AnyObjectTO) original, incremental);
384 }
385
386 throw new IllegalArgumentException("Unsupported: " + updated.getClass().getName());
387 }
388
389 private static Collection<Attr> patch(final Map<String, Attr> attrs, final Set<AttrPatch> attrPatches) {
390 Map<String, Attr> rwattrs = new HashMap<>(attrs);
391 attrPatches.forEach(patch -> {
392 if (patch.getAttr() == null) {
393 LOG.warn("Invalid {} specified: {}", AttrPatch.class.getName(), patch);
394 } else {
395 rwattrs.remove(patch.getAttr().getSchema());
396 if (patch.getOperation() == PatchOperation.ADD_REPLACE && !patch.getAttr().getValues().isEmpty()) {
397 rwattrs.put(patch.getAttr().getSchema(), patch.getAttr());
398 }
399 }
400 });
401
402 return rwattrs.values();
403 }
404
405 private static <T extends AnyTO, K extends AnyUR> void patch(final T to, final K req, final T result) {
406
407 if (to.getKey() == null || !to.getKey().equals(req.getKey())) {
408 throw new IllegalArgumentException(
409 to.getClass().getSimpleName() + " and "
410 + req.getClass().getSimpleName() + " keys must be the same");
411 }
412
413
414 if (req.getRealm() != null) {
415 result.setRealm(req.getRealm().getValue());
416 }
417
418
419 for (StringPatchItem auxClassPatch : req.getAuxClasses()) {
420 switch (auxClassPatch.getOperation()) {
421 case ADD_REPLACE:
422 result.getAuxClasses().add(auxClassPatch.getValue());
423 break;
424
425 case DELETE:
426 default:
427 result.getAuxClasses().remove(auxClassPatch.getValue());
428 }
429 }
430
431
432 result.getPlainAttrs().clear();
433 result.getPlainAttrs().addAll(patch(EntityTOUtils.buildAttrMap(to.getPlainAttrs()), req.getPlainAttrs()));
434
435
436 result.getVirAttrs().clear();
437 result.getVirAttrs().addAll(req.getVirAttrs());
438
439
440 for (StringPatchItem resourcePatch : req.getResources()) {
441 switch (resourcePatch.getOperation()) {
442 case ADD_REPLACE:
443 result.getResources().add(resourcePatch.getValue());
444 break;
445
446 case DELETE:
447 default:
448 result.getResources().remove(resourcePatch.getValue());
449 }
450 }
451 }
452
453 public static AnyTO patch(final AnyTO anyTO, final AnyUR anyUR) {
454 if (anyTO instanceof UserTO) {
455 return patch((UserTO) anyTO, (UserUR) anyUR);
456 }
457 if (anyTO instanceof GroupTO) {
458 return patch((GroupTO) anyTO, (GroupUR) anyUR);
459 }
460 if (anyTO instanceof AnyObjectTO) {
461 return patch((AnyObjectTO) anyTO, (AnyObjectUR) anyUR);
462 }
463 return null;
464 }
465
466 public static GroupTO patch(final GroupTO groupTO, final GroupUR groupUR) {
467 GroupTO result = SerializationUtils.clone(groupTO);
468 patch(groupTO, groupUR, result);
469
470 if (groupUR.getName() != null) {
471 result.setName(groupUR.getName().getValue());
472 }
473
474 if (groupUR.getUserOwner() != null) {
475 result.setGroupOwner(groupUR.getUserOwner().getValue());
476 }
477 if (groupUR.getGroupOwner() != null) {
478 result.setGroupOwner(groupUR.getGroupOwner().getValue());
479 }
480
481 result.setUDynMembershipCond(groupUR.getUDynMembershipCond());
482 result.getADynMembershipConds().clear();
483 result.getADynMembershipConds().putAll(groupUR.getADynMembershipConds());
484
485 return result;
486 }
487
488 public static AnyObjectTO patch(final AnyObjectTO anyObjectTO, final AnyObjectUR anyObjectUR) {
489 AnyObjectTO result = SerializationUtils.clone(anyObjectTO);
490 patch(anyObjectTO, anyObjectUR, result);
491
492 if (anyObjectUR.getName() != null) {
493 result.setName(anyObjectUR.getName().getValue());
494 }
495
496
497 anyObjectUR.getRelationships().
498 forEach(relPatch -> {
499 if (relPatch.getRelationshipTO() == null) {
500 LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch);
501 } else {
502 result.getRelationships().remove(relPatch.getRelationshipTO());
503 if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) {
504 result.getRelationships().add(relPatch.getRelationshipTO());
505 }
506 }
507 });
508
509
510 anyObjectUR.getMemberships().forEach(membPatch -> {
511 if (membPatch.getGroup() == null) {
512 LOG.warn("Invalid {} specified: {}", MembershipUR.class.getName(), membPatch);
513 } else {
514 result.getMemberships().stream().
515 filter(membership -> membPatch.getGroup().equals(membership.getGroupKey())).
516 findFirst().ifPresent(memb -> result.getMemberships().remove(memb));
517
518 if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
519 MembershipTO newMembershipTO = new MembershipTO.Builder(membPatch.getGroup()).
520
521 plainAttrs(membPatch.getPlainAttrs()).
522
523 virAttrs(membPatch.getVirAttrs()).
524 build();
525
526 result.getMemberships().add(newMembershipTO);
527 }
528 }
529 });
530
531 return result;
532 }
533
534 public static UserTO patch(final UserTO userTO, final UserUR userUR) {
535 UserTO result = SerializationUtils.clone(userTO);
536 patch(userTO, userUR, result);
537
538
539 if (userUR.getPassword() != null) {
540 result.setPassword(userUR.getPassword().getValue());
541 }
542
543
544 if (userUR.getUsername() != null) {
545 result.setUsername(userUR.getUsername().getValue());
546 }
547
548
549 userUR.getRelationships().forEach(relPatch -> {
550 if (relPatch.getRelationshipTO() == null) {
551 LOG.warn("Invalid {} specified: {}", RelationshipUR.class.getName(), relPatch);
552 } else {
553 result.getRelationships().remove(relPatch.getRelationshipTO());
554 if (relPatch.getOperation() == PatchOperation.ADD_REPLACE) {
555 result.getRelationships().add(relPatch.getRelationshipTO());
556 }
557 }
558 });
559
560
561 userUR.getMemberships().forEach(membPatch -> {
562 if (membPatch.getGroup() == null) {
563 LOG.warn("Invalid {} specified: {}", MembershipUR.class.getName(), membPatch);
564 } else {
565 result.getMemberships().stream().
566 filter(membership -> membPatch.getGroup().equals(membership.getGroupKey())).
567 findFirst().ifPresent(memb -> result.getMemberships().remove(memb));
568
569 if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
570 MembershipTO newMembershipTO = new MembershipTO.Builder(membPatch.getGroup()).
571
572 plainAttrs(membPatch.getPlainAttrs()).
573
574 virAttrs(membPatch.getVirAttrs()).
575 build();
576
577 result.getMemberships().add(newMembershipTO);
578 }
579 }
580 });
581
582
583 for (StringPatchItem rolePatch : userUR.getRoles()) {
584 switch (rolePatch.getOperation()) {
585 case ADD_REPLACE:
586 result.getRoles().add(rolePatch.getValue());
587 break;
588
589 case DELETE:
590 default:
591 result.getRoles().remove(rolePatch.getValue());
592 }
593 }
594
595
596 userUR.getLinkedAccounts().forEach(accountPatch -> {
597 if (accountPatch.getLinkedAccountTO() == null) {
598 LOG.warn("Invalid {} specified: {}", LinkedAccountUR.class.getName(), accountPatch);
599 } else {
600 result.getLinkedAccounts().remove(accountPatch.getLinkedAccountTO());
601 if (accountPatch.getOperation() == PatchOperation.ADD_REPLACE) {
602 result.getLinkedAccounts().add(accountPatch.getLinkedAccountTO());
603 }
604 }
605 });
606
607 return result;
608 }
609
610
611
612
613
614
615
616 public static void cleanEmptyAttrs(final AnyTO anyTO, final AnyUR anyUR) {
617 anyUR.getPlainAttrs().addAll(anyTO.getPlainAttrs().stream().
618 filter(AnyOperations::isEmpty).
619 map(plainAttr -> new AttrPatch.Builder(new Attr.Builder(plainAttr.getSchema()).build()).
620 operation(PatchOperation.DELETE).
621 build()).collect(Collectors.toSet()));
622 }
623
624 private static boolean isEmpty(final Attr attr) {
625 return attr.getValues().isEmpty() || NULL_SINGLETON_LIST.equals(attr.getValues());
626 }
627 }