View Javadoc
1   /**
2    * Copyright 2014 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * processes rules and kicks off actions
54   * @author mchyzer
55   *
56   */
57  public class RuleEngine {
58  
59    /**
60     * used for testing to see how many rule firings there are (that pass the check and if)
61     */
62    static long ruleFirings = 0;
63    
64    /** cached rule definitions */
65    static GrouperCache<Boolean, RuleEngine> ruleEngineCache = 
66      new GrouperCache<Boolean, RuleEngine>(RuleEngine.class.getName() + ".ruleEngine", 100, false, 5*60, 5*60, false);
67    
68    /** rule definitions */
69    private Set<RuleDefinition> ruleDefinitions = new HashSet<RuleDefinition>();
70    
71    /**
72     * rule definitions
73     * @return the ruleDefinitions
74     */
75    public Set<RuleDefinition> getRuleDefinitions() {
76      return this.ruleDefinitions;
77    }
78  
79    /**
80     * get rule definitions from cache based on name or id
81     * @param ruleCheck
82     * @return the definitions
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    * get rule definitions from cache based on name or id
152    * @param ruleCheck
153    * @return the definitions
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     //direct parent
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     //direct parent or ancestor
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    * rule definitions
190    * @param ruleDefinitions the ruleDefinitions to set
191    */
192   public void setRuleDefinitions(Set<RuleDefinition> ruleDefinitions) {
193     this.ruleDefinitions = ruleDefinitions;
194   }
195 
196   /**
197    * 
198    * @return all the rule definitions, cached
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                   //dont validate, already validated
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   /** map of checks to sets of relevant rules */
244   private Map<RuleCheck, Set<RuleDefinition>> ruleCheckIndex = null;
245 
246   
247   /**
248    * map of checks to sets of relevant rules
249    * @return the ruleCheckIndex
250    */
251   public Map<RuleCheck, Set<RuleDefinition>> getRuleCheckIndex() {
252     return this.ruleCheckIndex;
253   }
254 
255   /** logger */
256   private static final Log LOG = GrouperUtil.getLog(RuleEngine.class);
257   
258   /**
259    * index the rules so they can be searched quickly
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       //if this list isnt there, then make one
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    * pop one and log error if multiple
292    * @param ruleDefinitions
293    * @return the set
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    * get rule definitions from cache based on name or id
310    * @param ruleCheck
311    * @return the definitions
312    */
313   public Set<RuleDefinition> ruleCheckIndexDefinitionsByNameOrIdInFolderPickOneArgOptional(RuleCheck ruleCheck) {
314   
315     if (!GrouperConfig.retrieveConfig().propertyValueBoolean("rules.enable", true)) {
316       return null;
317     }
318 
319     //owner name is the stem name
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     //see if there is a direct that matches the arg
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     //see if there is a direct without a matching arg
340     ruleDefinitions = this.getRuleCheckIndex().get(tempRuleCheck);
341     
342     if (GrouperUtil.length(ruleDefinitions) > 0) {
343       return listPopOneLogError(ruleDefinitions);
344     }
345     
346     //we need the stems, 
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     //loop through all stems from more specific to less specific
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       //see if there is a direct without a matching arg
373       ruleDefinitions = this.getRuleCheckIndex().get(tempRuleCheck);
374       
375       if (GrouperUtil.length(ruleDefinitions) > 0) {
376         return listPopOneLogError(ruleDefinitions);
377       }
378     }
379     //didnt find anything, dont return null
380     return new HashSet<RuleDefinition>();
381 
382   }
383 
384   /**
385    * find rules and fire them
386    * @param ruleCheckType
387    * @param rulesBean
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       //see if we should log
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           //act as the act as
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             //lets get the currect subject and put it in the bean...
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                 //we are firing, note this isnt synchronized, so might not be exact, but used for testing, so thats ok
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    * get all rules from the DB in the form of attribute assignments
494    * @param queryOptions 
495    * @return the assigns of all rules
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     //List<AttributeAssign> keyList = new ArrayList<AttributeAssign>(result.keySet());
505     //List<List<AttributeAssignValueContainer>> valuesList = new ArrayList<List<AttributeAssignValueContainer>>();
506     //for (AttributeAssign attributeAssign: keyList) {
507     //  valuesList.add(new ArrayList<AttributeAssignValueContainer>(result.get(attributeAssign)));
508     //}
509     
510     return result;
511     
512   }
513   
514   /**
515    * validate the rules, and run the daemon stuff in rules
516    * @return the number of records changed
517    */
518   public static int daemon() {
519     return daemon(null);
520   }
521 
522   /**
523    * validate the rules, and run the daemon stuff in rules
524    * @param hib3GrouperLoaderLog
525    * @return the number of records changed
526    */
527   public static int daemon(Hib3GrouperLoaderLog hib3GrouperLoaderLog) {
528     
529     //get all enabled rules
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                 //run daemon on rule if should
558                 ruleDefinition.runDaemonOnDefinitionIfShould();
559                 
560                 if (!ruleDefinition.isValidInAttributes()) {
561                   rulesChanged++;
562                   ruleChanged = true;
563                 }
564               } else {
565                 validReason = "INVALID: " + validReason;
566         
567                 //throw out invalid rules
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    * multikey is sourceId and subjectId , default is 2.5 minutes
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    * clear this for testing
619    */
620   static void clearSubjectHasAccessToElApi() {
621     subjectHasAccessToApi.clear();
622   }
623   
624   /**
625    * clear this for testing
626    */
627   public static void clearRuleEngineCache() {
628     ruleEngineCache.clear();
629   }
630   
631   /**
632    * see if a subejct (e.g. act as subject) has access to the EL api
633    * @param subject
634    * @return true if has access, flase if not, use the cache
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       //go to root
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           //add back to cache
661           subjectHasAccessToApi.put(subjectKey, result);
662           
663           return result;
664         }
665       });
666       
667     }
668 
669     return result;
670   }
671   
672   
673 }