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  /*--
17  $Id: SourceManager.java,v 1.12 2009-08-13 06:26:30 mchyzer Exp $
18  $Date: 2009-08-13 06:26:30 $
19  
20  Copyright 2005 Internet2 and Stanford University.  All Rights Reserved.
21  See doc/license.txt in this distribution.
22   */
23  
24  package edu.internet2.middleware.subject.provider;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.LinkedHashSet;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.commons.lang.StringUtils;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.xml.sax.SAXException;
41  
42  import edu.internet2.middleware.grouper.cache.GrouperCacheDatabase;
43  import edu.internet2.middleware.grouper.cache.GrouperCacheDatabaseClear;
44  import edu.internet2.middleware.grouper.cache.GrouperCacheDatabaseClearInput;
45  import edu.internet2.middleware.grouper.cfg.GrouperConfig;
46  import edu.internet2.middleware.grouper.cfg.dbConfig.GrouperConfigHibernate;
47  import edu.internet2.middleware.grouper.externalSubjects.ExternalSubjectAutoSourceAdapter;
48  import edu.internet2.middleware.grouperClient.util.ExpirableCache;
49  import edu.internet2.middleware.subject.Source;
50  import edu.internet2.middleware.subject.SourceUnavailableException;
51  import edu.internet2.middleware.subject.Subject;
52  import edu.internet2.middleware.subject.SubjectType;
53  import edu.internet2.middleware.subject.SubjectUtils;
54  import edu.internet2.middleware.subject.config.SubjectConfig;
55  
56  /**
57   * Factory to load and get Sources.  Sources are defined
58   * in a configuration file named, subject.properties, and must
59   * be placed in the classpath.<p>
60   *
61   */
62  public class SourceManager {
63  
64    //  <init-param>
65    //  <param-name>statusLabel<param-name>
66    //  <param-value>status</param-value>
67    //</init-param>
68    //<!-- available statuses from screen (if not specified, any will be allowed). comma separated list - - >
69    //<init-param>
70    //  <param-name>statusesFromUser<param-name>
71    //  <param-value>Active, Inactive, Pending, All</param-value>
72    //</init-param>
73    //<!-- all label from the user - - >
74    //<init-param>
75    //  <param-name>statusAllFromUser<param-name>
76    //  <param-value>All</param-value>
77    //</init-param>
78    
79    /**
80     * bean to hold the status stuff across all sources
81     */
82    public static class SourceManagerStatusBean {
83  
84      /**
85       * map of source id to status
86       * @return the source id
87       */
88      public Map<String, SubjectStatusConfig> getSourceIdToStatusConfigs() {
89        return this.sourceIdToStatusConfigs;
90      }
91  
92      /**
93       * map of source id to status
94       */
95      private Map<String, SubjectStatusConfig> sourceIdToStatusConfigs = new HashMap<String, SubjectStatusConfig>();
96      
97      /**
98       * search string from user which represents the status.  e.g. status=active
99       */
100     private Set<String> statusLabels = new HashSet<String>();
101     
102     /**
103      * available statuses from screen (if not specified, any will be allowed). comma separated list
104      */
105     private Set<String> statusesFromUser = new HashSet<String>();
106     
107     /**
108      * all label from the user
109      */
110     private Set<String> statusAllFromUser = new HashSet<String>();
111 
112     /**
113      * search string from user which represents the status.  e.g. status=active
114      * @return search string
115      */
116     public Set<String> getStatusLabels() {
117       return this.statusLabels;
118     }
119 
120     /**
121      * available statuses from screen (if not specified, any will be allowed). comma separated list
122      * @return available statuses
123      */
124     public Set<String> getStatusesFromUser() {
125       return this.statusesFromUser;
126     }
127 
128     /**
129      * all label from the user
130      * @return status from user
131      */
132     public Set<String> getStatusAllFromUser() {
133       return this.statusAllFromUser;
134     }
135     
136     /**
137      * loop through config beans from subject.properties 
138      */
139     public void processConfigBeans() {
140       
141       for (SubjectStatusConfig subjectStatusConfig : this.sourceIdToStatusConfigs.values()) {
142 
143         {
144           String statusLabel = subjectStatusConfig.getStatusLabel();
145           if (!StringUtils.isBlank(statusLabel)) {
146             this.statusLabels.add(statusLabel);
147           }
148         }
149         
150         {
151           Set<String> statusesFromUser = subjectStatusConfig.getStatusesFromUser();
152           this.statusesFromUser.addAll(statusesFromUser);
153         }
154 
155         {
156           String statusAllFromUser = subjectStatusConfig.getStatusAllFromUser();
157           if (!StringUtils.isBlank(statusAllFromUser)) {
158             this.statusAllFromUser.add(statusAllFromUser);
159           }
160         }
161       }
162     }
163   }
164   
165   public static void clearAllSources() {
166     manager = null;
167   }
168   
169   /**
170    * search string from user which represents the status.  e.g. status=active
171    */
172   private static ExpirableCache<Boolean, SourceManagerStatusBean> sourceManagerStatusBeanCache = 
173       new ExpirableCache<Boolean, SourceManagerStatusBean>();
174 
175   /**
176    * get status information across all sources
177    * @return the status rollup bean
178    */
179   public SourceManagerStatusBean getSourceManagerStatusBean() {
180     SourceManagerStatusBean sourceManagerStatusBean = sourceManagerStatusBeanCache.get(true);
181     if (sourceManagerStatusBean == null) {
182       synchronized (sourceManagerStatusBeanCache) {
183         sourceManagerStatusBean = sourceManagerStatusBeanCache.get(true);
184         if (sourceManagerStatusBean == null) {
185           
186           sourceManagerStatusBean = new SourceManagerStatusBean();
187           
188           for (Source source : getInstance().getSources()) {
189             
190             SubjectStatusConfigatusConfig.html#SubjectStatusConfig">SubjectStatusConfig subjectStatusConfig = new SubjectStatusConfig(source);
191             sourceManagerStatusBean.getSourceIdToStatusConfigs().put(source.getId(), subjectStatusConfig);
192             
193           }
194           
195           sourceManagerStatusBean.processConfigBeans();
196           
197           sourceManagerStatusBeanCache.put(Boolean.TRUE, sourceManagerStatusBean);
198           
199         }
200       }
201     }
202     return sourceManagerStatusBean;
203   }
204   
205   /**
206    * print out the config for the subject API
207    * @return the config
208    */
209   public String printConfig() {
210     try {
211       StringBuilder result = new StringBuilder();
212 
213       File subjectPropertiesFile = SubjectUtils.fileFromResourceName("subject.properties");
214       String subjectPropertiesFileLocation = subjectPropertiesFile == null ? " [cant find subject.properties]"
215           : SubjectUtils.fileCanonicalPath(subjectPropertiesFile);
216       result.append("subject.properties read from: " + subjectPropertiesFileLocation + "\n");
217 
218       result.append("sources configured in:        subject.properties\n");
219       File sourcesXmlFile = SubjectUtils.fileFromResourceName("sources.xml");
220       if (sourcesXmlFile != null && sourcesXmlFile.exists() && sourcesXmlFile.isFile()) {
221         String sourcesError = "NON-FATAL ERROR:              subject sources are read from subject.properties but you "
222             + "still have a sources.xml on the classpath which is confusing, please backup and remove this file: " + sourcesXmlFile.getAbsolutePath();
223         result.append(sourcesError + "\n");
224         log.error(sourcesError);
225       }
226       
227       //at this point, we have a subject.properties...  now check it out
228       Collection<Source> sources = SourceManager.getInstance().getSources();
229       for (Source source : sources) {
230         result.append(source.printConfig()).append("\n");
231       }
232       //dont end in newline
233       if (result.toString().endsWith("\n")) {
234         result.deleteCharAt(result.length() - 1);
235       }
236       return result.toString();
237     } catch (Exception e) {
238       log.error("Cant print subject API configs", e);
239     }
240     return "Cant print subject API configs";
241 
242   }
243 
244   /** */
245   private static Log log = edu.internet2.middleware.grouper.util.GrouperUtil.getLog(SourceManager.class);
246 
247   /** */
248   private static SourceManager manager;
249 
250   /** */
251   private Map<SubjectType, Set<Source>> source2TypeMap = new HashMap<SubjectType, Set<Source>>();
252 
253   /** */
254   Map<String, Source> sourceMap = new HashMap<String, Source>();
255   
256   private static Set<String> registeredDatabaseCacheNames = Collections.synchronizedSet(new HashSet<String>());
257 
258   private static boolean grouperCacheClearDatabaseInitted;
259   /**
260    * Default constructor.
261    */
262   private SourceManager() {
263     init();
264     
265     if (!grouperCacheClearDatabaseInitted) {
266       String cacheName = "edu.internet2.middleware.subject.provider.SourceManager.reloadSource";
267       GrouperCacheDatabase.customRegisterDatabaseClearable(cacheName, new GrouperCacheDatabaseClear() {         
268               
269         @Override
270         public void clear(GrouperCacheDatabaseClearInput grouperCacheDatabaseClearInput) {
271           String cacheName = grouperCacheDatabaseClearInput.getCacheName();
272           String sourceId = StringUtils.substringAfterLast(cacheName, "____");
273           GrouperConfigHibernate.clearConfigsInMemory();
274           SourceManager.getInstance().reloadSource(sourceId);
275         }
276       });
277       grouperCacheClearDatabaseInitted = true;
278     }
279     
280     
281   }
282 
283   /**
284    * Returns the singleton instance of SourceManager.
285    * @return source manager
286    *
287    */
288   public static SourceManager getInstance() {
289     if (manager == null) {
290       synchronized(SourceManager.class) {
291         if (manager == null) {
292           manager = new SourceManager();
293         }
294       }
295     }
296     return manager;
297   }
298 
299   /**
300    * Gets Source for the argument source ID.
301    * @param sourceId
302    * @return Source
303    * @throws SourceUnavailableException
304    */
305   public Source getSource(String sourceId) throws SourceUnavailableException {
306     Source source = this.sourceMap.get(sourceId);
307     if (source == null) {
308       
309       StringBuilder allSources = new StringBuilder();
310       int i=0;
311       for (String theSourceId : this.sourceMap.keySet()) {
312         allSources.append(theSourceId);
313         if (i != this.sourceMap.size() - 1) {
314           allSources.append(", ");
315         }
316         i++;
317       }
318       
319       throw new SourceUnavailableException("Source not found: '" + sourceId + "', available sources are: " + allSources);
320     }
321     return source;
322   }
323 
324   /**
325    * Returns a Collection of Sources.
326    * @return Collection
327    */
328   public Collection<Source> getSources() {
329     return new LinkedHashSet<Source>(this.sourceMap.values());
330   }
331 
332   /**
333    * Returns a Collection of Sources that
334    * supports the argument SubjectType.
335    * @param type 
336    * @return Collection
337    */
338   public Collection<Source> getSources(SubjectType type) {
339     if (this.source2TypeMap.containsKey(type)) {
340       return this.source2TypeMap.get(type);
341     }
342     return new HashSet<Source>();
343   }
344 
345   /**
346    * Initialize this SourceManager.
347    * @throws RuntimeException
348    */
349   private void init() throws RuntimeException {
350     try {
351       parseConfig();
352       
353       if (GrouperConfig.retrieveConfig().propertyValueBoolean("externalSubjects.autoCreateSource", true)) {
354         
355         this.loadSource(ExternalSubjectAutoSourceAdapter.instance());
356         
357       }
358       
359 
360     } catch (Exception ex) {
361       log.error("Error initializing SourceManager: " + ex.getMessage(), ex);
362       throw new RuntimeException("Error initializing SourceManager", ex);
363     }
364   }
365   
366   public synchronized void reloadSource(String sourceId) {
367     Source source = SubjectConfig.retrieveConfig().reloadSourceConfigs(sourceId);
368     if (source != null) {
369       loadSource(source);
370     } else {
371       sourceMap.remove(sourceId);
372     }
373   }
374 
375   /**
376    * (non-javadoc)
377    * @param source
378    */
379   public void loadSource(Source source) {
380     log.debug("Loading source: " + source.getId());
381     
382     //put in map before initting
383     this.sourceMap.put(source.getId(), source);
384     
385     String cacheName = "edu.internet2.middleware.subject.provider.SourceManager.reloadSource____" + source.getId();
386     if (!registeredDatabaseCacheNames.contains(cacheName)) {
387       registeredDatabaseCacheNames.add(cacheName);
388       GrouperCacheDatabase.customRegisterDatabaseClearable(cacheName, new GrouperCacheDatabaseClear() {         
389              
390              private String sourceId = source.getId();
391              
392              @Override
393              public void clear(GrouperCacheDatabaseClearInput grouperCacheDatabaseClearInput) {
394                GrouperConfigHibernate.clearConfigsInMemory();
395                SourceManager.getInstance().reloadSource(sourceId);
396              }
397           });
398     }
399 
400     for (Iterator it = source.getSubjectTypes().iterator(); it.hasNext();) {
401       SubjectType../../../../edu/internet2/middleware/subject/SubjectType.html#SubjectType">SubjectType type = (SubjectType) it.next();
402       Set<Source> sources = this.source2TypeMap.get(type);
403       if (sources == null) {
404         sources = new HashSet<Source>();
405         this.source2TypeMap.put(type, sources);
406       }
407       sources.add(source);
408     }
409 
410     //do this last in case it throws exceptions...
411     source.init();
412       
413     source.getSearchAttributes();
414     source.getSortAttributes();
415     source.getSubjectIdentifierAttributes();
416     source.getSubjectIdentifierAttributesAll();
417   }
418 
419   /**
420    * Parses subject.properties config file using org.apache.commons.digester.Digester.
421    * @throws IOException 
422    * @throws SAXException 
423    */
424   private void parseConfig() throws IOException, SAXException {
425     for (Source source : SubjectConfig.retrieveConfig().retrieveSourceConfigs().values()) {
426       loadSource(source);
427     }
428   }
429 
430   /**
431    * 
432    * @return true if using subject.properties, false if subject.properties
433    */
434   public static boolean usingSubjectProperties() {
435     return true;
436   }
437   
438   
439   /**
440    * Validates subject.properties config file.
441    * @param args 
442    */
443   public static void main(String[] args) {
444     
445     try {
446       SourceManager mgr = SourceManager.getInstance();
447       for (Iterator iter = mgr.getSources().iterator(); iter.hasNext();) {
448         BaseSourceAdapter/../edu/internet2/middleware/subject/provider/BaseSourceAdapter.html#BaseSourceAdapter">BaseSourceAdapter source = (BaseSourceAdapter) iter.next();
449         log.debug("Source init params: " + "id = " + source.getId() + ", params = "
450             + source.initParams());
451         source.init();
452         if (source.getId().equals("example")) {
453 
454           Subject subject = source.getSubject("70061854", true);
455           //Subject subject = source.getSubject("xxxxx");
456           log.debug("getSubject id: " + subject.getId() + " name: " + subject.getName()
457               + " description:" + subject.getDescription());
458           subject = source.getSubjectByIdentifier("esluss", true);
459           log.debug("getSubjectByIdentifier id: " + subject.getId() + " name: "
460               + subject.getName() + " description:" + subject.getDescription());
461           log.debug("Starting barton search");
462           Set subjectSet = source.search("barton");
463           log.debug("num elements found: " + subjectSet.size());
464           for (Iterator it = subjectSet.iterator(); it.hasNext();) {
465             subject = (Subject) it.next();
466             log.debug("id: " + subject.getId() + " name: " + subject.getName()
467                 + " description:" + subject.getDescription());
468             Map attrs = subject.getAttributes();
469             for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); log
470                 .debug(it2.next()))
471               ;
472           }
473 
474         } else if (source.getId().equals("jdbc")) {
475           Subject subject = source.getSubject("37413", true);
476 
477           //Subject subject = source.getSubject("xxxxx");
478           log.debug("getSubject id: " + subject.getId() + " name: " + subject.getName()
479               + " description:" + subject.getDescription());
480           subject = source.getSubjectByIdentifier("abean", true);
481           log.debug("getSubjectByIdentifier id: " + subject.getId() + " name: "
482               + subject.getName() + " description:" + subject.getDescription());
483           log.debug("Starting barton search");
484           Set subjectSet = source.search("smith");
485           log.debug("num elements found: " + subjectSet.size());
486           for (Iterator it = subjectSet.iterator(); it.hasNext();) {
487             subject = (Subject) it.next();
488             log.debug("id: " + subject.getId() + " name: " + subject.getName()
489                 + " description:" + subject.getDescription());
490             Map attrs = subject.getAttributes();
491             for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); log
492                 .debug(it2.next()))
493               ;
494           }
495           subjectSet = source.search("bean");
496           log.debug("num elements found: " + subjectSet.size());
497           for (Iterator it = subjectSet.iterator(); it.hasNext();) {
498             subject = (Subject) it.next();
499             log.debug("id: " + subject.getId() + " name: " + subject.getName()
500                 + " description:" + subject.getDescription());
501             Map attrs = subject.getAttributes();
502             for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); log
503                 .debug(it2.next()))
504               ;
505           }
506           subjectSet = source.search("smith");
507           log.debug("num elements found: " + subjectSet.size());
508           for (Iterator it = subjectSet.iterator(); it.hasNext();) {
509             subject = (Subject) it.next();
510             log.debug("id: " + subject.getId() + " name: " + subject.getName()
511                 + " description:" + subject.getDescription());
512             Map attrs = subject.getAttributes();
513             for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); log
514                 .debug(it2.next()))
515               ;
516           }
517           subjectSet = source.search("bean");
518           log.debug("num elements found: " + subjectSet.size());
519           for (Iterator it = subjectSet.iterator(); it.hasNext();) {
520             subject = (Subject) it.next();
521             log.debug("id: " + subject.getId() + " name: " + subject.getName()
522                 + " description:" + subject.getDescription());
523             Map attrs = subject.getAttributes();
524             for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); log
525                 .debug(it2.next()))
526               ;
527           }
528           subjectSet = source.search("smith");
529           log.debug("num elements found: " + subjectSet.size());
530           for (Iterator it = subjectSet.iterator(); it.hasNext();) {
531             subject = (Subject) it.next();
532             log.debug("id: " + subject.getId() + " name: " + subject.getName()
533                 + " description:" + subject.getDescription());
534             Map attrs = subject.getAttributes();
535             for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); log
536                 .debug(it2.next()))
537               ;
538           }
539 
540         }
541       }
542     } catch (Exception ex) {
543       log.error("Exception occurred: " + ex.getMessage(), ex);
544     }
545   }
546   
547   /**
548    * remove source for testing
549    * @param sourceId
550    */
551   public void internal_removeSource(String sourceId) {
552     sourceMap.remove(sourceId);
553   }
554 }