1 package edu.internet2.middleware.grouper.pspng;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Date;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31 import java.util.concurrent.Callable;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.Future;
36 import java.util.concurrent.ThreadFactory;
37 import java.util.concurrent.atomic.AtomicInteger;
38 import java.util.concurrent.atomic.AtomicReference;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 import org.apache.commons.lang.BooleanUtils;
43 import org.apache.commons.lang.StringUtils;
44 import org.apache.log4j.MDC;
45 import org.joda.time.DateTime;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import edu.internet2.middleware.grouper.Group;
50 import edu.internet2.middleware.grouper.GroupFinder;
51 import edu.internet2.middleware.grouper.GrouperSession;
52 import edu.internet2.middleware.grouper.Member;
53 import edu.internet2.middleware.grouper.Stem;
54 import edu.internet2.middleware.grouper.Stem.Scope;
55 import edu.internet2.middleware.grouper.StemFinder;
56 import edu.internet2.middleware.grouper.StemSave;
57 import edu.internet2.middleware.grouper.SubjectFinder;
58 import edu.internet2.middleware.grouper.app.loader.GrouperLoaderConfig;
59 import edu.internet2.middleware.grouper.attr.AttributeDef;
60 import edu.internet2.middleware.grouper.attr.AttributeDefType;
61 import edu.internet2.middleware.grouper.attr.AttributeDefValueType;
62 import edu.internet2.middleware.grouper.audit.GrouperEngineBuiltin;
63 import edu.internet2.middleware.grouper.cache.GrouperCache;
64 import edu.internet2.middleware.grouper.cfg.GrouperConfig;
65 import edu.internet2.middleware.grouper.changeLog.ChangeLogEntry;
66 import edu.internet2.middleware.grouper.changeLog.ChangeLogLabels;
67 import edu.internet2.middleware.grouper.changeLog.ChangeLogTypeBuiltin;
68 import edu.internet2.middleware.grouper.exception.GroupNotFoundException;
69 import edu.internet2.middleware.grouper.hibernate.GrouperContext;
70 import edu.internet2.middleware.grouper.internal.dao.QueryOptions;
71 import edu.internet2.middleware.grouper.misc.GrouperCheckConfig;
72 import edu.internet2.middleware.grouper.misc.GrouperDAOFactory;
73 import edu.internet2.middleware.grouper.pit.PITGroup;
74 import edu.internet2.middleware.grouper.pit.finder.PITGroupFinder;
75 import edu.internet2.middleware.grouper.util.GrouperUtil;
76 import edu.internet2.middleware.grouperClient.collections.MultiKey;
77 import edu.internet2.middleware.grouperClient.jdbc.GcDbAccess;
78 import edu.internet2.middleware.grouperClient.util.ExpirableCache;
79 import edu.internet2.middleware.subject.Subject;
80 import edu.internet2.middleware.subject.provider.SubjectTypeEnum;
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public abstract class Provisioner
128 <ConfigurationClass extends ProvisionerConfiguration,
129 TSUserClass extends TargetSystemUser,
130 TSGroupClass extends TargetSystemGroup> {
131 private static final String DO_NOT_PROVISION_TO_ATTRIBUTE = "do_not_provision_to";
132
133 private static final String PROVISION_TO_ATTRIBUTE = "provision_to";
134
135 static final Logger STATIC_LOG = LoggerFactory.getLogger(Provisioner.class);
136
137 protected final Logger LOG;
138
139
140
141
142 private JobStatistics jobStatistics;
143
144
145
146
147
148 public JobStatistics getJobStatistics() {
149 return jobStatistics;
150 }
151
152
153
154
155
156 public void setJobStatistics(JobStatistics jobStatistics) {
157 this.jobStatistics = jobStatistics;
158 }
159
160
161
162 final public String provisionerDisplayName;
163
164
165
166
167 final public String provisionerConfigName;
168
169
170 final GrouperCache<String, GrouperGroupInfo> grouperGroupInfoCache;
171
172
173 final GrouperCache<String, Subject> grouperSubjectCache;
174
175
176
177
178 final PspDatedCache<Subject, TSUserClass> targetSystemUserCache;
179
180
181
182
183
184
185
186
187
188
189 private Map<Subject, TSUserClass> tsUserCache_shortTerm = new HashMap<Subject, TSUserClass>();
190
191
192
193
194
195 final GrouperCache<GrouperGroupInfo, TSGroupClass> targetSystemGroupCache;
196
197
198
199
200
201
202
203
204
205
206
207 private Map<GrouperGroupInfo, TSGroupClass> tsGroupCache_shortTerm = new HashMap<GrouperGroupInfo, TSGroupClass>();
208
209
210
211 private ThreadLocal<ProvisioningWorkItem> currentWorkItem = new ThreadLocal<ProvisioningWorkItem>();
212
213
214
215
216
217
218 private AtomicReference<Set<GrouperGroupInfo>> selectedGroups = new AtomicReference<>();
219
220
221
222
223
224 protected final boolean fullSyncMode;
225
226 final protected ConfigurationClass config;
227
228 final private ExecutorService tsUserFetchingService;
229
230
231 public static final ThreadLocal<Provisioner> activeProvisioner = new ThreadLocal<>();
232
233
234 Provisioner(String provisionerConfigName, ConfigurationClass config, boolean fullSyncMode) {
235 this.provisionerConfigName = provisionerConfigName;
236
237 if (fullSyncMode) {
238 this.provisionerDisplayName = provisionerConfigName + "-full";
239 } else {
240 this.provisionerDisplayName = provisionerConfigName;
241 }
242 this.fullSyncMode = fullSyncMode;
243 LOG = LoggerFactory.getLogger(String.format("%s.%s", getClass().getName(), provisionerDisplayName));
244
245 this.config = config;
246
247 checkAttributeDefinitions();
248
249
250
251 grouperGroupInfoCache
252 = new GrouperCache<String, GrouperGroupInfo>(String.format("PSP-%s-GrouperGroupInfoCache", getConfigName()),
253 config.getGrouperGroupCacheSize() > 1 ? config.getGrouperGroupCacheSize() : 1,
254 false,
255 config.getDataCacheTime_secs(),
256 config.getDataCacheTime_secs(),
257 false);
258
259 grouperSubjectCache
260 = new GrouperCache<String, Subject>(String.format("PSP-%s-GrouperSubjectCache", getConfigName()),
261 config.getGrouperSubjectCacheSize() > 1 ? config.getGrouperSubjectCacheSize() : 1,
262 false,
263 config.getDataCacheTime_secs(),
264 config.getDataCacheTime_secs(),
265 false);
266
267 targetSystemUserCache
268 = new PspDatedCache<Subject, TSUserClass>(String.format("PSP-%s-TargetSystemUserCache", getConfigName()),
269 config.getTargetSystemUserCacheSize() > 1 ? config.getTargetSystemUserCacheSize() : 1,
270 false,
271 config.getDataCacheTime_secs(),
272 config.getDataCacheTime_secs(),
273 false);
274
275
276
277 targetSystemGroupCache
278 = new GrouperCache<GrouperGroupInfo, TSGroupClass>(String.format("PSP-%s-TargetSystemGroupCache", getDisplayName()),
279 config.getTargetSystemGroupCacheSize() > 1 ? config.getTargetSystemGroupCacheSize() : 1,
280 false,
281 config.getDataCacheTime_secs(),
282 config.getDataCacheTime_secs(),
283 false);
284
285 if ( config.needsTargetSystemUsers() ) {
286 tsUserFetchingService = Executors.newFixedThreadPool(
287 config.getNumberOfDataFetchingWorkers(),
288 new ThreadFactory() {
289 AtomicInteger counter = new AtomicInteger(1);
290 @Override
291 public Thread newThread(Runnable r) {
292 Thread thread = new Thread(r, String.format("TSUserFetcher-%s-%d", getDisplayName(), counter.getAndIncrement()));
293 thread.setDaemon(true);
294 return thread;
295 }
296 });
297 } else {
298 tsUserFetchingService=null;
299 }
300
301 selectedGroups.set(getAllGroupsForProvisioner());
302 }
303
304
305
306
307
308 public static void checkAttributeDefinitions() {
309 GrouperSession grouperSession = GrouperSession.staticGrouperSession();
310 if ( grouperSession == null )
311 grouperSession = GrouperSession.startRootSession();
312
313
314
315 String pspngManagementStemName = GrouperConfig.retrieveConfig().propertyValueString(
316 "grouper.rootStemForBuiltinObjects", "etc") + ":pspng";
317
318 Stem pspngManagementStem = StemFinder.findByName(grouperSession, pspngManagementStemName, false);
319 if (pspngManagementStem == null) {
320 pspngManagementStem = new StemSave(grouperSession).assignCreateParentStemsIfNotExist(true)
321 .assignDescription("Location for pspng-management objects.")
322 .assignName(pspngManagementStemName).save();
323 }
324
325
326 String provisionToDefName = pspngManagementStemName + ":" + PROVISION_TO_ATTRIBUTE + "_def";
327 AttributeDef provisionToDef = GrouperDAOFactory.getFactory().getAttributeDef().findByNameSecure(
328 provisionToDefName, false, new QueryOptions().secondLevelCache(false));
329 if (provisionToDef == null) {
330 provisionToDef = pspngManagementStem.addChildAttributeDef(PROVISION_TO_ATTRIBUTE + "_def", AttributeDefType.type);
331 provisionToDef.setAssignToGroup(true);
332 provisionToDef.setAssignToStem(true);
333 provisionToDef.setMultiAssignable(true);
334 provisionToDef.setValueType(AttributeDefValueType.string);
335 provisionToDef.store();
336 }
337
338
339 String doNotProvisionToDefName = pspngManagementStemName + ":" + DO_NOT_PROVISION_TO_ATTRIBUTE + "_def";
340 AttributeDef doNotProvisionToDef = GrouperDAOFactory.getFactory().getAttributeDef().findByNameSecure(
341 doNotProvisionToDefName, false, new QueryOptions().secondLevelCache(false));
342 if (doNotProvisionToDef == null) {
343 doNotProvisionToDef = pspngManagementStem.addChildAttributeDef(DO_NOT_PROVISION_TO_ATTRIBUTE+"_def", AttributeDefType.type);
344 doNotProvisionToDef.setAssignToGroup(true);
345 doNotProvisionToDef.setAssignToStem(true);
346 doNotProvisionToDef.setMultiAssignable(true);
347 doNotProvisionToDef.setValueType(AttributeDefValueType.string);
348 doNotProvisionToDef.store();
349 }
350
351 GrouperCheckConfig.checkAttribute(pspngManagementStem, provisionToDef, PROVISION_TO_ATTRIBUTE, PROVISION_TO_ATTRIBUTE, "Defines what provisioners should process a group or groups within a folder", true);
352 GrouperCheckConfig.checkAttribute(pspngManagementStem, doNotProvisionToDef, DO_NOT_PROVISION_TO_ATTRIBUTE, DO_NOT_PROVISION_TO_ATTRIBUTE, "Defines what provisioners should not process a group or groups within a folder. Since the default is already for provisioners to not provision any groups, this attribute is to override a provision_to attribute set on an ancestor folder. ", true);
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370 protected abstract void addMembership(GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup,
371 Subject subject, TSUserClass tsUser) throws PspException;
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387 protected abstract void deleteMembership(GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup,
388 Subject subject, TSUserClass tsUser) throws PspException;
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 protected abstract TSGroupClass createGroup(GrouperGroupInfo grouperGroup, Collection<Subject> initialMembers) throws PspException;
406
407
408
409
410
411
412
413
414
415
416 protected abstract void
417 deleteGroup(GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup) throws PspException;
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 protected abstract boolean doFullSync(
443 GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup,
444 Set<Subject> correctSubjects, Map<Subject, TSUserClass> tsUserMap, Set<TSUserClass> correctTSUsers,
445 JobStatistics stats)
446 throws PspException;
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462 protected abstract void doFullSync_cleanupExtraGroups(JobStatistics stats) throws PspException;
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477 protected abstract Map<GrouperGroupInfo, TSGroupClass>
478 fetchTargetSystemGroups(Collection<GrouperGroupInfo> grouperGroups) throws PspException;
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494 protected abstract Map<Subject, TSUserClass>
495 fetchTargetSystemUsers(Collection<Subject> personSubjects) throws PspException;
496
497
498
499
500 private static Map<String, ExpirableCache<String, MultiKey>> configIdToGroupNameToMillisAndProvisionable = new HashMap<String, ExpirableCache<String, MultiKey>>();
501
502 public static ExpirableCache<String, MultiKey> groupNameToMillisAndProvisionable(String provisionerConfigId) {
503
504 ExpirableCache<String, MultiKey> cache = configIdToGroupNameToMillisAndProvisionable.get(provisionerConfigId);
505 if (cache == null) {
506 cache = new ExpirableCache<String, MultiKey>(10);
507 configIdToGroupNameToMillisAndProvisionable.put(provisionerConfigId, cache);
508 }
509
510 return cache;
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529 public List<ProvisioningWorkItem>
530 filterWorkItems2(List<ProvisioningWorkItem> workItems) throws PspException {
531
532 List<ProvisioningWorkItem> result = new ArrayList<ProvisioningWorkItem>();
533
534 LOG.debug("Filtering provisioning batch of {} items", workItems.size());
535
536 boolean pspngCacheGroupProvisionable = GrouperLoaderConfig.retrieveConfig().propertyValueBoolean("pspngCacheGroupProvisionable", true);
537
538 Set<String> attributesUsedInProvisioning = new HashSet<String>(GrouperUtil.nonNull(getConfig().getAttributesUsedInGroupSelectionExpression()));
539
540 ExpirableCache<String, MultiKey> groupNameToMillisAndProvisionable = groupNameToMillisAndProvisionable(this.provisionerConfigName);
541
542 for ( ProvisioningWorkItem workItem : workItems ) {
543
544 String groupNameForCache = null;
545 Boolean provisionableFromCache = null;
546 if (pspngCacheGroupProvisionable) {
547 groupNameForCache = workItem.getGroupName();
548
549 final String attributeName = workItem.getAttributeName();
550
551
552 if (!StringUtils.isBlank(attributeName) && attributesUsedInProvisioning.contains(attributeName)) {
553
554
555 groupNameToMillisAndProvisionable.clear();
556 } else {
557
558
559 if (!StringUtils.isBlank(groupNameForCache)) {
560
561
562 MultiKey millisProvisionable = groupNameToMillisAndProvisionable.get(groupNameForCache);
563 ChangeLogEntry changeLogEntry = workItem.getChangelogEntry();
564
565
566 Long millisFromChangeLogEntry = changeLogEntry == null ? null : (changeLogEntry.getCreatedOnDb() / 1000);
567
568 if (millisProvisionable != null && millisFromChangeLogEntry != null) {
569
570
571 long millisProvisionableCacheDecision = (Long)millisProvisionable.getKey(0);
572
573
574 if (millisFromChangeLogEntry < millisProvisionableCacheDecision) {
575 provisionableFromCache = (Boolean)millisProvisionable.getKey(1);
576
577
578 if (!provisionableFromCache) {
579 workItem.markAsSkipped("Ignoring work item due to cached decision");
580 continue;
581 }
582
583 }
584 }
585 }
586 }
587 }
588
589 GrouperGroupInfo group = workItem.getGroupInfo(this);
590 final long millisWhenProvisionableDecisionMade = System.currentTimeMillis();
591
592
593
594 if ( group != null && provisionableFromCache == null ) {
595 if ( !group.hasGroupBeenDeleted() && !shouldGroupBeProvisioned(group)) {
596 workItem.markAsSkipped("Ignoring work item because (existing) group should not be provisioned");
597
598
599 if (!StringUtils.isBlank(groupNameForCache)) {
600 groupNameToMillisAndProvisionable.put(groupNameForCache, new MultiKey(millisWhenProvisionableDecisionMade, false));
601 }
602 continue;
603 }
604
605 if ( group.hasGroupBeenDeleted() && !selectedGroups.get().contains(group) ) {
606 workItem.markAsSkippedAndWarn("Ignoring work item because (deleted) group was not provisioned before it was deleted");
607
608 if (!StringUtils.isBlank(groupNameForCache)) {
609 groupNameToMillisAndProvisionable.put(groupNameForCache, new MultiKey(millisWhenProvisionableDecisionMade, false));
610 }
611 continue;
612 }
613 }
614
615 if (!StringUtils.isBlank(groupNameForCache) && provisionableFromCache == null) {
616
617 groupNameToMillisAndProvisionable.put(groupNameForCache, new MultiKey(millisWhenProvisionableDecisionMade, true));
618 }
619
620
621 if ( shouldWorkItemBeProcessed(workItem) ) {
622 result.add(workItem);
623 } else {
624
625
626 workItem.markAsSkipped("Ignoring work item");
627 }
628 }
629
630 return result;
631 }
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647 public List<ProvisioningWorkItem>
648 filterWorkItems(List<ProvisioningWorkItem> workItems) throws PspException {
649
650 boolean pspngCacheGroupProvisionable = GrouperLoaderConfig.retrieveConfig().propertyValueBoolean("pspngCacheGroupProvisionable", true);
651 if (pspngCacheGroupProvisionable) {
652 return filterWorkItems2(workItems);
653 }
654 List<ProvisioningWorkItem> result = new ArrayList<ProvisioningWorkItem>();
655
656 LOG.debug("Filtering provisioning batch of {} items", workItems.size());
657
658 for ( ProvisioningWorkItem workItem : workItems ) {
659 GrouperGroupInfo group = workItem.getGroupInfo(this);
660
661
662 if ( group != null ) {
663 if ( !group.hasGroupBeenDeleted() && !shouldGroupBeProvisioned(group)) {
664 workItem.markAsSkipped("Ignoring work item because (existing) group should not be provisioned");
665 continue;
666 }
667
668 if ( group.hasGroupBeenDeleted() && !selectedGroups.get().contains(group) ) {
669 workItem.markAsSkippedAndWarn("Ignoring work item because (deleted) group was not provisioned before it was deleted");
670 continue;
671 }
672 }
673
674 if ( shouldWorkItemBeProcessed(workItem) ) {
675 result.add(workItem);
676 } else {
677
678 workItem.markAsSkipped("Ignoring work item");
679 }
680 }
681
682 return result;
683 }
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701 protected boolean shouldWorkItemBeProcessed(ProvisioningWorkItem workItem) {
702 if ( workItem.isSubjectUnresolvable(this) ) {
703 workItem.markAsSkippedAndWarn("Subject is unresolvable");
704 return false;
705 }
706
707
708
709 if ( getConfig().areChangesToInternalGrouperSubjectsIgnored() ) {
710 Subject subject = workItem.getSubject(this);
711 if ( subject != null && subject.getSourceId().equalsIgnoreCase("g:gsa") ) {
712 workItem.markAsSkipped("Ignoring event about a g:gsa subject");
713 return false;
714 }
715 }
716
717
718
719
720 if ( workItem.getGroupInfo(this) != null &&
721 workItem.getGroupInfo(this).hasGroupBeenDeleted() ) {
722 if (workItem.matchesChangelogType(ChangeLogTypeBuiltin.GROUP_DELETE) ) {
723 LOG.debug("{}: Group has been deleted within grouper, so we're processing group-deletion event: {}", getDisplayName(), workItem);
724 } else {
725 workItem.markAsSkipped("Group has been deleted within Grouper. Skipping event because it is not the actual GROUP_DELETE event");
726 return false;
727 }
728 }
729
730 if ( !workItem.matchesChangelogType(ChangelogHandlingConfig.allRelevantChangelogTypes) ) {
731 workItem.markAsSkipped("Changelog type is not relevant to PSPNG provisioning (see ChangelogHandlingConfig.allRelevantChangelogTypes)");
732 return false;
733 }
734
735 return true;
736 }
737
738
739
740
741
742
743
744
745
746 public void startCoordination(List<ProvisioningWorkItem> workItems) {
747 for (ProvisioningWorkItem workItem : workItems) {
748 GrouperGroupInfo grouperGroupInfo = workItem.getGroupInfo(this);
749 if (grouperGroupInfo == null)
750
751 continue;
752
753 if (isFullSyncMode()) {
754 getProvisionerCoordinator().lockForFullSyncIfNoIncrementalIsUnderway(grouperGroupInfo);
755 } else {
756 getProvisionerCoordinator().lockForIncrementalProvisioningIfNoFullSyncIsUnderway(grouperGroupInfo);
757 }
758 }
759 }
760
761
762
763
764
765
766
767 public void finishCoordination(List<ProvisioningWorkItem> workItems, boolean wasSuccessful) {
768
769 for ( ProvisioningWorkItem workItem : workItems ) {
770 GrouperGroupInfo groupInfo = workItem.getGroupInfo(this);
771 if ( groupInfo != null ) {
772 if ( isFullSyncMode() ) {
773 getProvisionerCoordinator().unlockAfterFullSync(groupInfo, wasSuccessful);
774 }
775 else {
776 getProvisionerCoordinator().unlockAfterIncrementalProvisioning(groupInfo);
777 }
778 }
779 }
780 }
781
782
783
784
785
786
787
788
789
790 public void startProvisioningBatch(List<ProvisioningWorkItem> workItems) throws PspException {
791 Provisioner.activeProvisioner.set(this);
792 LOG.info("Starting provisioning batch of {} items", workItems.size());
793
794 warnAboutCacheSizeConcerns();
795
796 DateTime newestAsOfDate=null;
797 for ( ProvisioningWorkItem workItem : workItems) {
798 LOG.debug("-->Work item: {}", workItem);
799
800 if ( workItem.asOfDate!=null ) {
801 if ( workItem.groupName != null ) {
802 DateTime lastFullSync = getFullSyncer().getLastSuccessfulFullSyncDate(workItem.groupName);
803 if ( lastFullSync!=null && lastFullSync.isAfter(workItem.asOfDate) ) {
804 workItem.markAsSkipped("Change was covered by full sync: {}",
805 PspUtils.formatDate_DateHoursMinutes(lastFullSync,null));
806 continue;
807 }
808 }
809
810 if (newestAsOfDate == null || newestAsOfDate.isBefore(workItem.asOfDate)) {
811 newestAsOfDate = workItem.asOfDate;
812 }
813 }
814 }
815
816 LOG.info("Information cached before {} will be ignored", newestAsOfDate);
817
818 Set<Subject> subjects = new HashSet<Subject>();
819
820
821 Set<GrouperGroupInfo> grouperGroupInfos = new HashSet<GrouperGroupInfo>();
822
823 for ( ProvisioningWorkItem workItem : workItems) {
824 GrouperGroupInfo grouperGroupInfo = workItem.getGroupInfo(this);
825 if ( grouperGroupInfo == null ) {
826
827 continue;
828 }
829
830 grouperGroupInfos.add(grouperGroupInfo);
831
832 Subject s = workItem.getSubject(this);
833 if ( s != null )
834 subjects.add(s);
835 }
836
837 prepareGroupCache(grouperGroupInfos);
838 prepareUserCache(subjects, newestAsOfDate);
839 }
840
841 protected void warnAboutCacheSizeConcerns() {
842 warnAboutCacheSizeConcerns(grouperGroupInfoCache.getStats().getObjectCount(), config.getGrouperGroupCacheSize(), "grouper groups", "grouperGroupCacheSize");
843 warnAboutCacheSizeConcerns(targetSystemGroupCache.getStats().getObjectCount(), config.getTargetSystemGroupCacheSize(), "provisioned groups", "targetSystemGroupCacheSize");
844 warnAboutCacheSizeConcerns(grouperSubjectCache.getStats().getObjectCount(), config.getGrouperSubjectCacheSize(), "grouper subjects", "grouperSubjectCacheSize");
845 warnAboutCacheSizeConcerns(targetSystemUserCache.getStats().getObjectCount(), config.getTargetSystemUserCacheSize(), "provisioned subjects", "targetSystemUserCacheSize");
846 }
847
848 private void warnAboutCacheSizeConcerns(long cacheObjectCount, int configuredSize, String objectType, String configurationProperty) {
849 if ( !config.areCacheSizeWarningsEnabled() )
850 return;
851
852
853 if ( configuredSize <= 1 ) {
854 return;
855 }
856
857 double cacheWarningThreshold_percentage = config.getCacheFullnessWarningThreshold_percentage();
858 long cacheWarningTreshold_count = Math.round(cacheWarningThreshold_percentage*configuredSize);
859
860 long cacheFullness_percentage = Math.round(100.0*cacheObjectCount/configuredSize);
861
862 if ( cacheFullness_percentage >= cacheWarningThreshold_percentage ) {
863 LOG.warn("{}: Cache of {} is very full ({}%). Provisioning performance is much better if {} is big enough to hold all {}. " +
864 "({} is currently set to {}, and this warning occurs when cache is {}% full)",
865 getConfigName(),
866 objectType,
867 cacheFullness_percentage,
868 configurationProperty,
869 objectType,
870 configurationProperty, configuredSize,
871 cacheFullness_percentage);
872 } else {
873 LOG.debug("{}: Cache of {} is sufficiently sized ({}% full) (property {}={})",
874 getConfigName(), objectType, cacheFullness_percentage, configurationProperty, configuredSize);
875 }
876
877 }
878
879 private ProvisionerCoordinator getProvisionerCoordinator() {
880 return ProvisionerFactory.getProvisionerCoordinator(this);
881 }
882
883
884
885 public void finishProvisioningBatch(List<ProvisioningWorkItem> workItems) throws PspException {
886 tsUserCache_shortTerm.clear();
887 tsGroupCache_shortTerm.clear();
888
889 LOG.debug("Done with provisining batch");
890 }
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905 protected final String evaluateJexlExpression(String expressionName, String expression,
906 Subject subject, TSUserClass tsUser,
907 GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup,
908 Object... keysAndValues) throws PspException {
909
910 LOG.trace("Evaluating {} Jexl expression: {}", expressionName, expression);
911
912 Map<String, Object> variableMap = new HashMap<>();
913
914 variableMap.put("utils", new PspJexlUtils());
915
916
917 GrouperUtil.assertion(keysAndValues.length % 2 == 0, "KeysAndValues must be paired evenly");
918 for (int i=0; i<keysAndValues.length; i+=2)
919 variableMap.put(keysAndValues[i].toString(), keysAndValues[i+1]);
920
921
922 populateJexlMap(expression, variableMap,
923 subject,
924 tsUser,
925 grouperGroupInfo,
926 tsGroup);
927
928
929 config.populateElMap(variableMap);
930
931 try {
932
933
934
935
936
937 Pattern atomicExpressionPattern = Pattern.compile("\\$\\{([^$]|\\$[^{])*?\\}" );
938 String result=expression;
939 Matcher atomicExpressionMatcher = atomicExpressionPattern.matcher(result);
940
941 while ( atomicExpressionMatcher.find()) {
942 String atomicExpression = atomicExpressionMatcher.group();
943 String atomicExpressionResult;
944
945
946
947 if ( ! atomicExpression.contains(":-") ) {
948 if (atomicExpression.contains("idIndex") && !variableMap.containsKey("idIndex")) {
949 throw new DeletedGroupException("EL has idIndex but variable doesnt!");
950 }
951 atomicExpressionResult = GrouperUtil.substituteExpressionLanguage(atomicExpression, variableMap, true, false, false);
952 }
953 else {
954
955
956 String expressionOne = StringUtils.substringBefore(atomicExpression, ":-") + "}";
957 String expressionTwo = "${" + StringUtils.substringAfter(atomicExpression, ":-");
958
959 try {
960 atomicExpressionResult = GrouperUtil.substituteExpressionLanguage(expressionOne, variableMap, true, false, false);
961 } catch (RuntimeException e) {
962 LOG.warn("{}: Problem evaluating '{}'. Will try :- expression '{}': {}",
963 new Object[]{expressionName, expressionOne, expressionTwo, e.getMessage()});
964
965
966 atomicExpressionResult = GrouperUtil.substituteExpressionLanguage(expressionTwo, variableMap, true, false, false);
967 }
968 }
969 LOG.debug("Evaluated {} Jexl expression: '{}'", expressionName, atomicExpressionResult);
970 LOG.trace("Evaluated {} Jexl expression: '{}' FROM {} WITH variables {}",
971 new Object[]{expressionName, atomicExpressionResult, atomicExpression, variableMap});
972
973
974
975
976
977
978
979
980
981
982 result = atomicExpressionMatcher.replaceFirst(atomicExpressionResult.replaceAll("\\\\", "\\\\\\\\"));
983 atomicExpressionMatcher = atomicExpressionPattern.matcher(result);
984 }
985
986 if ( expression.equals(result) ) {
987 LOG.trace("{} Jexl expression did not include any substitutions: {}", expressionName, expression);
988 } else {
989 LOG.debug("Evaluated entire {} Jexl expression: '{}'", expressionName, result);
990 }
991
992 return result;
993 }
994 catch (RuntimeException e) {
995 LOG.error("Jexl Expression {} '{}' could not be evaluated for subject '{}/{}' and group '{}/{}' which used variableMap '{}'",
996 new Object[] {expressionName, expression,
997 subject, tsUser,
998 grouperGroupInfo, tsGroup,
999 variableMap, e});
1000 throw new PspException("Jexl evaluation failed: %s", e.getMessage());
1001 }
1002 }
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017 protected void populateJexlMap(String expression, Map<String, Object> variableMap, Subject subject,
1018 TSUserClass tsUser, GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup) {
1019 variableMap.put("provisionerType", getClass().getSimpleName());
1020 variableMap.put("provisionerName", getDisplayName());
1021
1022 if ( subject != null )
1023 variableMap.put("subject", subject);
1024
1025 if ( tsUser != null )
1026 variableMap.put("tsUser", tsUser.getJexlMap());
1027
1028 if ( grouperGroupInfo != null ) {
1029 Map<String, Object> groupMap = getGroupJexlMap(expression, grouperGroupInfo);
1030 variableMap.putAll(groupMap);
1031 }
1032
1033 if ( tsGroup != null )
1034 variableMap.put("tsGroup", tsGroup.getJexlMap());
1035 }
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047 private void prepareUserCache(Set<Subject> subjects, DateTime oldestCacheTimeAllowed) throws PspException {
1048 LOG.debug("Starting to cache user information for {} items", subjects.size());
1049 tsUserCache_shortTerm.clear();
1050
1051
1052 if ( ! config.needsTargetSystemUsers() || subjects.size()==0 )
1053 return;
1054 Collection<Subject> subjectsToFetch = new ArrayList<Subject>();
1055
1056 for (Subject s : subjects) {
1057
1058 if ( s.getSourceId().equals("g:gsa") )
1059 continue;
1060
1061
1062 TSUserClass cachedTSU = targetSystemUserCache.get(s, oldestCacheTimeAllowed);
1063 if ( cachedTSU != null )
1064
1065 cacheUser(s, cachedTSU);
1066 else
1067 subjectsToFetch.add(s);
1068 }
1069
1070 LOG.info("{} out of {} subjects were found in cache ({}%)",
1071 tsUserCache_shortTerm.size(), subjects.size(),
1072 (int) 100.0*tsUserCache_shortTerm.size()/subjects.size());
1073
1074 if ( subjectsToFetch.size() == 0 )
1075 return;
1076
1077 ProgressMonitoressMonitor.html#ProgressMonitor">ProgressMonitor fetchingProgress = new ProgressMonitor(subjectsToFetch.size(), LOG, false, 15, "Fetching subjects");
1078
1079 List<List<Subject>> batchesOfSubjectsToFetch = PspUtils.chopped(subjectsToFetch, config.getUserSearch_batchSize());
1080
1081 List<Future<Map<Subject, TSUserClass>>> futures = new ArrayList<>();
1082
1083
1084 for (final List<Subject> batchOfSubjectsToFetch : batchesOfSubjectsToFetch ) {
1085
1086
1087
1088
1089 Future<Map<Subject, TSUserClass>> future = tsUserFetchingService.submit(
1090 new Callable<Map<Subject, TSUserClass>>() {
1091 @Override
1092 public Map<Subject, TSUserClass> call() throws Exception {
1093
1094 GrouperSession grouperSession = GrouperSession.startRootSession();
1095 GrouperContext grouperContext = GrouperContext.createNewDefaultContext(GrouperEngineBuiltin.LOADER, false, true);
1096
1097 Provisioner.activeProvisioner.set(Provisioner.this);
1098 Map<Subject, TSUserClass> fetchedData;
1099 try {
1100 fetchedData = fetchTargetSystemUsers(batchOfSubjectsToFetch);
1101 } catch (PspException e1) {
1102 LOG.warn("Batch-fetching subject information failed. Trying fetching information for each subject individually", e1);
1103
1104
1105 fetchedData = new HashMap<>();
1106 for (Subject subject : batchOfSubjectsToFetch) {
1107 try {
1108 TSUserClass tsUser = fetchTargetSystemUser(subject);
1109 fetchedData.put(subject, tsUser);
1110 } catch (PspException e2) {
1111 LOG.error("Problem fetching information about subject '{}'", subject, e2);
1112 throw new RuntimeException("Problem fetching information on subject " + subject + ": " + e2.getMessage());
1113 }
1114 }
1115 } finally {
1116 GrouperSession.stopQuietly(grouperSession);
1117 GrouperContext.deleteDefaultContext();
1118 }
1119 return fetchedData;
1120 }
1121 });
1122
1123 futures.add(future);
1124 }
1125
1126
1127 while (!futures.isEmpty()) {
1128 Iterator<Future<Map<Subject, TSUserClass>>> futureIterator = futures.iterator();
1129
1130 while (futureIterator.hasNext()) {
1131 Future<Map<Subject, TSUserClass>> future = futureIterator.next();
1132
1133
1134 if ( future.isDone() ) {
1135 Map<Subject, TSUserClass> fetchedData;
1136 try {
1137 fetchedData = future.get();
1138 } catch (InterruptedException e) {
1139 LOG.error("Problem fetching information on subjects", e);
1140
1141 throw new RuntimeException("Problem fetching information on subjects: " + e.getMessage());
1142 } catch (ExecutionException e) {
1143 LOG.error("Problem fetching information on subjects", e);
1144
1145 throw new RuntimeException("Problem fetching information on subjects: " + e.getMessage());
1146 }
1147
1148
1149 for ( Entry<Subject, TSUserClass> subjectInfo : fetchedData.entrySet() )
1150 cacheUser(subjectInfo.getKey(), subjectInfo.getValue());
1151
1152 fetchingProgress.workCompleted(fetchedData.size());
1153 futureIterator.remove();
1154 }
1155 }
1156 }
1157 fetchingProgress.completelyDone("Success");
1158
1159
1160
1161
1162
1163 for ( Subject subj : subjects ) {
1164 if ( !tsUserCache_shortTerm.containsKey(subj) ) {
1165 if ( config.isCreatingMissingUsersEnabled() ) {
1166 TSUserClass newTSUser = createUser(subj);
1167 if ( newTSUser != null )
1168 cacheUser(subj, newTSUser);
1169 }
1170 else
1171 LOG.warn("{}: User not found in target system: {}", getDisplayName(), subj.getId());
1172 }
1173 }
1174 }
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187 private void prepareGroupCache(Collection<GrouperGroupInfo> grouperGroupInfos) throws PspException {
1188
1189 Set<GrouperGroupInfo> groupInfoSet = new HashSet<>(grouperGroupInfos);
1190
1191 LOG.debug("Starting to cache group information for {} items", groupInfoSet.size());
1192 tsGroupCache_shortTerm.clear();
1193
1194
1195
1196 if ( ! config.needsTargetSystemGroups() )
1197 return;
1198
1199 Collection<GrouperGroupInfo> groupsToFetch = new ArrayList<GrouperGroupInfo>();
1200
1201 for (GrouperGroupInfo grouperGroupInfo : groupInfoSet) {
1202
1203 TSGroupClass cachedTSG = targetSystemGroupCache.get(grouperGroupInfo);
1204 if (cachedTSG != null) {
1205
1206 cacheGroup(grouperGroupInfo, cachedTSG);
1207 }
1208 else {
1209 groupsToFetch.add(grouperGroupInfo);
1210 }
1211 }
1212
1213 Map<GrouperGroupInfo, TSGroupClass> fetchedData = fetchTargetSystemGroupsInBatches(groupsToFetch);
1214
1215 for ( Entry<GrouperGroupInfo, TSGroupClass> grouperGroupInfo : fetchedData.entrySet() )
1216 cacheGroup(grouperGroupInfo.getKey(), grouperGroupInfo.getValue());
1217
1218
1219
1220
1221
1222
1223 if ( config.areEmptyGroupsSupported() ) {
1224 for (GrouperGroupInfo grouperGroupInfo : groupsToFetch) {
1225 if (!tsGroupCache_shortTerm.containsKey(grouperGroupInfo) &&
1226 shouldGroupBeProvisionedConsiderCache(grouperGroupInfo))
1227 {
1228
1229 TSGroupClass tsGroup = createGroup(grouperGroupInfo, new ArrayList<Subject>());
1230 cacheGroup(grouperGroupInfo, tsGroup);
1231 }
1232 }
1233 }
1234
1235 }
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245 public Map<GrouperGroupInfo, TSGroupClass> fetchTargetSystemGroupsInBatches(Collection<GrouperGroupInfo> groupsToFetch) throws PspException {
1246 Map<GrouperGroupInfo, TSGroupClass> result = new HashMap<>(groupsToFetch.size());
1247
1248 List<List<GrouperGroupInfo>> batchesOfGroupsToFetch = PspUtils.chopped(groupsToFetch, config.getGroupSearch_batchSize());
1249
1250 for ( List<GrouperGroupInfo> batchOfGroupsToFetch : batchesOfGroupsToFetch ) {
1251 Map<GrouperGroupInfo, TSGroupClass> fetchedData;
1252
1253 try {
1254 fetchedData = fetchTargetSystemGroups(batchOfGroupsToFetch);
1255 result.putAll(fetchedData);
1256 }
1257 catch (PspException e1) {
1258 LOG.warn("Batch-fetching group information failed. Trying to fetch information for each group individually", e1);
1259
1260
1261 for ( GrouperGroupInfo grouperGroupInfo : batchOfGroupsToFetch ) {
1262 try {
1263 TSGroupClass tsGroup = fetchTargetSystemGroup(grouperGroupInfo);
1264 cacheGroup(grouperGroupInfo, tsGroup);
1265 }
1266 catch (PspException e2) {
1267 LOG.error("Problem fetching information on group '{}'", grouperGroupInfo, e2);
1268 throw new RuntimeException("Problem fetching information on group " + grouperGroupInfo);
1269 }
1270 }
1271 }
1272 }
1273
1274 return result;
1275 }
1276
1277
1278 public TSUserClass getTargetSystemUser(Subject subject) throws PspException {
1279 GrouperUtil.assertion(config.needsTargetSystemUsers(),
1280 String.format("%s: system doesn't need target-system users, but one was requested", getDisplayName()));
1281
1282 TSUserClass result = tsUserCache_shortTerm.get(subject);
1283
1284 if ( result == null ) {
1285 if ( config.isCreatingMissingUsersEnabled() ) {
1286 result=createUser(subject);
1287 cacheUser(subject, result);
1288 }
1289 else {
1290 LOG.warn("{}: user is missing and user-creation is not enabled ({})", getDisplayName(), subject.getId());
1291 }
1292 }
1293
1294 return result;
1295 }
1296
1297
1298
1299
1300
1301
1302
1303 private void cacheUser(Subject subject, TSUserClass newTSUser) {
1304 LOG.debug("Adding target-system user to cache: {}", subject);
1305 targetSystemUserCache.put(subject, newTSUser);
1306 tsUserCache_shortTerm.put(subject, newTSUser);
1307 }
1308
1309
1310
1311
1312
1313
1314
1315 protected void cacheGroup(GrouperGroupInfo grouperGroupInfo, TSGroupClass newTSGroup) {
1316 if ( newTSGroup != null ) {
1317 LOG.debug("Adding target-system group to cache: {}", grouperGroupInfo);
1318 targetSystemGroupCache.put(grouperGroupInfo, newTSGroup);
1319 tsGroupCache_shortTerm.put(grouperGroupInfo, newTSGroup);
1320 } else {
1321 if ( targetSystemGroupCache.containsKey(grouperGroupInfo) ||
1322 tsGroupCache_shortTerm.containsKey(grouperGroupInfo) ) {
1323 LOG.debug("Removing target-system group from cache: {}", grouperGroupInfo);
1324 targetSystemGroupCache.remove(grouperGroupInfo);
1325 tsGroupCache_shortTerm.remove(grouperGroupInfo);
1326 } else {
1327 LOG.debug("No target-system group to cache: {}", grouperGroupInfo);
1328 }
1329 }
1330 }
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341 protected void uncacheGroup(GrouperGroupInfo grouperGroupInfo, TSGroupClass tsGroup) {
1342
1343
1344 if ( grouperGroupInfo == null && tsGroup != null ) {
1345 for (GrouperGroupInfo gi : targetSystemGroupCache.keySet())
1346 if (targetSystemGroupCache.get(gi) == tsGroup) {
1347 grouperGroupInfo = gi;
1348 break;
1349 }
1350
1351 if ( grouperGroupInfo == null ) {
1352 LOG.warn("Can't find Grouper Group to uncache from tsGroup {}", tsGroup);
1353 return;
1354 }
1355 }
1356
1357 LOG.debug("Flushing group from target-system cache: {}", grouperGroupInfo);
1358 targetSystemGroupCache.remove(grouperGroupInfo);
1359
1360 LOG.debug("Flushing group from pspng group-info cache: {}", grouperGroupInfo.getName());
1361 grouperGroupInfoCache.remove(grouperGroupInfo.getName());
1362
1363 grouperGroupInfo.hibernateRefresh();
1364 }
1365
1366
1367
1368
1369
1370 protected void uncacheAllGroups() {
1371
1372 for (GrouperGroupInfo g : grouperGroupInfoCache.values()) {
1373 uncacheGroup(g, null);
1374 }
1375 }
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389 protected final TSUserClass fetchTargetSystemUser(Subject personSubject) throws PspException {
1390
1391 Map<Subject, TSUserClass> result = fetchTargetSystemUsers(Arrays.asList(personSubject));
1392 return result.get(personSubject);
1393 }
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406 protected final TSGroupClass fetchTargetSystemGroup(GrouperGroupInfo grouperGroup) throws PspException {
1407
1408 Map<GrouperGroupInfo, TSGroupClass> result = fetchTargetSystemGroups(Arrays.asList(grouperGroup));
1409 return result.get(grouperGroup);
1410 }
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422 protected TSUserClass createUser(Subject personSubject) throws PspException {
1423 return null;
1424 }
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436 protected void provisionItem(ProvisioningWorkItem workItem) throws PspException {
1437 LOG.debug("Starting provisioning of item: {}", workItem);
1438
1439 currentWorkItem.set(workItem);
1440 ChangeLogEntry entry = workItem.getChangelogEntry();
1441 String workItem_identifier = null;
1442 if ( workItem.getChangelogEntry()!=null ) {
1443 workItem_identifier = Long.toString(workItem.getChangelogEntry().getSequenceNumber());
1444 }
1445
1446 try {
1447 if ( workItem.matchesChangelogType(ChangelogHandlingConfig.changelogTypesThatAreHandledIncrementally) ) {
1448 processIncrementalSyncEvent(workItem);
1449 return;
1450 }
1451
1452 if ( workItemMightChangeGroupSelection(workItem) ) {
1453 processAnyChangesInGroupSelection(workItem);
1454 }
1455
1456
1457
1458
1459 if ( workItem.getGroupInfo(this) != null &&
1460 selectedGroups.get().contains(workItem.getGroupInfo(this)) ) {
1461
1462
1463
1464
1465
1466
1467 FullSyncQueueItem fullSyncStatus = getFullSyncer()
1468 .scheduleGroupForSync(
1469 FullSyncProvisioner.QUEUE_TYPE.ASAP,
1470 workItem.groupName,
1471 workItem_identifier,
1472 "Changelog: %s", workItem);
1473
1474 workItem.markAsSuccess("Handled with scheduled FullSync (qid=%d)", fullSyncStatus.id);
1475 return;
1476 }
1477
1478
1479 if ( !workItem.hasBeenProcessed() ) {
1480 workItem.markAsSkipped("Nothing to do (not a supported change)");
1481 }
1482
1483 } catch (PspException e) {
1484 LOG.error("Problem provisioning item {}", workItem, e);
1485 workItem.markAsFailure("Provisioning failure: %s", e.getMessage());
1486 } finally {
1487 currentWorkItem.set(null);
1488 }
1489 }
1490
1491 private void processAnyChangesInGroupSelection(ProvisioningWorkItem workItem) throws PspException {
1492 String workItem_identifier;
1493 ChangeLogEntry changelogEntry = workItem.getChangelogEntry();
1494 if ( changelogEntry != null ) {
1495 workItem_identifier = String.format("chlog #%d",changelogEntry.getSequenceNumber());
1496 } else {
1497 workItem_identifier = workItem.toString();
1498 }
1499
1500 LOG.info("{}: Checking to see if group selection has changed", getDisplayName());
1501 Set<GrouperGroupInfo> selectedGroups_before = selectedGroups.get();
1502 Set<GrouperGroupInfo> selectedGroups_now = getAllGroupsForProvisioner();
1503
1504
1505 Set<GrouperGroupInfo> deselectedGroups = new HashSet<>(selectedGroups_before);
1506 deselectedGroups.removeAll(selectedGroups_now);
1507
1508
1509 Set<GrouperGroupInfo> newlySelectedGroups = new HashSet<>(selectedGroups_now);
1510 newlySelectedGroups.removeAll(selectedGroups_before);
1511
1512
1513 selectedGroups.set(selectedGroups_now);
1514
1515 LOG.info("{}: Change deselected {} groups from provisioner", getDisplayName(), deselectedGroups.size());
1516 LOG.info("{}: Change selected {} new groups for provisioner", getDisplayName(), newlySelectedGroups.size());
1517
1518 for (GrouperGroupInfo group : deselectedGroups) {
1519 TSGroupClass tsGroup=null;
1520 if ( getConfig().needsTargetSystemGroups() ) {
1521 tsGroup = fetchTargetSystemGroup(group);
1522 if ( tsGroup == null ) {
1523 LOG.info("{}: Group is already not present in target system: {}", getDisplayName(), group.getName());
1524 continue;
1525 }
1526 }
1527 LOG.info("{}: Deleting group from target system because it is no longer selected for provisioning: {}", getDisplayName(), group);
1528 deleteGroup(group, tsGroup);
1529 }
1530
1531 for (GrouperGroupInfo group : newlySelectedGroups) {
1532 LOG.info("{}: Scheduling full sync of group because it is now selected for provisioning: {}", getDisplayName(), group);
1533
1534 JobStatistics jobStatistics = this.getJobStatistics();
1535 if (jobStatistics != null) {
1536 jobStatistics.insertCount.addAndGet(1);
1537 }
1538
1539 getFullSyncer().scheduleGroupForSync(
1540 FullSyncProvisioner.QUEUE_TYPE.CHANGELOG,
1541 group.getName(),
1542 workItem_identifier,
1543 "group newly selected for provisioning");
1544
1545 }
1546
1547 String attributeValue = null;
1548 String attributeAssignId = null;
1549 String nameOfAttributeDefName = null;
1550 boolean isAddAndNotDelete = false;
1551
1552
1553 if (changelogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.ATTRIBUTE_ASSIGN_VALUE_ADD)) {
1554 attributeValue = changelogEntry.retrieveValueForLabel(ChangeLogLabels.ATTRIBUTE_ASSIGN_VALUE_ADD.value);
1555 attributeAssignId = changelogEntry.retrieveValueForLabel(ChangeLogLabels.ATTRIBUTE_ASSIGN_VALUE_ADD.attributeAssignId);
1556 nameOfAttributeDefName = changelogEntry.retrieveValueForLabel(ChangeLogLabels.ATTRIBUTE_ASSIGN_VALUE_ADD.attributeDefNameName);
1557 isAddAndNotDelete = true;
1558 }
1559
1560 if (changelogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.ATTRIBUTE_ASSIGN_VALUE_DELETE)) {
1561 attributeValue = changelogEntry.retrieveValueForLabel(ChangeLogLabels.ATTRIBUTE_ASSIGN_VALUE_DELETE.value);
1562 attributeAssignId = changelogEntry.retrieveValueForLabel(ChangeLogLabels.ATTRIBUTE_ASSIGN_VALUE_DELETE.attributeAssignId);
1563 nameOfAttributeDefName = changelogEntry.retrieveValueForLabel(ChangeLogLabels.ATTRIBUTE_ASSIGN_VALUE_DELETE.attributeDefNameName);
1564 }
1565
1566 String provisionToName = GrouperConfig.retrieveConfig().propertyValueString(
1567 "grouper.rootStemForBuiltinObjects", "etc") + ":pspng:provision_to";
1568 String doNotProvisionToName = GrouperConfig.retrieveConfig().propertyValueString(
1569 "grouper.rootStemForBuiltinObjects", "etc") + ":pspng:do_not_provision_to";
1570
1571
1572 if (!StringUtils.isBlank(attributeAssignId) && !StringUtils.isBlank(attributeValue) && StringUtils.equals(attributeValue, this.provisionerConfigName)
1573 && getConfig().getAttributesUsedInGroupSelectionExpression().contains(nameOfAttributeDefName)
1574 && (StringUtils.equals(provisionToName, nameOfAttributeDefName) || StringUtils.equals(doNotProvisionToName, nameOfAttributeDefName))) {
1575
1576 List<String> groupNames = new GcDbAccess().sql("SELECT gg.name FROM grouper_attribute_assign gaa, grouper_groups gg "
1577 + "WHERE gaa.id = ? AND gg.id = gaa.OWNER_GROUP_ID and gg.name is not null").addBindVar(attributeAssignId).selectList(String.class);
1578
1579 if (GrouperUtil.length(groupNames) == 0) {
1580 groupNames = new GcDbAccess().sql("SELECT gg.name FROM grouper_attribute_assign gaa, grouper_stems gs, grouper_stem_set gss, grouper_groups gg "
1581 + " WHERE gss.then_has_stem_id = gs.id AND gg.PARENT_STEM = gss.IF_HAS_STEM_ID "
1582 + " AND gaa.id = ? AND gs.id = gaa.OWNER_STEM_ID").addBindVar(attributeAssignId).selectList(String.class);
1583 }
1584
1585 if (GrouperUtil.length(groupNames) == 0) {
1586 groupNames = new GcDbAccess().sql("SELECT gpg.name FROM grouper_pit_attribute_assign gpaa, grouper_pit_groups gpg "
1587 + "WHERE gpaa.source_id = ? AND gpg.id = gpaa.OWNER_GROUP_ID AND gpaa.END_TIME IS NULL and gpg.name is not null")
1588 .addBindVar(attributeAssignId).selectList(String.class);
1589 }
1590
1591 if (GrouperUtil.length(groupNames) == 0) {
1592 groupNames = new GcDbAccess().sql("SELECT gpg.name FROM grouper_pit_attribute_assign gpaa, grouper_pit_groups gpg "
1593 + "WHERE gpaa.source_id = ? AND gpg.name IS NOT NULL AND gpg.id = gpaa.OWNER_GROUP_ID AND gpaa.END_TIME = "
1594 + "(SELECT max(gpaa2.end_time) FROM grouper_pit_attribute_assign gpaa2 WHERE gpaa2.end_time IS NOT NULL AND gpaa.source_id = gpaa2.source_id)")
1595 .addBindVar(attributeAssignId).selectList(String.class);
1596 }
1597
1598 if (GrouperUtil.length(groupNames) == 0) {
1599 groupNames = new GcDbAccess().sql("SELECT gg.name FROM grouper_pit_attribute_assign gpaa, grouper_pit_stems gps, "
1600 + "grouper_stem_set gss, grouper_groups gg "
1601 + "WHERE gss.then_has_stem_id = gps.source_id AND gg.PARENT_STEM = gss.IF_HAS_STEM_ID AND gpaa.end_time IS null "
1602 + "AND gpaa.source_id = ? AND gps.id = gpaa.OWNER_STEM_ID")
1603 .addBindVar(attributeAssignId).selectList(String.class);
1604 }
1605
1606 if (GrouperUtil.length(groupNames) == 0) {
1607 groupNames = new GcDbAccess().sql("SELECT gg.name FROM grouper_pit_attribute_assign gpaa, grouper_pit_stems gps, grouper_stem_set gss, grouper_groups gg "
1608 + "WHERE gss.then_has_stem_id = gps.source_id AND gg.PARENT_STEM = gss.IF_HAS_STEM_ID "
1609 + "AND gpaa.source_id = ? AND gps.id = gpaa.OWNER_STEM_ID "
1610 + "AND gpaa.end_time = (SELECT max(gpaa2.end_time) FROM grouper_pit_attribute_assign gpaa2 WHERE gpaa2.end_time IS NOT NULL AND gpaa.source_id = gpaa2.source_id)")
1611 .addBindVar(attributeAssignId).selectList(String.class);
1612 }
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632 HashSet<String> groupNameSet = new HashSet<String>(GrouperUtil.nonNull(groupNames));
1633 for (String groupName : groupNameSet) {
1634
1635
1636 boolean newlyProvisionable = (StringUtils.equals(provisionToName, nameOfAttributeDefName) && isAddAndNotDelete)
1637 || (StringUtils.equals(doNotProvisionToName, nameOfAttributeDefName) && !isAddAndNotDelete);
1638
1639 GrouperGroupInfo group = getGroupInfoOfExistingGroup(groupName);
1640
1641 boolean provisionable = GrouperUtil.nonNull(selectedGroups_now).contains(group);
1642
1643 if (!newlyProvisionable) {
1644 if (!provisionable) {
1645 TSGroupClass tsGroup=null;
1646 if ( getConfig().needsTargetSystemGroups() ) {
1647 tsGroup = fetchTargetSystemGroup(group);
1648 if ( tsGroup == null ) {
1649 LOG.info("{}: Group is already not present in target system: {}", getDisplayName(), group == null ? "null" : group.getName());
1650 continue;
1651 }
1652 }
1653 LOG.info("{}: Deleting group from target system because it is no longer selected for provisioning: {}", getDisplayName(), group);
1654 deleteGroup(group, tsGroup);
1655 }
1656 } else {
1657 if (provisionable) {
1658 LOG.info("{}: Scheduling full sync of group because it is now selected for provisioning: {}", getDisplayName(), group);
1659
1660 JobStatistics jobStatistics = this.getJobStatistics();
1661 if (jobStatistics != null) {
1662 jobStatistics.insertCount.addAndGet(1);
1663 }
1664
1665 getFullSyncer().scheduleGroupForSync(
1666 FullSyncProvisioner.QUEUE_TYPE.CHANGELOG,
1667 group.getName(),
1668 workItem_identifier,
1669 "group newly selected for provisioning");
1670
1671 }
1672 }
1673
1674
1675 }
1676
1677
1678 }
1679
1680 workItem.markAsSuccess("Processed any changes in group selection");
1681 }
1682
1683
1684 private void processIncrementalSyncEvent(ProvisioningWorkItem workItem) throws PspException {
1685 ChangeLogEntry entry = workItem.getChangelogEntry();
1686
1687 if ( entry.equalsCategoryAndAction(ChangeLogTypeBuiltin.GROUP_ADD ))
1688 {
1689 GrouperGroupInfo grouperGroupInfo = workItem.getGroupInfo(this);
1690
1691 if ( grouperGroupInfo == null || grouperGroupInfo.hasGroupBeenDeleted() ) {
1692 workItem.markAsSkippedAndWarn("Ignored group-add: group does not exist any more");
1693 return;
1694 }
1695
1696 if ( !shouldGroupBeProvisionedConsiderCache(grouperGroupInfo) ) {
1697 workItem.markAsSkipped("Group %s is not selected to be provisioned", grouperGroupInfo);
1698 return;
1699 } else {
1700
1701 selectedGroups.get().add(grouperGroupInfo);
1702 }
1703
1704 if ( tsGroupCache_shortTerm.containsKey(grouperGroupInfo) ) {
1705 workItem.markAsSuccess("Group %s already exists", grouperGroupInfo);
1706 return;
1707 }
1708 else
1709 createGroup(grouperGroupInfo, Collections.EMPTY_LIST);
1710 }
1711 else if ( entry.equalsCategoryAndAction(ChangeLogTypeBuiltin.GROUP_DELETE ))
1712 {
1713
1714
1715 GrouperGroupInfo grouperGroupInfo = workItem.getGroupInfo(this);
1716
1717 if ( grouperGroupInfo == null ) {
1718 workItem.markAsSkippedAndWarn("Ignoring group-deletion event because group information was not found in grouper");
1719 return;
1720 }
1721
1722 TSGroupClass tsGroup = tsGroupCache_shortTerm.get(grouperGroupInfo);
1723
1724
1725 if ( config.needsTargetSystemGroups() ) {
1726 if ( tsGroup == null ) {
1727 workItem.markAsSuccess("Nothing to do: Group does not exist in target system");
1728 return;
1729 } else {
1730 LOG.info("Deleting provisioned group: {}", grouperGroupInfo);
1731 deleteGroup(grouperGroupInfo, tsGroup);
1732 }
1733 }
1734 else {
1735
1736
1737
1738 LOG.info("{}: Group has been deleted from grouper. Checking to see if it was provisioned to target system", getDisplayName());
1739 deleteGroup(grouperGroupInfo, tsGroup);
1740 }
1741 }
1742 else if ( entry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_ADD))
1743 {
1744 GrouperGroupInfo grouperGroupInfo = workItem.getGroupInfo(this);
1745
1746 if ( grouperGroupInfo == null || grouperGroupInfo.hasGroupBeenDeleted() ) {
1747 workItem.markAsSkipped("Ignoring membership-add event for group that was deleted");
1748 return;
1749 }
1750
1751 if ( !shouldGroupBeProvisioned(grouperGroupInfo) ) {
1752 workItem.markAsSkipped("Group %s is not selected to be provisioned", grouperGroupInfo);
1753 return;
1754 }
1755
1756 TSGroupClass tsGroup = tsGroupCache_shortTerm.get(grouperGroupInfo);
1757 Subject subject = workItem.getSubject(this);
1758
1759 if ( subject == null ) {
1760 workItem.markAsSkippedAndWarn("Ignoring membership-add event because subject is no longer in grouper");
1761 return;
1762 }
1763
1764 if ( subject.getTypeName().equalsIgnoreCase("group") ) {
1765 workItem.markAsSkipped("Nested-group membership skipped");
1766 return;
1767 }
1768
1769 TSUserClass tsUser = tsUserCache_shortTerm.get(subject);
1770
1771 if ( config.needsTargetSystemUsers() && tsUser==null ) {
1772 workItem.markAsSkippedAndWarn("Skipped: subject doesn't exist in target system");
1773 return;
1774 }
1775
1776 addMembership(grouperGroupInfo, tsGroup, subject, tsUser);
1777 }
1778 else if ( entry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_DELETE))
1779 {
1780 GrouperGroupInfo grouperGroupInfo = workItem.getGroupInfo(this);
1781
1782 if ( grouperGroupInfo==null || grouperGroupInfo.hasGroupBeenDeleted() ) {
1783 workItem.markAsSkipped("Ignoring membership-delete event for group that was deleted");
1784 return;
1785 }
1786
1787 if ( !shouldGroupBeProvisioned(grouperGroupInfo) ) {
1788 workItem.markAsSkipped("Group %s is not selected to be provisioned", grouperGroupInfo);
1789 return;
1790 }
1791
1792 TSGroupClass tsGroup = tsGroupCache_shortTerm.get(grouperGroupInfo);
1793 Subject subject = workItem.getSubject(this);
1794
1795 if ( subject == null ) {
1796 workItem.markAsSkippedAndWarn("Ignoring membership-delete event because subject is no longer in grouper");
1797 LOG.warn("Work item ignored: {}", workItem);
1798 return;
1799 }
1800
1801 TSUserClass tsUser = tsUserCache_shortTerm.get(subject);
1802
1803 if ( config.needsTargetSystemUsers() && tsUser==null ) {
1804 workItem.markAsSkippedAndWarn("Skipped: subject doesn't exist in target system");
1805 return;
1806 }
1807 deleteMembership(grouperGroupInfo, tsGroup, subject, tsUser);
1808 }
1809 }
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819 final void prepareAndRunGroupCleanup(JobStatistics stats) throws PspException {
1820
1821 GrouperUtil.assertion(isFullSyncMode(), "FullSync operations should only be used with provisioners initialized for full-sync");
1822
1823 if ( !config.isGrouperAuthoritative() ) {
1824 LOG.warn("{}: Not doing group cleanup because grouper is not marked as authoritative in provisioner configuration", getDisplayName());
1825 return;
1826 }
1827
1828 tsUserCache_shortTerm.clear();
1829 tsGroupCache_shortTerm.clear();
1830 try {
1831 MDC.put("step", "clean/");
1832 doFullSync_cleanupExtraGroups(stats);
1833 }
1834 catch (PspException e) {
1835 LOG.error("Problem while looking for and removing extra groups", e);
1836 throw e;
1837 }
1838 finally {
1839 MDC.remove("step");
1840 }
1841 }
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859 final boolean doFullSync(GrouperGroupInfo grouperGroupInfo, DateTime oldestCacheTimeAllowed, JobStatistics stats)
1860 throws PspException {
1861
1862 GrouperUtil.assertion(isFullSyncMode(),
1863 "FullSync operations should only be used with provisioners initialized for full-sync");
1864
1865
1866 TSGroupClass tsGroup = tsGroupCache_shortTerm.get(grouperGroupInfo);
1867
1868
1869 if ( tsGroup != null ) {
1870 if (grouperGroupInfo.hasGroupBeenDeleted() && tsGroup != null) {
1871 LOG.info("{} full sync: Deleting group because it was deleted from grouper: {}/{}",
1872 new Object[]{getDisplayName(), grouperGroupInfo, tsGroup});
1873
1874 deleteGroup(grouperGroupInfo, tsGroup);
1875 return true;
1876 }
1877
1878 if (!shouldGroupBeProvisionedConsiderCache(grouperGroupInfo)) {
1879 LOG.info("{} full sync: Deleting group because it is not selected for this provisioner: {}/{}",
1880 new Object[]{getDisplayName(), grouperGroupInfo, tsGroup});
1881
1882 deleteGroup(grouperGroupInfo, tsGroup);
1883 return true;
1884 }
1885 }
1886
1887 Set<Member> groupMembers = grouperGroupInfo.getMembers();
1888 Set<Subject> correctSubjects = new HashSet<Subject>();
1889
1890 for (Member member : groupMembers) {
1891 Subject subject = member.getSubject();
1892 if ( subject.getTypeName().equalsIgnoreCase(SubjectTypeEnum.PERSON.getName()) ) {
1893 correctSubjects.add(subject);
1894 }
1895 }
1896
1897 if ( correctSubjects.size() > 0 )
1898 prepareUserCache(correctSubjects, oldestCacheTimeAllowed);
1899
1900 Set<TSUserClass> correctTSUsers = new HashSet<TSUserClass>();
1901
1902 if ( getConfig().needsTargetSystemUsers() ) {
1903
1904
1905 for ( Subject correctSubject: new ArrayList<Subject>(correctSubjects) ) {
1906 TSUserClass tsUser = tsUserCache_shortTerm.get(correctSubject);
1907 if ( tsUser == null ) {
1908
1909 LOG.warn("{}: Member in grouper group {} is being ignored because subject is not present in target system: {}",
1910 getDisplayName(), grouperGroupInfo, correctSubject);
1911
1912 correctSubjects.remove(correctSubject);
1913 }
1914 else {
1915 correctTSUsers.add(tsUser);
1916 }
1917 }
1918 }
1919
1920 LOG.debug("{}/{}: All correct member subjects: {}",
1921 new Object[] {getDisplayName(), grouperGroupInfo, correctSubjects});
1922
1923 LOG.info("{}/{}: {} correct member subjects. Sample: {}...",
1924 new Object[] {getDisplayName(), grouperGroupInfo, correctSubjects.size(),
1925 new ArrayList<Subject>(correctSubjects).subList(0, Math.min(10, correctSubjects.size()))});
1926
1927 try {
1928 MDC.put("step", "prov/");
1929 return doFullSync(grouperGroupInfo, tsGroup, correctSubjects, tsUserCache_shortTerm, correctTSUsers, stats);
1930 }
1931 finally {
1932 MDC.remove("step");
1933 }
1934 }
1935
1936
1937
1938
1939
1940 public ProvisioningWorkItem getCurrentWorkItem() {
1941 return currentWorkItem.get();
1942 }
1943
1944 public void setCurrentWorkItem(ProvisioningWorkItem item) {
1945 currentWorkItem.set(item);
1946 }
1947
1948 protected static String getSubjectCacheKey(String subjectId, String sourceId) {
1949 String cacheKey = String.format("%s__%s", sourceId, subjectId);
1950 return cacheKey;
1951 }
1952
1953 protected static String getSubjectCacheKey(Subject subject) {
1954 String cacheKey = getSubjectCacheKey(subject.getSourceId(), subject.getSourceId());
1955 return cacheKey;
1956 }
1957
1958 protected Subject getSubject(String subjectId, String sourceId) {
1959 String cacheKey = getSubjectCacheKey(subjectId, sourceId);
1960
1961 Subject subject = grouperSubjectCache.get(cacheKey);
1962
1963 if ( subject != null )
1964 return subject;
1965
1966 subject = SubjectFinder.findByIdAndSource(subjectId, sourceId, false);
1967
1968 if ( subject != null )
1969 grouperSubjectCache.put(cacheKey, subject);
1970
1971 return subject;
1972 }
1973
1974 protected GrouperGroupInfo getGroupInfoOfExistingGroup(Group group) {
1975 String groupName = group.getName();
1976 GrouperGroupInfo result = grouperGroupInfoCache.get(groupName);
1977 if ( result == null ) {
1978 result = new GrouperGroupInfo(group);
1979 grouperGroupInfoCache.put(groupName, result);
1980 }
1981 return result;
1982 }
1983
1984
1985 protected GrouperGroupInfo getGroupInfoOfExistingGroup(String groupName) {
1986 GrouperGroupInfo grouperGroupInfo = grouperGroupInfoCache.get(groupName);
1987
1988
1989 if (grouperGroupInfo != null)
1990 return grouperGroupInfo;
1991
1992 try {
1993
1994 Group group = GroupFinder.findByName(GrouperSession.staticGrouperSession(false), groupName, false);
1995
1996 if (group != null) {
1997 return getGroupInfoOfExistingGroup(group);
1998 }
1999 } catch (GroupNotFoundException e) {
2000 LOG.warn("Unable to find existing group '{}'", groupName);
2001 }
2002 return null;
2003 }
2004
2005 protected GrouperGroupInfo getGroupInfo(ProvisioningWorkItem workItem) {
2006 String groupName = workItem.groupName;
2007
2008 if ( groupName == null ) {
2009 return null;
2010 }
2011
2012 GrouperGroupInfo result = getGroupInfoOfExistingGroup(groupName);
2013 if ( result != null ) {
2014 LOG.debug("{}: Found existing group {}", getDisplayName(), result);
2015 return result;
2016 }
2017 PITGroup pitGroup = null;
2018 try {
2019
2020 pitGroup = PITGroupFinder.findMostRecentByName(groupName, false);
2021 }
2022 catch (GroupNotFoundException e) {
2023 LOG.warn("Unable to find PIT group '{}'", groupName);
2024 }
2025 if ( pitGroup != null ) {
2026 result = new GrouperGroupInfo(pitGroup);
2027 result.idIndex = workItem.getGroupIdIndex();
2028
2029
2030
2031 LOG.debug("{}: Found PIT group {}", getDisplayName(), result);
2032
2033 return result;
2034 } else {
2035 LOG.warn("Could not find PIT group: {}", groupName);
2036 result = new GrouperGroupInfo(groupName, workItem.getGroupIdIndex());
2037 LOG.debug("{}: Using barebones group info {}", getDisplayName(), result);
2038 return result;
2039 }
2040
2041 }
2042
2043 private FullSyncProvisioner getFullSyncer() throws PspException {
2044 return FullSyncProvisionerFactory.getFullSyncer(this);
2045 }
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059 public Set<GrouperGroupInfo> getAllGroupsForProvisioner2() {
2060 Date start = new Date();
2061
2062 LOG.debug("{}: Compiling a list of all groups selected for provisioning", getDisplayName());
2063 Set<GrouperGroupInfo> result = new HashSet<>();
2064
2065 Set<Group> interestingGroups = new HashSet<Group>();
2066
2067 Map<String, Set<String>> attributeToGroupNameAssigned = new HashMap<String, Set<String>>();
2068 Map<String, Set<String>> attributeToStemNameAssigned = new HashMap<String, Set<String>>();
2069
2070 for ( String attribute : getConfig().getAttributesUsedInGroupSelectionExpression() ) {
2071 Set<Stem> foldersReferencingAttribute;
2072 Set<Group> groupsReferencingAttribute;
2073
2074 if ( getConfig().areAttributesUsedInGroupSelectionExpressionComparedToProvisionerName() ) {
2075 LOG.debug("Looking for folders that match attribute {}={}", attribute, getConfigName());
2076 foldersReferencingAttribute = new StemFinder().assignNameOfAttributeDefName(attribute).assignAttributeValue(getConfigName()).findStems();
2077 LOG.debug("Looking for groups that match attribute {}={}", attribute, getConfigName());
2078 groupsReferencingAttribute = new GroupFinder().assignNameOfAttributeDefName(attribute).assignAttributeValue(getConfigName()).findGroups();
2079 }
2080 else {
2081 LOG.debug("Looking for folders that have attribute {}", attribute);
2082 foldersReferencingAttribute = new StemFinder().assignNameOfAttributeDefName(attribute).findStems();
2083 LOG.debug("Looking for groups that have attribute {}", attribute);
2084 groupsReferencingAttribute = new GroupFinder().assignNameOfAttributeDefName(attribute).findGroups();
2085 }
2086
2087 {
2088 Set<String> groupNames = attributeToGroupNameAssigned.get(attribute);
2089 if (groupNames == null) {
2090 groupNames = new HashSet<String>();
2091 attributeToGroupNameAssigned.put(attribute, groupNames);
2092 }
2093 for (Group group : GrouperUtil.nonNull(groupsReferencingAttribute)) {
2094 groupNames.add(group.getName());
2095 }
2096 }
2097 {
2098 Set<String> stemNames = attributeToStemNameAssigned.get(attribute);
2099 if (stemNames == null) {
2100 stemNames = new HashSet<String>();
2101 attributeToStemNameAssigned.put(attribute, stemNames);
2102 }
2103 for (Stem stem : GrouperUtil.nonNull(foldersReferencingAttribute)) {
2104 stemNames.add(stem.getName());
2105 }
2106 }
2107
2108 LOG.debug("{}: There are {} folders that match {} attribute", new Object[]{getDisplayName(), foldersReferencingAttribute.size(), attribute});
2109 LOG.debug("{}: There are {} groups that match {} attribute", new Object[]{getDisplayName(), groupsReferencingAttribute.size(), attribute});
2110
2111 interestingGroups.addAll(groupsReferencingAttribute);
2112 for ( Stem folder : foldersReferencingAttribute ) {
2113 Set<Group> groupsUnderFolder;
2114
2115 groupsUnderFolder = new GroupFinder().assignParentStemId(folder.getId()).assignStemScope(Scope.SUB).findGroups();
2116
2117 LOG.debug("{}: There are {} groups underneath folder {}", new Object[]{getDisplayName(), groupsUnderFolder.size(), folder.getName()});
2118 interestingGroups.addAll(groupsUnderFolder);
2119 }
2120 }
2121
2122 boolean pspngNonScriptProvisionable = GrouperLoaderConfig.retrieveConfig().propertyValueBoolean("pspngNonScriptProvisionable", true);
2123
2124 String provisionToName = GrouperConfig.retrieveConfig().propertyValueString(
2125 "grouper.rootStemForBuiltinObjects", "etc") + ":pspng:provision_to";
2126 String doNotProvisionToName = GrouperConfig.retrieveConfig().propertyValueString(
2127 "grouper.rootStemForBuiltinObjects", "etc") + ":pspng:do_not_provision_to";
2128
2129 for ( Group group : interestingGroups ) {
2130 GrouperGroupInforGroupInfo.html#GrouperGroupInfo">GrouperGroupInfo grouperGroupInfo = new GrouperGroupInfo(group);
2131 if (pspngNonScriptProvisionable && StringUtils.equals(config.getGroupSelectionExpression(), config.groupSelectionExpression_defaultValue())) {
2132 Boolean provisionable = null;
2133 if (grouperGroupInfo.hasGroupBeenDeleted() ) {
2134 provisionable = false;
2135 }
2136 final String objectName = group.getName();
2137
2138 if (provisionable == null && attributeToGroupNameAssigned.get(doNotProvisionToName).contains(objectName)) {
2139 provisionable = false;
2140 }
2141
2142 if (provisionable == null) {
2143 String currentObjectNamePointer = objectName;
2144 for (int i=0;i<1000;i++) {
2145 currentObjectNamePointer = GrouperUtil.parentStemNameFromName(currentObjectNamePointer, false);
2146 if (provisionable == null && attributeToStemNameAssigned.get(doNotProvisionToName).contains(currentObjectNamePointer)) {
2147 provisionable = false;
2148 break;
2149 }
2150 if (":".equals(currentObjectNamePointer) || "".equals(currentObjectNamePointer)) {
2151 break;
2152 }
2153 }
2154 }
2155
2156 if (provisionable == null && attributeToGroupNameAssigned.get(provisionToName).contains(objectName)) {
2157 provisionable = true;
2158 }
2159
2160 if (provisionable == null) {
2161 String currentObjectNamePointer = objectName;
2162 for (int i=0;i<1000;i++) {
2163 currentObjectNamePointer = GrouperUtil.parentStemNameFromName(currentObjectNamePointer, false);
2164 if (provisionable == null && attributeToStemNameAssigned.get(provisionToName).contains(currentObjectNamePointer)) {
2165 provisionable = true;
2166 break;
2167 }
2168 if (":".equals(currentObjectNamePointer) || "".equals(currentObjectNamePointer)) {
2169 break;
2170 }
2171 }
2172 }
2173 if (provisionable != null && provisionable) {
2174 result.add(grouperGroupInfo);
2175 }
2176 } else {
2177 if ( shouldGroupBeProvisioned(grouperGroupInfo) ) {
2178 result.add(grouperGroupInfo);
2179 }
2180 }
2181 }
2182 LOG.info("{}: There are {} groups selected for provisioning (found in {})",
2183 getDisplayName(), result.size(), PspUtils.formatElapsedTime(start, null));
2184
2185 return result;
2186 }
2187
2188 private static ExpirableCache<String, Set<GrouperGroupInfo>> allGroupsForProvisionerCache = null;
2189
2190 public static void allGroupsForProvisionerFromCacheClear(String provisionerName) {
2191 if (allGroupsForProvisionerCache != null) {
2192 allGroupsForProvisionerCache.put(provisionerName, null);
2193 }
2194 }
2195
2196 public Set<GrouperGroupInfo> allGroupsForProvisionerFromCache(String provisionerName) {
2197 int pspngCacheAllGroupProvisionableMinutes = GrouperLoaderConfig.retrieveConfig().propertyValueInt("pspngCacheAllGroupProvisionableMinutes", 2);
2198 if (pspngCacheAllGroupProvisionableMinutes <= 0) {
2199 return null;
2200 }
2201 if (allGroupsForProvisionerCache == null) {
2202 allGroupsForProvisionerCache = new ExpirableCache<String, Set<GrouperGroupInfo>>(pspngCacheAllGroupProvisionableMinutes);
2203 }
2204 Set<GrouperGroupInfo> result = allGroupsForProvisionerCache.get(provisionerName);
2205 return result;
2206 }
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219 public Set<GrouperGroupInfo> getAllGroupsForProvisioner() {
2220 Set<GrouperGroupInfo> result = allGroupsForProvisionerFromCache(this.provisionerConfigName);
2221
2222 if (result != null) {
2223 return result;
2224 }
2225
2226 boolean pspngCacheGroupProvisionable = GrouperLoaderConfig.retrieveConfig().propertyValueBoolean("pspngCacheGroupProvisionable", true);
2227 if (pspngCacheGroupProvisionable) {
2228 result = getAllGroupsForProvisioner2();
2229 if (this.allGroupsForProvisionerCache != null) {
2230 this.allGroupsForProvisionerCache.put(this.provisionerConfigName, result);
2231 }
2232 return result;
2233 }
2234
2235 Date start = new Date();
2236
2237 LOG.debug("{}: Compiling a list of all groups selected for provisioning", getDisplayName());
2238 result = new HashSet<>();
2239
2240 Set<Group> interestingGroups = new HashSet<Group>();
2241 for ( String attribute : getConfig().getAttributesUsedInGroupSelectionExpression() ) {
2242 Set<Stem> foldersReferencingAttribute;
2243 Set<Group> groupsReferencingAttribute;
2244
2245 if ( getConfig().areAttributesUsedInGroupSelectionExpressionComparedToProvisionerName() ) {
2246 LOG.debug("Looking for folders that match attribute {}={}", attribute, getConfigName());
2247 foldersReferencingAttribute = new StemFinder().assignNameOfAttributeDefName(attribute).assignAttributeValue(getConfigName()).findStems();
2248 LOG.debug("Looking for groups that match attribute {}={}", attribute, getConfigName());
2249 groupsReferencingAttribute = new GroupFinder().assignNameOfAttributeDefName(attribute).assignAttributeValue(getConfigName()).findGroups();
2250 }
2251 else {
2252 LOG.debug("Looking for folders that have attribute {}", attribute);
2253 foldersReferencingAttribute = new StemFinder().assignNameOfAttributeDefName(attribute).findStems();
2254 LOG.debug("Looking for groups that have attribute {}", attribute);
2255 groupsReferencingAttribute = new GroupFinder().assignNameOfAttributeDefName(attribute).findGroups();
2256 }
2257
2258 LOG.debug("{}: There are {} folders that match {} attribute", new Object[]{getDisplayName(), foldersReferencingAttribute.size(), attribute});
2259 LOG.debug("{}: There are {} groups that match {} attribute", new Object[]{getDisplayName(), groupsReferencingAttribute.size(), attribute});
2260
2261 interestingGroups.addAll(groupsReferencingAttribute);
2262 for ( Stem folder : foldersReferencingAttribute ) {
2263 Set<Group> groupsUnderFolder;
2264
2265 groupsUnderFolder = new GroupFinder().assignParentStemId(folder.getId()).assignStemScope(Scope.SUB).findGroups();
2266
2267 LOG.debug("{}: There are {} groups underneath folder {}", new Object[]{getDisplayName(), groupsUnderFolder.size(), folder.getName()});
2268 interestingGroups.addAll(groupsUnderFolder);
2269 }
2270 }
2271
2272 for ( Group group : interestingGroups ) {
2273 GrouperGroupInforGroupInfo.html#GrouperGroupInfo">GrouperGroupInfo grouperGroupInfo = new GrouperGroupInfo(group);
2274 if ( shouldGroupBeProvisioned(grouperGroupInfo) )
2275 result.add(grouperGroupInfo);
2276 }
2277 LOG.info("{}: There are {} groups selected for provisioning (found in %s)",
2278 getDisplayName(), result.size(), PspUtils.formatElapsedTime(start, null));
2279
2280 if (this.allGroupsForProvisionerCache != null) {
2281 this.allGroupsForProvisionerCache.put(this.provisionerConfigName, result);
2282 }
2283 return result;
2284 }
2285
2286
2287 public ConfigurationClass getConfig() {
2288 return config;
2289 }
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304 public static Class<? extends ProvisionerConfiguration> getPropertyClass() {
2305 return ProvisionerConfiguration.class;
2306 }
2307
2308 protected Map<String, Object> getGroupJexlMap(String expression, GrouperGroupInfo grouperGroupInfo) {
2309
2310 Map<String, Object> result = grouperGroupInfo.getJexlMap();
2311
2312 boolean pspngCacheGroupAndStemAttributes = GrouperLoaderConfig.retrieveConfig().propertyValueBoolean("pspngCacheGroupAndStemAttributes", true);
2313
2314 if (!pspngCacheGroupAndStemAttributes) {
2315
2316 Map<String, Object> stemAttributes = PspUtils.getStemAttributes(grouperGroupInfo.getGrouperGroup());
2317 result.put("stemAttributes", stemAttributes);
2318 Map<String, Object> groupAttributes = PspUtils.getGroupAttributes(grouperGroupInfo.getGrouperGroup());
2319 result.put("groupAttributes", groupAttributes);
2320 } else {
2321
2322
2323 if (expression != null && expression.contains("stemAttributes")) {
2324
2325 Map<String, Object> stemAttributes = PspUtils.getStemAttributes(grouperGroupInfo.getGrouperGroup());
2326 result.put("stemAttributes", stemAttributes);
2327 }
2328
2329
2330 if (expression != null && expression.contains("groupAttributes")) {
2331
2332 Map<String, Object> groupAttributes = PspUtils.getGroupAttributes(grouperGroupInfo.getGrouperGroup());
2333 result.put("groupAttributes", groupAttributes);
2334 }
2335 }
2336 return result;
2337 }
2338
2339
2340
2341
2342
2343
2344
2345
2346 protected boolean shouldGroupBeProvisionedConsiderCache(GrouperGroupInfo grouperGroupInfo) {
2347 boolean provisionable = false;
2348 int pspngCacheAllGroupProvisionableMinutes = GrouperLoaderConfig.retrieveConfig().propertyValueInt("pspngCacheAllGroupProvisionableMinutes", 2);
2349
2350 boolean pspngNonScriptProvisionable = GrouperLoaderConfig.retrieveConfig().propertyValueBoolean("pspngNonScriptProvisionable", true);
2351
2352
2353 if (pspngCacheAllGroupProvisionableMinutes > 0 && pspngNonScriptProvisionable
2354 && StringUtils.equals(config.getGroupSelectionExpression(), config.groupSelectionExpression_defaultValue())) {
2355 provisionable = this.getAllGroupsForProvisioner().contains(grouperGroupInfo);
2356
2357 } else {
2358 provisionable = this.shouldGroupBeProvisioned(grouperGroupInfo);
2359 }
2360
2361 return provisionable;
2362 }
2363
2364
2365
2366
2367
2368
2369
2370
2371 protected boolean shouldGroupBeProvisioned(GrouperGroupInfo grouperGroupInfo) {
2372 if ( grouperGroupInfo.hasGroupBeenDeleted() ) {
2373 return false;
2374 }
2375
2376 try {
2377 String resultString = evaluateJexlExpression("GroupSelection", config.getGroupSelectionExpression(), null, null, grouperGroupInfo, null);
2378
2379 boolean result = BooleanUtils.toBoolean(resultString);
2380
2381 if (result)
2382 LOG.debug("{}: Group {} matches group-selection filter.", getDisplayName(), grouperGroupInfo);
2383 else
2384 LOG.trace("{}: Group {} does not match group-selection filter.", getDisplayName(), grouperGroupInfo);
2385
2386 return result;
2387 } catch (PspException e) {
2388 LOG.warn("{}: Error evaluating groupSelection expression for group {}. Assuming group is not selected for provisioner",
2389 getDisplayName(), grouperGroupInfo, e);
2390 return false;
2391 }
2392 }
2393
2394 public String getDisplayName() {
2395 return provisionerDisplayName;
2396 }
2397
2398 public String getConfigName() { return provisionerConfigName; }
2399
2400 public void provisionBatchOfItems(List<ProvisioningWorkItem> allWorkItems) {
2401 activeProvisioner.set(this);
2402 List<ProvisioningWorkItem> filteredWorkItems=null;
2403
2404
2405
2406
2407
2408
2409 if (!config.isEnabled()) {
2410 for (ProvisioningWorkItem workItem : allWorkItems)
2411 workItem.markAsSkippedAndWarn("Provisioner %s is not enabled", getDisplayName());
2412 return;
2413 }
2414
2415
2416
2417
2418
2419 MDC.put("step", "cache_eval");
2420 try {
2421 flushCachesIfNecessary(allWorkItems);
2422 } catch (PspException e) {
2423 LOG.error("Unable to evaluate our caches", e);
2424 MDC.remove("step");
2425 throw new RuntimeException("No entries provisioned. Cache evaluation failed: " + e.getMessage(), e);
2426 }
2427
2428
2429
2430 MDC.put("step", "filter/");
2431 try {
2432 filteredWorkItems = filterWorkItems(allWorkItems);
2433 LOG.info("{}: {} work items need to be processed further", getDisplayName(), filteredWorkItems.size());
2434 } catch (PspException e) {
2435 LOG.error("Unable to filter the provisioning batch", e);
2436 MDC.remove("step");
2437 throw new RuntimeException("No entries provisioned. Batch-filtering failed: " + e.getMessage(), e);
2438 }
2439
2440
2441 MDC.put("step", "start/");
2442 try {
2443 try {
2444 startCoordination(filteredWorkItems);
2445 startProvisioningBatch(filteredWorkItems);
2446 } catch (PspException e) {
2447 LOG.error("Unable to begin the provisioning batch", e);
2448 MDC.remove("step");
2449 throw new RuntimeException("No entries provisioned. Batch-Start failed: " + e.getMessage(), e);
2450 }
2451
2452
2453
2454 for (ProvisioningWorkItem workItem : allWorkItems) {
2455 MDC.put("step", String.format("prov/%s/", workItem.getMdcLabel()));
2456 if (!workItem.hasBeenProcessed()) {
2457 try {
2458 provisionItem(workItem);
2459 } catch (PspException e) {
2460 LOG.error("Problem provisioning {}", workItem, e);
2461 workItem.markAsFailure(e.getMessage());
2462 }
2463 }
2464 }
2465
2466
2467
2468 MDC.put("step", "fin/");
2469 List<ProvisioningWorkItem> workItemsToFinish = new ArrayList<ProvisioningWorkItem>();
2470 try {
2471 for (ProvisioningWorkItem workItem : allWorkItems) {
2472 if (!workItem.hasBeenProcessed())
2473 workItemsToFinish.add(workItem);
2474 }
2475 finishProvisioningBatch(workItemsToFinish);
2476 finishCoordination(filteredWorkItems, true);
2477 } catch (PspException e) {
2478 LOG.error("Problem completing provisioning batch", e);
2479 for (ProvisioningWorkItem workItem : workItemsToFinish) {
2480 if (!workItem.hasBeenProcessed())
2481 workItem.markAsFailure("Unable to finish provisioning (%s)", e.getMessage());
2482 }
2483 }
2484 MDC.remove("step");
2485 }
2486 finally{
2487 finishCoordination(filteredWorkItems, false);
2488 activeProvisioner.remove();
2489 }
2490 }
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501 protected void flushCachesIfNecessary(List<ProvisioningWorkItem> allWorkItems) throws PspException{
2502 for (ProvisioningWorkItem workItem : allWorkItems ) {
2503
2504
2505 if (!workItem.matchesChangelogType(ChangelogHandlingConfig.allRelevantChangelogTypes)) {
2506 continue;
2507 }
2508
2509
2510 if (!workItem.matchesChangelogType(ChangelogHandlingConfig.relevantChangesThatNeedGroupCacheFlushing)) {
2511 continue;
2512 }
2513
2514
2515
2516 GrouperGroupInfo groupInfo = workItem.getGroupInfo(this);
2517 if (groupInfo != null) {
2518 uncacheGroup(groupInfo, null);
2519 } else {
2520
2521 uncacheAllGroups();
2522 return;
2523 }
2524 }
2525 }
2526
2527
2528 public boolean isFullSyncMode() {
2529 return fullSyncMode;
2530 }
2531
2532
2533 @Override
2534 public String toString() {
2535 return String.format("%s[%s]", getClass().getSimpleName(), getDisplayName());
2536 }
2537
2538
2539
2540
2541
2542
2543
2544 public boolean workItemMightChangeGroupSelection(ProvisioningWorkItem workItem) {
2545
2546 if ( !workItem.matchesChangelogType(ChangelogHandlingConfig.changelogTypesThatCanChangeGroupSelection) ) {
2547 return false;
2548 }
2549
2550 String attributeName = workItem.getAttributeName();
2551
2552
2553
2554 if (StringUtils.isEmpty(attributeName)) {
2555 LOG.info("{}: Change might change group selection: {}", getDisplayName(), workItem);
2556 return true;
2557 }
2558
2559 if ( getConfig().attributesUsedInGroupSelectionExpression.contains(attributeName) ) {
2560 LOG.info("{}: Change changes {} which might change group selection: {}",
2561 new Object[]{getDisplayName(), attributeName, workItem});
2562 return true;
2563 }
2564
2565 return false;
2566 }
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578 protected boolean shouldLogAboutMissingSubjects(Collection<Subject> subjectsToFetch, Collection<?> subjectsInfoFound) {
2579 int missingResults_count = subjectsToFetch.size() - subjectsInfoFound.size();
2580 double missingResults_percentage = 100.0 * missingResults_count / subjectsToFetch.size();
2581
2582 if (subjectsToFetch.size() > 5 && missingResults_percentage > config.getMissingSubjectsWarningThreshold_percentage()) {
2583 return true;
2584 }
2585 return false;
2586 }
2587
2588
2589
2590
2591 }