1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package edu.internet2.middleware.grouper.rules;
17
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24
25 import edu.internet2.middleware.grouperClient.collections.MultiKey;
26 import org.apache.commons.lang.StringUtils;
27 import org.apache.commons.lang.exception.ExceptionUtils;
28 import org.apache.commons.logging.Log;
29
30 import edu.emory.mathcs.backport.java.util.Collections;
31 import edu.internet2.middleware.grouper.Group;
32 import edu.internet2.middleware.grouper.GroupFinder;
33 import edu.internet2.middleware.grouper.GrouperSession;
34 import edu.internet2.middleware.grouper.Stem;
35 import edu.internet2.middleware.grouper.app.loader.GrouperLoaderStatus;
36 import edu.internet2.middleware.grouper.app.loader.db.Hib3GrouperLoaderLog;
37 import edu.internet2.middleware.grouper.attr.AttributeDefName;
38 import edu.internet2.middleware.grouper.attr.assign.AttributeAssign;
39 import edu.internet2.middleware.grouper.attr.value.AttributeAssignValueContainer;
40 import edu.internet2.middleware.grouper.cache.GrouperCache;
41 import edu.internet2.middleware.grouper.cfg.GrouperConfig;
42 import edu.internet2.middleware.grouper.exception.GrouperSessionException;
43 import edu.internet2.middleware.grouper.hooks.examples.GrouperAttributeAssignValueRulesConfigHook;
44 import edu.internet2.middleware.grouper.internal.dao.QueryOptions;
45 import edu.internet2.middleware.grouper.misc.GrouperCheckConfig;
46 import edu.internet2.middleware.grouper.misc.GrouperDAOFactory;
47 import edu.internet2.middleware.grouper.misc.GrouperSessionHandler;
48 import edu.internet2.middleware.grouper.rules.beans.RulesBean;
49 import edu.internet2.middleware.grouper.util.GrouperUtil;
50 import edu.internet2.middleware.subject.Subject;
51
52
53
54
55
56
57 public class RuleEngine {
58
59
60
61
62 static long ruleFirings = 0;
63
64
65 static GrouperCache<Boolean, RuleEngine> ruleEngineCache =
66 new GrouperCache<Boolean, RuleEngine>(RuleEngine.class.getName() + ".ruleEngine", 100, false, 5*60, 5*60, false);
67
68
69 private Set<RuleDefinition> ruleDefinitions = new HashSet<RuleDefinition>();
70
71
72
73
74
75 public Set<RuleDefinition> getRuleDefinitions() {
76 return this.ruleDefinitions;
77 }
78
79
80
81
82
83
84 public Set<RuleDefinition> ruleCheckIndexDefinitionsByNameOrId(RuleCheck ruleCheck) {
85
86 if (!GrouperConfig.retrieveConfig().propertyValueBoolean("rules.enable", true)) {
87 return null;
88 }
89
90 String ownerId = ruleCheck.getCheckOwnerId();
91 String ownerName = ruleCheck.getCheckOwnerName();
92
93 Set<RuleDefinition> theRuleDefinitions = new HashSet<RuleDefinition>();
94
95 if (!StringUtils.isBlank(ownerId)) {
96 ruleCheck.setCheckOwnerName(null);
97 Set<RuleDefinition> ruleDefinitionsToAdd = GrouperUtil.nonNull(this.getRuleCheckIndex().get(ruleCheck));
98
99 if (LOG.isDebugEnabled() && GrouperConfig.retrieveConfig().propertyValueBoolean("rules.logWhyRulesDontFire", false)) {
100
101 StringBuilder logMessage = new StringBuilder("Checking rules by id: ");
102 logMessage.append(ownerId);
103 logMessage.append(", ids found: ");
104 for (RuleDefinition current : ruleDefinitionsToAdd) {
105 logMessage.append(current.getCheck().getCheckOwnerId()).append(",");
106 }
107
108 logMessage.append("... ids in rules engine: ");
109 for (RuleCheck current : this.getRuleCheckIndex().keySet()) {
110 if (!StringUtils.isBlank(current.getCheckOwnerId())) {
111 logMessage.append(current.getCheckOwnerId()).append(",");
112 }
113 }
114 LOG.debug(logMessage);
115 }
116
117 theRuleDefinitions.addAll(ruleDefinitionsToAdd);
118 ruleCheck.setCheckOwnerName(ownerName);
119 }
120
121 if (!StringUtils.isBlank(ownerName)) {
122 ruleCheck.setCheckOwnerId(null);
123 Set<RuleDefinition> ruleDefinitionsToAdd = GrouperUtil.nonNull(this.getRuleCheckIndex().get(ruleCheck));
124
125 if (LOG.isDebugEnabled() && GrouperConfig.retrieveConfig().propertyValueBoolean("rules.logWhyRulesDontFire", false)) {
126
127 StringBuilder logMessage = new StringBuilder("Checking rules by name: ");
128 logMessage.append(ownerName);
129 logMessage.append(", names found: ");
130 for (RuleDefinition current : ruleDefinitionsToAdd) {
131 logMessage.append(current.getCheck().getCheckOwnerName()).append(",");
132 }
133
134 logMessage.append("... names in rules engine: ");
135 for (RuleCheck current : this.getRuleCheckIndex().keySet()) {
136 if (!StringUtils.isBlank(current.getCheckOwnerName())) {
137 logMessage.append(current.getCheckOwnerName()).append(",");
138 }
139 }
140 LOG.debug(logMessage);
141 }
142
143 theRuleDefinitions.addAll(ruleDefinitionsToAdd);
144 ruleCheck.setCheckOwnerId(ownerId);
145 }
146
147 return theRuleDefinitions;
148 }
149
150
151
152
153
154
155 public Set<RuleDefinition> ruleCheckIndexDefinitionsByNameOrIdInFolder(RuleCheck ruleCheck) {
156
157 if (!GrouperConfig.retrieveConfig().propertyValueBoolean("rules.enable", true)) {
158 return null;
159 }
160
161 String ownerName = ruleCheck.getCheckOwnerName();
162
163 if (StringUtils.isBlank(ownerName)) {
164 throw new RuntimeException("Checking in folder needs an owner name: " + ruleCheck);
165 }
166
167 Set<RuleDefinition> ruleDefinitions = new HashSet<RuleDefinition>();
168
169
170 String parentStemName = GrouperUtil.parentStemNameFromName(ownerName);
171
172 RuleCheckles/RuleCheck.html#RuleCheck">RuleCheck tempRuleCheck = new RuleCheck(ruleCheck.getCheckType(), null, parentStemName, Stem.Scope.ONE.name(), null, null);
173 ruleDefinitions.addAll(GrouperUtil.nonNull(this.getRuleCheckIndex().get(tempRuleCheck)));
174
175
176 Set<String> parentStemNames = GrouperUtil.findParentStemNames(ownerName);
177
178 for (String ancestorStemName : parentStemNames) {
179
180 tempRuleCheck = new RuleCheck(ruleCheck.getCheckType(), null, ancestorStemName, Stem.Scope.SUB.name(), null, null);
181 ruleDefinitions.addAll(GrouperUtil.nonNull(this.getRuleCheckIndex().get(tempRuleCheck)));
182
183 }
184
185 return ruleDefinitions;
186 }
187
188
189
190
191
192 public void setRuleDefinitions(Set<RuleDefinition> ruleDefinitions) {
193 this.ruleDefinitions = ruleDefinitions;
194 }
195
196
197
198
199
200 public static RuleEngine ruleEngine() {
201 RuleEngine ruleEngine = ruleEngineCache.get(Boolean.TRUE);
202
203 if (ruleEngine == null) {
204
205 synchronized (RuleEngine.class) {
206 ruleEngine = ruleEngineCache.get(Boolean.TRUE);
207 if (ruleEngine == null) {
208
209 GrouperSession grouperSession = GrouperSession.staticGrouperSession().internal_getRootSession();
210 ruleEngine = (RuleEngine)GrouperSession.callbackGrouperSession(grouperSession, new GrouperSessionHandler() {
211
212 public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
213 Map<AttributeAssign, Set<AttributeAssignValueContainer>> attributeAssignValueContainers
214 = allRulesAttributeAssignValueContainers(null);
215
216 RuleEngine/rules/RuleEngine.html#RuleEngine">RuleEngine newEngine = new RuleEngine();
217 Set<RuleDefinition> newDefinitions = newEngine.getRuleDefinitions();
218
219 for (Set<AttributeAssignValueContainer> attributeAssignValueContainersSet :
220 GrouperUtil.nonNull(attributeAssignValueContainers).values()) {
221 RuleDefinitionleDefinition.html#RuleDefinition">RuleDefinition ruleDefinition = new RuleDefinition(attributeAssignValueContainersSet);
222 if (ruleDefinition.isValidInAttributes()) {
223
224 newDefinitions.add(ruleDefinition);
225 }
226 }
227
228 newEngine.indexData();
229 ruleEngineCache.put(Boolean.TRUE, newEngine);
230 return newEngine;
231 }
232 });
233
234 }
235 }
236
237 }
238
239 return ruleEngine;
240
241 }
242
243
244 private Map<RuleCheck, Set<RuleDefinition>> ruleCheckIndex = null;
245
246
247
248
249
250
251 public Map<RuleCheck, Set<RuleDefinition>> getRuleCheckIndex() {
252 return this.ruleCheckIndex;
253 }
254
255
256 private static final Log LOG = GrouperUtil.getLog(RuleEngine.class);
257
258
259
260
261 private void indexData() {
262 this.ruleCheckIndex = new HashMap<RuleCheck, Set<RuleDefinition>>();
263
264 for (RuleDefinition ruleDefinition : GrouperUtil.nonNull(this.ruleDefinitions)) {
265
266 RuleCheck originalRuleCheck = ruleDefinition.getCheck();
267 RuleCheck ruleCheck = originalRuleCheck.checkTypeEnum().checkKey(ruleDefinition);
268
269 if (StringUtils.isBlank(ruleCheck.getCheckOwnerId())
270 && StringUtils.isBlank(ruleCheck.getCheckOwnerName())) {
271
272 LOG.error("Why are ownerId and ownerName blank for this rule: " + ruleDefinition);
273 continue;
274 }
275
276 Set<RuleDefinition> checkRuleDefinitions = this.ruleCheckIndex.get(ruleCheck);
277
278
279 if (checkRuleDefinitions == null) {
280 checkRuleDefinitions = new HashSet<RuleDefinition>();
281 this.ruleCheckIndex.put(ruleCheck, checkRuleDefinitions);
282 }
283
284 checkRuleDefinitions.add(ruleDefinition);
285
286 }
287
288 }
289
290
291
292
293
294
295 private static Set<RuleDefinition> listPopOneLogError(Set<RuleDefinition> ruleDefinitions) {
296 int length = GrouperUtil.length(ruleDefinitions);
297 if (length == 1) {
298 return ruleDefinitions;
299 }
300 if (length == 0) {
301 throw new RuntimeException("Why is length 0");
302 }
303 RuleDefinition ruleDefinition = ruleDefinitions.iterator().next();
304 LOG.error("Why is there more than 1? " + length + ", " + ruleDefinition);
305 return GrouperUtil.toSet(ruleDefinition);
306 }
307
308
309
310
311
312
313 public Set<RuleDefinition> ruleCheckIndexDefinitionsByNameOrIdInFolderPickOneArgOptional(RuleCheck ruleCheck) {
314
315 if (!GrouperConfig.retrieveConfig().propertyValueBoolean("rules.enable", true)) {
316 return null;
317 }
318
319
320 String ownerName = ruleCheck.getCheckOwnerName();
321
322 if (StringUtils.isBlank(ownerName)) {
323 throw new RuntimeException("Checking in folder needs an owner name: " + ruleCheck);
324 }
325
326 Set<RuleDefinition> ruleDefinitions = null;
327
328
329 RuleCheckles/RuleCheck.html#RuleCheck">RuleCheck tempRuleCheck = new RuleCheck(ruleCheck.getCheckType(), null, ownerName, Stem.Scope.ONE.name(), ruleCheck.getCheckArg0(), null);
330
331 ruleDefinitions = this.getRuleCheckIndex().get(tempRuleCheck);
332
333 if (GrouperUtil.length(ruleDefinitions) > 0) {
334 return listPopOneLogError(ruleDefinitions);
335 }
336
337 tempRuleCheck.setCheckArg0(null);
338
339
340 ruleDefinitions = this.getRuleCheckIndex().get(tempRuleCheck);
341
342 if (GrouperUtil.length(ruleDefinitions) > 0) {
343 return listPopOneLogError(ruleDefinitions);
344 }
345
346
347 List<String> stemNameList = null;
348
349 {
350 Set<String> stemNameSet = GrouperUtil.findParentStemNames(ownerName);
351
352 stemNameSet.add(ownerName);
353
354 stemNameList = new ArrayList<String>(stemNameSet);
355
356 Collections.reverse(stemNameList);
357 }
358
359
360 for (String ancestorStemName : stemNameList) {
361
362 tempRuleCheck = new RuleCheck(ruleCheck.getCheckType(), null, ancestorStemName, Stem.Scope.SUB.name(), ruleCheck.getCheckArg0(), null);
363
364 ruleDefinitions = this.getRuleCheckIndex().get(tempRuleCheck);
365
366 if (GrouperUtil.length(ruleDefinitions) > 0) {
367 return listPopOneLogError(ruleDefinitions);
368 }
369
370 tempRuleCheck.setCheckArg0(null);
371
372
373 ruleDefinitions = this.getRuleCheckIndex().get(tempRuleCheck);
374
375 if (GrouperUtil.length(ruleDefinitions) > 0) {
376 return listPopOneLogError(ruleDefinitions);
377 }
378 }
379
380 return new HashSet<RuleDefinition>();
381
382 }
383
384
385
386
387
388
389 public static void fireRule(final RuleCheckType ruleCheckType, final RulesBean rulesBean) {
390
391 if (GrouperCheckConfig.inCheckConfig) {
392 return;
393 }
394
395 if (!GrouperConfig.retrieveConfig().propertyValueBoolean("rules.enable", true)) {
396 return;
397 }
398
399 final RuleEngine ruleEngine = ruleEngine();
400
401 if (ruleEngine == null) {
402 throw new NullPointerException("ruleEngine cannot be null");
403 }
404
405 StringBuilder logData = null;
406 boolean shouldLog = LOG.isDebugEnabled();
407 try {
408
409 Set<RuleDefinition> ruleDefinitions = ruleCheckType.ruleDefinitions(ruleEngine, rulesBean);
410
411
412 for (final RuleDefinition ruleDefinition : GrouperUtil.nonNull(ruleDefinitions)) {
413 shouldLog = shouldLog || ruleDefinition.shouldLog();
414 }
415
416 if (shouldLog) {
417 logData = new StringBuilder();
418 logData.append("Rules engine processing rulesBean: " + rulesBean);
419 }
420
421
422 if (shouldLog) {
423 logData.append(", found " + GrouperUtil.length(ruleDefinitions) + " matching rule definitions");
424 }
425
426 int shouldFireCount = 0;
427
428 for (final RuleDefinition ruleDefinition : GrouperUtil.nonNull(ruleDefinitions)) {
429
430 boolean shouldLogThisDefinition = LOG.isDebugEnabled() || ruleDefinition.shouldLog();
431 final StringBuilder logDataForThisDefinition = shouldLogThisDefinition ? logData : null;
432 if (ruleDefinition.getIfCondition().shouldFire(ruleDefinition, ruleEngine, rulesBean, logDataForThisDefinition)) {
433
434 shouldFireCount++;
435
436 if (shouldLogThisDefinition) {
437 logDataForThisDefinition.append(", ruleDefinition should fire: ").append(ruleDefinition.toString());
438 }
439
440
441 RuleSubjectActAs actAs = ruleDefinition.getActAs();
442 Subject actAsSubject = actAs.subject(false);
443 if (actAsSubject == null) {
444 LOG.error("Cant find subject for rule: " + ruleDefinition);
445 } else {
446
447
448 GrouperSession grouperSession = GrouperSession.staticGrouperSession(false);
449 Subject subjectGrouperSession = grouperSession == null ? null : grouperSession.getSubject();
450
451 if (subjectGrouperSession != null) {
452 rulesBean.setSubjectUnderlyingSession(subjectGrouperSession);
453 }
454
455 GrouperSession.callbackGrouperSession(GrouperSession.start(actAsSubject, false), new GrouperSessionHandler() {
456
457
458
459
460 public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
461
462 ruleFirings++;
463 ruleDefinition.getThen().fireRule(ruleDefinition, ruleEngine, rulesBean, logDataForThisDefinition);
464 return null;
465
466 }
467 });
468 }
469 }
470
471 }
472
473 if (shouldLog) {
474 logData.append(", shouldFire count: " + shouldFireCount);
475 }
476 } catch (RuntimeException re) {
477 if (shouldLog && logData != null) {
478 logData.append(", EXCEPTION: ").append(ExceptionUtils.getFullStackTrace(re));
479 }
480 throw re;
481 } finally {
482 if (shouldLog && logData != null) {
483 if (LOG.isDebugEnabled()) {
484 LOG.debug(logData);
485 } else if (LOG.isInfoEnabled()) {
486 LOG.info(logData);
487 }
488 }
489 }
490 }
491
492
493
494
495
496
497 public static Map<AttributeAssign, Set<AttributeAssignValueContainer>> allRulesAttributeAssignValueContainers(QueryOptions queryOptions) {
498
499 AttributeDefName ruleTypeDefName = RuleUtils.ruleAttributeDefName();
500
501 Map<AttributeAssign, Set<AttributeAssignValueContainer>> result = GrouperDAOFactory.getFactory()
502 .getAttributeAssign().findByAttributeTypeDefNameId(ruleTypeDefName.getId(), queryOptions);
503
504
505
506
507
508
509
510 return result;
511
512 }
513
514
515
516
517
518 public static int daemon() {
519 return daemon(null);
520 }
521
522
523
524
525
526
527 public static int daemon(Hib3GrouperLoaderLog hib3GrouperLoaderLog) {
528
529
530 final Map<AttributeAssign, Set<AttributeAssignValueContainer>> attributeAssignValueContainers
531 = allRulesAttributeAssignValueContainers(new QueryOptions().secondLevelCache(false));
532
533 GrouperSession grouperSession = GrouperSession.startRootSession(false);
534
535 return (Integer)GrouperSession.callbackGrouperSession(grouperSession, new GrouperSessionHandler() {
536
537 public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
538 Throwable throwable = null;
539 int numberOfErrors = 0;
540
541 GrouperAttributeAssignValueRulesConfigHook.getThreadLocalInValidateRule().set(Boolean.TRUE);
542 int rulesChanged = 0;
543 try {
544
545 for (Set<AttributeAssignValueContainer> attributeAssignValueContainersSet :
546 GrouperUtil.nonNull(attributeAssignValueContainers).values()) {
547
548 RuleDefinition ruleDefinition = null;
549 try {
550 ruleDefinition = new RuleDefinition(attributeAssignValueContainersSet);
551
552 String validReason = ruleDefinition.validate();
553 boolean ruleChanged = false;
554 if (StringUtils.isBlank(validReason)) {
555 validReason = "T";
556
557
558 ruleDefinition.runDaemonOnDefinitionIfShould();
559
560 if (!ruleDefinition.isValidInAttributes()) {
561 rulesChanged++;
562 ruleChanged = true;
563 }
564 } else {
565 validReason = "INVALID: " + validReason;
566
567
568 LOG.error("Invalid rule definition: "
569 + validReason + ", ruleDefinition: " + ruleDefinition);
570
571 if (ruleDefinition.isValidInAttributes()) {
572 rulesChanged++;
573 ruleChanged = true;
574 }
575 }
576
577 if (ruleChanged) {
578 AttributeAssign typeAttributeAssign = ruleDefinition.getAttributeAssignType();
579
580 typeAttributeAssign.getAttributeValueDelegate().assignValue(RuleUtils.ruleValidName(), validReason);
581 }
582
583 } catch (Exception e) {
584 numberOfErrors++;
585 LOG.error("Error with daemon on rule: " + ruleDefinition, e);
586 if (throwable == null) {
587 throwable = e;
588 }
589 }
590 }
591 } finally {
592 GrouperAttributeAssignValueRulesConfigHook.getThreadLocalInValidateRule().set(Boolean.FALSE);
593 }
594
595 if (hib3GrouperLoaderLog != null) {
596 hib3GrouperLoaderLog.setUpdateCount(rulesChanged);
597 hib3GrouperLoaderLog.setJobMessage("Ran rules daemon, changed " + rulesChanged + " records, there were " + numberOfErrors + " errors.");
598 if (numberOfErrors > 0) {
599 hib3GrouperLoaderLog.setStatus(GrouperLoaderStatus.ERROR.name());
600 hib3GrouperLoaderLog.appendJobMessage(" All errors logged but here's one: " + ExceptionUtils.getFullStackTrace(throwable));
601 } else {
602 hib3GrouperLoaderLog.setStatus(GrouperLoaderStatus.SUCCESS.name());
603 }
604 }
605
606 return rulesChanged;
607 }
608 });
609 }
610
611
612
613
614 private static GrouperCache<MultiKey, Boolean> subjectHasAccessToApi =
615 new GrouperCache<MultiKey, Boolean>(RuleEngine.class.getName() + ".hasAccessToElApi", 1000, false, 150, 150, false);
616
617
618
619
620 static void clearSubjectHasAccessToElApi() {
621 subjectHasAccessToApi.clear();
622 }
623
624
625
626
627 public static void clearRuleEngineCache() {
628 ruleEngineCache.clear();
629 }
630
631
632
633
634
635
636 public static boolean hasAccessToElApi(final Subject subject) {
637
638 final String hasAccessToGroupName = GrouperConfig.retrieveConfig().propertyValueString("rules.accessToApiInEl.group");
639
640 if (StringUtils.isBlank(hasAccessToGroupName)) {
641 return false;
642 }
643
644 final MultiKey subjectKey = new MultiKey(subject.getSourceId(), subject.getId());
645
646 Boolean result = subjectHasAccessToApi.get(subjectKey);
647 if (result == null) {
648
649
650 GrouperSession grouperSession = GrouperSession.staticGrouperSession().internal_getRootSession();
651
652 result = (Boolean)GrouperSession.callbackGrouperSession(grouperSession, new GrouperSessionHandler() {
653
654 public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
655
656 Group hasAccessGroup = GroupFinder.findByName(grouperSession, hasAccessToGroupName, true);
657
658 boolean result = hasAccessGroup.hasMember(subject);
659
660
661 subjectHasAccessToApi.put(subjectKey, result);
662
663 return result;
664 }
665 });
666
667 }
668
669 return result;
670 }
671
672
673 }