001: /* Copyright 2003 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.groups.filesystem;
007:
008: import java.io.BufferedReader;
009: import java.io.File;
010: import java.io.FileNotFoundException;
011: import java.io.FileReader;
012: import java.io.FilenameFilter;
013: import java.io.IOException;
014: import java.util.ArrayList;
015: import java.util.Collection;
016: import java.util.Collections;
017: import java.util.HashMap;
018: import java.util.HashSet;
019: import java.util.Iterator;
020: import java.util.List;
021: import java.util.Map;
022: import java.util.Set;
023: import java.util.StringTokenizer;
024:
025: import org.apache.oro.io.GlobFilenameFilter;
026: import org.jasig.portal.EntityIdentifier;
027: import org.jasig.portal.EntityTypes;
028: import org.jasig.portal.groups.EntityGroupImpl;
029: import org.jasig.portal.groups.EntityImpl;
030: import org.jasig.portal.groups.GroupServiceConfiguration;
031: import org.jasig.portal.groups.GroupsException;
032: import org.jasig.portal.groups.IEntity;
033: import org.jasig.portal.groups.IEntityGroup;
034: import org.jasig.portal.groups.IEntityGroupStore;
035: import org.jasig.portal.groups.IEntitySearcher;
036: import org.jasig.portal.groups.IEntityStore;
037: import org.jasig.portal.groups.IGroupMember;
038: import org.jasig.portal.services.GroupService;
039: import org.apache.commons.logging.Log;
040: import org.apache.commons.logging.LogFactory;
041:
042: /**
043: * This class is an <code>IEntityGroupStore</code> that uses the native file
044: * system for its back end. It also implements <code>IEntityStore</code> and
045: * a no-op <code>IEntitySearcher</code>. You can substitute a functional entity
046: * searcher by adding it to the group service element for this component in the
047: * configuration document, <code>compositeGroupServices.xml</code>.
048: * <p>
049: * A groups file system looks like this:
050: * <p><code>
051: * <hr width="100%">
052: * -- groups root<br>
053: * <blockquote> -- org.jasig.portal.ChannelDefinition<br>
054: * <blockquote> -- channel definition file<br>
055: * -- channel definition file<br>
056: * ...<br>
057: * </blockquote>
058: * -- org.jasig.portal.security.IPerson<br>
059: * <blockquote> -- person directory<br>
060: * <blockquote> -- person file <br>
061: * -- person file <br>
062: * ...<br>
063: * </blockquote>
064: * -- person directory <br>
065: * </blockquote>
066: * etc.<br>
067: * </blockquote>
068: * <hr width="100%">
069: * </code><p>
070: * The groups root is a file system directory declared in the group service
071: * configuration document, where it is an attribute of the filesystem group
072: * service element. This directory has sub-directories, each named for the
073: * underlying entity type that groups in that sub-directory contain. If a
074: * service only contains groups of IPersons, the groups root would have 1
075: * sub-directory named org.jasig.portal.security.IPerson.
076: * <p>
077: * A directory named for a type may contain both sub-directories and files.
078: * The sub-directories represent groups that can contain other groups. The
079: * files represent groups that can contain entity as well as group members.
080: * The files contain keys, one to a line, and look like this:
081: * <p><code>
082: * <hr width="100%">
083: * # this is a comment<br>
084: * # another comment<br>
085: * <br>
086: * key1 Key One<br>
087: * key2<br>
088: * group:org$jasig$portal$security$IPerson/someDirectory/someFile<br>
089: * key3<br>
090: * # comment <br>
091: * <hr width="100%">
092: *</code><p>
093: * Blank lines and lines that start with the <code>COMMENT</code> String (here
094: * <code>#</code>) are ignored. The first token on a non-ignored line is
095: * assumed to be a group member key. If the key starts with the
096: * <code>GROUP_PREFIX</code> (here <code>:group</code>), it is treated as a
097: * local group key. Otherwise, it is assumed to be an entity key. The rest of
098: * the tokens on the line are ignored.
099: * <p>
100: * The file above contains 3 entity keys, <code>key1</code>, <code>key2</code>,
101: * and <code>key3</code>, and 1 group key,
102: * <code>org$jasig$portal$security$IPerson/someDirectory/someFile</code>. It
103: * represents a group with 3 entity members and 1 group member. The local key
104: * of a group is its file path starting at the type name, with the
105: * <code>FileSystemGroupStore.SUBSTITUTE_PERIOD</code> character substituted
106: * for the real period character.
107: * <p>
108: * The store is not implemented as a singleton, so you can have multiple
109: * concurrent instances pointing to different groups root directories.
110: * <p>
111: *
112: * @author Dan Ellentuck
113: * @version $Revision: 36529 $
114: */
115: public class FileSystemGroupStore implements IEntityGroupStore,
116: IEntityStore, IEntitySearcher {
117: private static final Log log = LogFactory
118: .getLog(FileSystemGroupStore.class);
119: // File system constants for unix/windows compatibility:
120: protected static char FORWARD_SLASH = '/';
121: protected static char BACK_SLASH = '\\';
122:
123: // Group file constants:
124: protected static String COMMENT = "#";
125: protected static String GROUP_PREFIX = "group:";
126:
127: // The period is legal in filesystem names but could conflict with
128: // the node separator in the group key.
129: protected static char PERIOD = '.';
130: protected static char SUBSTITUTE_PERIOD = '$';
131: protected boolean useSubstitutePeriod = false;
132:
133: private static String DEBUG_CLASS_NAME = "FileSystemGroupStore";
134:
135: // Path to groups root directory.
136: private String groupsRootPath;
137:
138: // Either back slash or forward slash.
139: protected char goodSeparator;
140: protected char badSeparator;
141:
142: // Cache of retrieved groups.
143: private Map cache;
144:
145: private FilenameFilter fileFilter = new FileFilter();
146:
147: private Class defaultEntityType;
148:
149: // Value holder adds last modified timestamp.
150: private class GroupHolder {
151: private long lastModified = 0;
152: private IEntityGroup group;
153:
154: protected GroupHolder(IEntityGroup g, long lm) {
155: this .group = g;
156: this .lastModified = lm;
157: }
158:
159: protected IEntityGroup getGroup() {
160: return group;
161: }
162:
163: protected long getLastModified() {
164: return lastModified;
165: }
166: }
167:
168: private class FileFilter implements FilenameFilter {
169: /**
170: * Tests if a specified file should be included in a file list.
171: *
172: * @param dir the directory in which the file was found.
173: * @param name the name of the file.
174: * @return <code>true</code> if and only if the name should be
175: * included in the file list; <code>false</code> otherwise.
176: */
177: public boolean accept(File dir, String name) {
178: return (!name.startsWith("#")) && (!name.startsWith("%"))
179: && (!name.startsWith(".")) && (!name.endsWith("~"))
180: && (!name.endsWith(".tmp"))
181: && (!name.endsWith(".temp"))
182: && (!name.endsWith(".txt"));
183: }
184: }
185:
186: /**
187: * FileSystemGroupStore constructor.
188: */
189: public FileSystemGroupStore() {
190: this (null);
191: }
192:
193: /**
194: * FileSystemGroupStore constructor.
195: */
196: public FileSystemGroupStore(GroupServiceConfiguration cfg) {
197: super ();
198: initialize(cfg);
199: }
200:
201: /**
202: * @return GroupHolder
203: */
204: protected GroupHolder cacheGet(String key) {
205: return (GroupHolder) getCache().get(key);
206: }
207:
208: /**
209: *
210: */
211: protected void cachePut(String key, Object val) {
212: getCache().put(key, val);
213: }
214:
215: /**
216: *
217: */
218: protected String conformSeparatorChars(String s) {
219: return s.replace(getBadSeparator(), getGoodSeparator());
220: }
221:
222: /**
223: * Delete this <code>IEntityGroup</code> from the data store. We assume that
224: * groups will be deleted via the file system, not the group service.
225: * @param group org.jasig.portal.groups.IEntityGroup
226: */
227: public void delete(org.jasig.portal.groups.IEntityGroup group)
228: throws GroupsException {
229: throw new UnsupportedOperationException(
230: "FileSystemGroupStore.delete() not supported");
231: }
232:
233: /**
234: * Returns an instance of the <code>IEntityGroup</code> from the data store.
235: * @return org.jasig.portal.groups.IEntityGroup
236: * @param file java.io.File
237: */
238: private IEntityGroup find(File file) throws GroupsException {
239: return find(getKeyFromFile(file));
240: }
241:
242: /**
243: * Returns an instance of the <code>IEntityGroup</code> from the data store.
244: * @return org.jasig.portal.groups.IEntityGroup
245: * @param key java.lang.String
246: */
247: public IEntityGroup find(String key) throws GroupsException {
248: if (log.isDebugEnabled()) {
249: log.debug(DEBUG_CLASS_NAME + ".find(): group key: " + key);
250: }
251:
252: String path = getFilePathFromKey(key);
253: File f = new File(path);
254:
255: GroupHolder groupHolder = cacheGet(key);
256:
257: if (groupHolder == null
258: || (groupHolder.getLastModified() != f.lastModified())) {
259: if (log.isDebugEnabled()) {
260: log
261: .debug(DEBUG_CLASS_NAME
262: + ".find(): retrieving group from file system for "
263: + path);
264: }
265:
266: if (!f.exists()) {
267: if (log.isDebugEnabled()) {
268: log.debug(DEBUG_CLASS_NAME
269: + ".find(): file does not exist: " + path);
270: }
271: return null;
272: }
273:
274: IEntityGroup group = newInstance(f);
275: groupHolder = new GroupHolder(group, f.lastModified());
276: cachePut(key, groupHolder);
277: }
278: return groupHolder.getGroup();
279: }
280:
281: /**
282: * Returns an <code>Iterator</code> over the <code>Collection</code> of
283: * <code>IEntityGroups</code> that the <code>IEntity</code> belongs to.
284: * @return java.util.Iterator
285: * @param ent org.jasig.portal.groups.IEntityGroup
286: */
287: protected Iterator findContainingGroups(IEntity ent)
288: throws GroupsException {
289: if (log.isDebugEnabled())
290: log.debug(DEBUG_CLASS_NAME
291: + ".findContainingGroups(): for " + ent);
292:
293: List groups = new ArrayList();
294: File root = getFileRoot(ent.getType());
295: if (root != null) {
296: File[] files = getAllFilesBelow(root);
297:
298: try {
299: for (int i = 0; i < files.length; i++) {
300: Collection ids = getEntityIdsFromFile(files[i]);
301: if (ids.contains(ent.getKey())) {
302: groups.add(find(files[i]));
303: }
304: }
305: } catch (IOException ex) {
306: throw new GroupsException(
307: "Problem reading group files", ex);
308: }
309: }
310:
311: return groups.iterator();
312: }
313:
314: /**
315: * Returns an <code>Iterator</code> over the <code>Collection</code> of
316: * <code>IEntityGroups</code> that the <code>IGroupMember</code> belongs to.
317: * @return java.util.Iterator
318: * @param group org.jasig.portal.groups.IEntityGroup
319: */
320: protected Iterator findContainingGroups(IEntityGroup group)
321: throws GroupsException {
322: if (log.isDebugEnabled())
323: log.debug(DEBUG_CLASS_NAME
324: + ".findContainingGroups(): for " + group);
325:
326: List groups = new ArrayList();
327: {
328: String typeName = group.getLeafType().getName();
329: File parent = getFile(group).getParentFile();
330: if (!parent.getName().equals(typeName)) {
331: groups.add(find(parent));
332: }
333:
334: File root = getFileRoot(group.getLeafType());
335: File[] files = getAllFilesBelow(root);
336: try {
337: for (int i = 0; i < files.length; i++) {
338: Collection ids = getGroupIdsFromFile(files[i]);
339: if (ids.contains(group.getLocalKey())) {
340: groups.add(find(files[i]));
341: }
342: }
343: } catch (IOException ex) {
344: throw new GroupsException(
345: "Problem reading group files", ex);
346: }
347: }
348: return groups.iterator();
349: }
350:
351: /**
352: * Returns an <code>Iterator</code> over the <code>Collection</code> of
353: * <code>IEntityGroups</code> that the <code>IGroupMember</code> belongs to.
354: * @return java.util.Iterator
355: * @param gm org.jasig.portal.groups.IEntityGroup
356: */
357: public Iterator findContainingGroups(IGroupMember gm)
358: throws GroupsException {
359: if (gm.isGroup()) {
360: IEntityGroup group = (IEntityGroup) gm;
361: return findContainingGroups(group);
362: } else {
363: IEntity ent = (IEntity) gm;
364: return findContainingGroups(ent);
365: }
366: }
367:
368: /**
369: * Returns an <code>Iterator</code> over the <code>Collection</code> of
370: * <code>IEntities</code> that are members of this <code>IEntityGroup</code>.
371: * @return java.util.Iterator
372: * @param group org.jasig.portal.groups.IEntityGroup
373: */
374: public java.util.Iterator findEntitiesForGroup(IEntityGroup group)
375: throws GroupsException {
376: if (log.isDebugEnabled())
377: log
378: .debug(DEBUG_CLASS_NAME
379: + ".findEntitiesForGroup(): retrieving entities for group "
380: + group);
381:
382: Collection entities = null;
383: File f = getFile(group);
384: if (f.isDirectory()) {
385: entities = Collections.EMPTY_LIST;
386: } else {
387: entities = getEntitiesFromFile(f);
388: }
389:
390: return entities.iterator();
391: }
392:
393: /**
394: * Returns an instance of the <code>ILockableEntityGroup</code> from the data store.
395: * @return org.jasig.portal.groups.IEntityGroup
396: * @param key java.lang.String
397: */
398: public org.jasig.portal.groups.ILockableEntityGroup findLockable(
399: String key) throws GroupsException {
400: throw new UnsupportedOperationException(DEBUG_CLASS_NAME
401: + ".findLockable() not supported");
402: }
403:
404: /**
405: * Returns a <code>String[]</code> containing the keys of <code>IEntityGroups</code>
406: * that are members of this <code>IEntityGroup</code>. In a composite group
407: * system, a group may contain a member group from a different service. This is
408: * called a foreign membership, and is only possible in an internally-managed
409: * service. A group store in such a service can return the key of a foreign member
410: * group, but not the group itself, which can only be returned by its local store.
411: *
412: * @return String[]
413: * @param group org.jasig.portal.groups.IEntityGroup
414: */
415: public java.lang.String[] findMemberGroupKeys(IEntityGroup group)
416: throws GroupsException {
417: String[] keys;
418: File f = getFile(group);
419: if (f.isDirectory()) {
420: File[] files = f.listFiles();
421: keys = new String[files.length];
422: for (int i = 0; i < files.length; i++) {
423: keys[i] = getKeyFromFile(files[i]);
424: }
425: } else {
426: try {
427: Collection groupKeys = getGroupIdsFromFile(f);
428: keys = (String[]) groupKeys
429: .toArray(new String[groupKeys.size()]);
430: } catch (IOException ex) {
431: throw new GroupsException(DEBUG_CLASS_NAME
432: + ".findMemberGroupKeys(): "
433: + "problem finding group members", ex);
434: }
435: }
436: return keys;
437: }
438:
439: /**
440: * Returns an <code>Iterator</code> over the <code>Collection</code> of
441: * <code>IEntityGroups</code> that are members of this <code>IEntityGroup</code>.
442: * @return java.util.Iterator
443: * @param group org.jasig.portal.groups.IEntityGroup
444: */
445: public java.util.Iterator findMemberGroups(IEntityGroup group)
446: throws GroupsException {
447: String[] keys = findMemberGroupKeys(group); // No foreign groups here.
448: List groups = new ArrayList(keys.length);
449: for (int i = 0; i < keys.length; i++) {
450: groups.add(find(keys[i]));
451: }
452: return groups.iterator();
453: }
454:
455: /**
456: * Recursive search of directories underneath dir for files that match filter.
457: * @return java.util.Set
458: */
459: public Set getAllDirectoriesBelow(File dir) {
460: Set allDirectories = new HashSet();
461: if (dir.isDirectory()) {
462: primGetAllDirectoriesBelow(dir, allDirectories);
463: }
464: return allDirectories;
465: }
466:
467: /**
468: * Recursive search of directories underneath dir for files that match filter.
469: */
470: public File[] getAllFilesBelow(File dir) {
471: Set allFiles = new HashSet();
472: if (dir.isDirectory()) {
473: primGetAllFilesBelow(dir, allFiles);
474: }
475: return (File[]) allFiles.toArray(new File[allFiles.size()]);
476: }
477:
478: /**
479: * Returns the filesystem separator character NOT in use.
480: * @return char
481: */
482: protected char getBadSeparator() {
483: return badSeparator;
484: }
485:
486: /**
487: * @return java.util.Map
488: */
489: protected java.util.Map getCache() {
490: return cache;
491: }
492:
493: /**
494: * Returns a Class representing the default entity type.
495: * @return Class
496: */
497: protected Class getDefaultEntityType() {
498: return defaultEntityType;
499: }
500:
501: /**
502: * @param idFile java.io.File - a file of ids.
503: * @return entities Collection.
504: */
505: protected Collection getEntitiesFromFile(File idFile)
506: throws GroupsException {
507: if (log.isDebugEnabled())
508: log.debug(DEBUG_CLASS_NAME + "getEntitiesFromFile(): for "
509: + idFile.getPath());
510:
511: Collection ids = null;
512: Class type = getEntityType(idFile);
513: if (EntityTypes.getEntityTypeID(type) == null) {
514: throw new GroupsException("Invalid entity type: " + type);
515: }
516: try {
517: ids = getEntityIdsFromFile(idFile);
518: } catch (Exception ex) {
519: throw new GroupsException(
520: "Problem retrieving keys from file", ex);
521: }
522:
523: Collection entities = new ArrayList(ids.size());
524:
525: for (Iterator itr = ids.iterator(); itr.hasNext();) {
526: String key = (String) itr.next();
527: entities.add(GroupService.getEntity(key, type));
528: }
529:
530: if (log.isDebugEnabled())
531: log.debug(DEBUG_CLASS_NAME
532: + "getEntitiesFromFile(): Retrieved "
533: + entities.size() + " entities");
534:
535: return entities;
536: }
537:
538: /**
539: * @param idFile java.io.File - a file of ids.
540: * @return String[] ids.
541: */
542: protected Collection getEntityIdsFromFile(File idFile)
543: throws IOException, FileNotFoundException {
544: if (log.isDebugEnabled())
545: log.debug(DEBUG_CLASS_NAME
546: + "getEntityIdsFromFile(): Reading "
547: + idFile.getPath());
548:
549: Collection ids = getIdsFromFile(idFile, false);
550:
551: if (log.isDebugEnabled())
552: log.debug(DEBUG_CLASS_NAME
553: + "getEntityIdsFromFile(): Retrieved " + ids.size()
554: + " IDs");
555:
556: return ids;
557: }
558:
559: /**
560: * @param f File
561: * @return java.lang.Class
562: * The Class is the first node of the full path name.
563: */
564: protected Class getEntityType(File f) {
565: String path = f.getPath();
566: String afterRootPath = null;
567: Class type = null;
568: if (path.startsWith(getGroupsRootPath())) {
569: afterRootPath = path
570: .substring(getGroupsRootPath().length());
571: int end = afterRootPath.indexOf(File.separatorChar);
572: String typeName = afterRootPath.substring(0, end);
573:
574: try {
575: type = Class.forName(typeName);
576: } catch (ClassNotFoundException cnfe) {
577: }
578: }
579: return type;
580: }
581:
582: /**
583: * @param group IEntityGroup.
584: * @return File
585: */
586: protected File getFile(IEntityGroup group) {
587: String key = getFilePathFromKey(group.getLocalKey());
588: return new File(key);
589: }
590:
591: /**
592: *
593: */
594: protected String getFilePathFromKey(String key) {
595: if (log.isDebugEnabled())
596: log.debug(DEBUG_CLASS_NAME
597: + ".getFilePathFromKey(): for key: " + key);
598:
599: String groupKey = useSubstitutePeriod ? key.replace(
600: SUBSTITUTE_PERIOD, PERIOD) : key;
601:
602: String fullKey = getGroupsRootPath() + groupKey;
603:
604: if (log.isDebugEnabled())
605: log.debug(DEBUG_CLASS_NAME
606: + ".getFilePathFromKey(): full key: " + fullKey);
607:
608: return conformSeparatorChars(fullKey);
609: }
610:
611: /**
612: * Returns a File that is the root for groups of the given type.
613: */
614: protected File getFileRoot(Class type) {
615: String path = getGroupsRootPath() + type.getName();
616: File f = new File(path);
617: return (f.exists()) ? f : null;
618: }
619:
620: /**
621: * Returns the filesystem separator character in use.
622: * @return char
623: */
624: protected char getGoodSeparator() {
625: return goodSeparator;
626: }
627:
628: /**
629: * @param idFile java.io.File - a file of ids.
630: * @return String[] ids.
631: */
632: protected Collection getGroupIdsFromFile(File idFile)
633: throws IOException, FileNotFoundException {
634: if (log.isDebugEnabled())
635: log.debug(DEBUG_CLASS_NAME
636: + "getGroupIdsFromFile(): Reading "
637: + idFile.getPath());
638:
639: Collection ids = getIdsFromFile(idFile, true);
640:
641: if (log.isDebugEnabled())
642: log.debug(DEBUG_CLASS_NAME
643: + "getGroupIdsFromFile(): Retrieved " + ids.size()
644: + " IDs");
645:
646: return ids;
647: }
648:
649: /**
650: * @return java.lang.String
651: */
652: public java.lang.String getGroupsRootPath() {
653: return groupsRootPath;
654: }
655:
656: /**
657: * @param idFile java.io.File - a file of ids.
658: * @return String[] ids.
659: */
660: protected Collection getIdsFromFile(File idFile, boolean groupIds)
661: throws IOException, FileNotFoundException {
662: Collection ids = new HashSet();
663: BufferedReader br = new BufferedReader(new FileReader(idFile));
664: String line, tok;
665:
666: line = br.readLine();
667: while (line != null) {
668: line = line.trim();
669: if (!line.startsWith(COMMENT) && (line.length() > 0)) {
670: StringTokenizer st = new StringTokenizer(line);
671: tok = st.nextToken();
672: if (tok != null) {
673: if (tok.startsWith(GROUP_PREFIX)) {
674: if (groupIds) {
675: ids.add(tok
676: .substring(GROUP_PREFIX.length()));
677: }
678: } else {
679: if (!groupIds) {
680: ids.add(tok);
681: }
682: }
683: }
684: }
685: line = br.readLine();
686: }
687: br.close();
688:
689: return ids;
690: }
691:
692: /**
693: *
694: */
695: protected String getKeyFromFile(File f) {
696: String key = null;
697: if (f.getPath().startsWith(getGroupsRootPath())) {
698: key = f.getPath().substring(getGroupsRootPath().length());
699:
700: if (useSubstitutePeriod) {
701: key = key.replace(PERIOD, SUBSTITUTE_PERIOD);
702: }
703: }
704: return key;
705: }
706:
707: /**
708: *
709: */
710: protected void initialize(GroupServiceConfiguration cfg) {
711: cache = Collections.synchronizedMap(new HashMap());
712:
713: goodSeparator = File.separatorChar;
714: badSeparator = (goodSeparator == FORWARD_SLASH) ? BACK_SLASH
715: : FORWARD_SLASH;
716:
717: defaultEntityType = org.jasig.portal.security.IPerson.class;
718: GroupServiceConfiguration config = cfg;
719: if (config == null) {
720: try {
721: config = GroupServiceConfiguration.getConfiguration();
722: } catch (Exception ex) {
723: throw new RuntimeException(ex);
724: }
725: }
726:
727: String sep = config.getNodeSeparator();
728: if (sep != null) {
729: String period = String.valueOf(PERIOD);
730: useSubstitutePeriod = sep.equals(period);
731: }
732: }
733:
734: /**
735: * @return org.jasig.portal.groups.IEntityGroup
736: */
737: private IEntityGroup newInstance(File f) throws GroupsException {
738: String key = getKeyFromFile(f);
739: String name = f.getName();
740: Class cl = getEntityType(f);
741: return newInstance(key, cl, name);
742: }
743:
744: /**
745: * @return org.jasig.portal.groups.IEntityGroup
746: * We assume that new groups will be created updated via the file system,
747: * not the group service.
748: */
749: public IEntityGroup newInstance(Class entityType)
750: throws GroupsException {
751: throw new UnsupportedOperationException(DEBUG_CLASS_NAME
752: + ".newInstance(Class cl) not supported");
753: }
754:
755: public IEntity newInstance(String key) throws GroupsException {
756: return newInstance(key, getDefaultEntityType());
757: }
758:
759: public IEntity newInstance(String key, Class type)
760: throws GroupsException {
761: if (org.jasig.portal.EntityTypes.getEntityTypeID(type) == null) {
762: throw new GroupsException("Invalid group type: " + type);
763: }
764: return new EntityImpl(key, type);
765: }
766:
767: /**
768: * @return org.jasig.portal.groups.IEntityGroup
769: */
770: private IEntityGroup newInstance(String newKey, Class newType,
771: String newName) throws GroupsException {
772: EntityGroupImpl egi = new EntityGroupImpl(newKey, newType);
773: egi.primSetName(newName);
774: return egi;
775: }
776:
777: /**
778: * Returns all directories under dir.
779: */
780: private void primGetAllDirectoriesBelow(File dir, Set allDirectories) {
781: File[] files = dir.listFiles(fileFilter);
782: for (int i = 0; i < files.length; i++) {
783: if (files[i].isDirectory()) {
784: primGetAllDirectoriesBelow(files[i], allDirectories);
785: allDirectories.add(files[i]);
786: }
787: }
788: }
789:
790: /**
791: * Returns all files (not directories) underneath dir.
792: */
793: private void primGetAllFilesBelow(File dir, Set allFiles) {
794: File[] files = dir.listFiles(fileFilter);
795: for (int i = 0; i < files.length; i++) {
796: if (files[i].isDirectory()) {
797: primGetAllFilesBelow(files[i], allFiles);
798: } else {
799: allFiles.add(files[i]);
800: }
801: }
802: }
803:
804: /**
805: * Find EntityIdentifiers for entities whose name matches the query string
806: * according to the specified method and is of the specified type
807: */
808: public EntityIdentifier[] searchForEntities(String query,
809: int method, Class type) throws GroupsException {
810: return new EntityIdentifier[0];
811: }
812:
813: /**
814: * Returns an EntityIdentifier[] of groups of the given leaf type whose names
815: * match the query string according to the search method.
816: *
817: * @param query String the string used to match group names.
818: * @param searchMethod see org.jasig.portal.groups.IGroupConstants.
819: * @param leafType the leaf type of the groups we are searching for.
820: * @return EntityIdentifier[]
821: */
822: public EntityIdentifier[] searchForGroups(String query,
823: int searchMethod, Class leafType) throws GroupsException {
824: List ids = new ArrayList();
825: File baseDir = getFileRoot(leafType);
826:
827: if (log.isDebugEnabled())
828: log
829: .debug(DEBUG_CLASS_NAME + "searchForGroups(): "
830: + query + " method: " + searchMethod
831: + " type: " + leafType);
832:
833: if (baseDir != null) {
834: String nameFilter = null;
835:
836: switch (searchMethod) {
837: case IS:
838: nameFilter = query;
839: break;
840: case STARTS_WITH:
841: nameFilter = query + "*";
842: break;
843: case ENDS_WITH:
844: nameFilter = "*" + query;
845: break;
846: case CONTAINS:
847: nameFilter = "*" + query + "*";
848: break;
849: default:
850: throw new GroupsException(DEBUG_CLASS_NAME
851: + ".searchForGroups(): Unknown search method: "
852: + searchMethod);
853: }
854:
855: FilenameFilter filter = new GlobFilenameFilter(nameFilter);
856: Set allDirs = getAllDirectoriesBelow(baseDir);
857: allDirs.add(baseDir);
858:
859: for (Iterator itr = allDirs.iterator(); itr.hasNext();) {
860: File[] files = ((File) itr.next()).listFiles(filter);
861: for (int filesIdx = 0; filesIdx < files.length; filesIdx++) {
862: String key = getKeyFromFile(files[filesIdx]);
863: EntityIdentifier ei = new EntityIdentifier(key,
864: EntityTypes.GROUP_ENTITY_TYPE);
865: ids.add(ei);
866: }
867: }
868: }
869:
870: if (log.isDebugEnabled())
871: log.debug(DEBUG_CLASS_NAME + ".searchForGroups(): found "
872: + ids.size() + " files.");
873:
874: return (EntityIdentifier[]) ids
875: .toArray(new EntityIdentifier[ids.size()]);
876: }
877:
878: /**
879: * @param newCache java.util.Map
880: */
881: protected void setCache(java.util.Map newCache) {
882: cache = newCache;
883: }
884:
885: /**
886: * @param newGroupsRootPath java.lang.String
887: */
888: protected void setGroupsRootPath(java.lang.String newGroupsRootPath) {
889: groupsRootPath = conformSeparatorChars(newGroupsRootPath)
890: + getGoodSeparator();
891: }
892:
893: /**
894: * Adds or updates the <code>IEntityGroup</code> AND ITS MEMBERSHIPS to the
895: * data store, as appropriate. We assume that groups will be updated via the
896: * file system, not the group service.
897: * @param group org.jasig.portal.groups.IEntityGroup
898: */
899: public void update(org.jasig.portal.groups.IEntityGroup group)
900: throws GroupsException {
901: throw new UnsupportedOperationException(DEBUG_CLASS_NAME
902: + ".update() not supported");
903: }
904:
905: /**
906: * Commits the group memberships of the <code>IEntityGroup</code> to
907: * the data store. We assume that groups will be updated via the
908: * file system, not the group service.
909: * @param group org.jasig.portal.groups.IEntityGroup
910: */
911: public void updateMembers(org.jasig.portal.groups.IEntityGroup group)
912: throws GroupsException {
913: throw new UnsupportedOperationException(DEBUG_CLASS_NAME
914: + ".updateMembers() not supported");
915: }
916:
917: /**
918: * Answers if <code>group</code> contains <code>member</code>.
919: * @return boolean
920: * @param group org.jasig.portal.groups.IEntityGroup
921: * @param member org.jasig.portal.groups.IGroupMember
922: */
923: public boolean contains(IEntityGroup group, IGroupMember member)
924: throws GroupsException {
925: File f = getFile(group);
926: return (f.isDirectory()) ? directoryContains(f, member)
927: : fileContains(f, member);
928: }
929:
930: /**
931: * Answers if <code>file</code> contains <code>member</code>.
932: * @param file
933: * @param member
934: * @return boolean
935: */
936: private boolean fileContains(File file, IGroupMember member)
937: throws GroupsException {
938: Collection ids = null;
939: try {
940: ids = (member.isEntity()) ? getEntityIdsFromFile(file)
941: : getGroupIdsFromFile(file);
942: } catch (Exception ex) {
943: throw new GroupsException("Error retrieving ids from file",
944: ex);
945: }
946: return ids.contains(member.getKey());
947: }
948:
949: /**
950: * Answers if <code>directory</code> contains <code>member</code>. A
951: * directory can only contain (other) groups.
952: * @param directory java.io.File
953: * @param member
954: * @return boolean
955: */
956: private boolean directoryContains(File directory,
957: IGroupMember member) {
958: boolean found = false;
959: if (member.isGroup()) {
960: File memberFile = getFile((IEntityGroup) member);
961: File[] files = directory.listFiles();
962: for (int i = 0; i < files.length & !found; i++) {
963: found = files[i].equals(memberFile);
964: }
965: }
966: return found;
967: }
968:
969: /**
970: * Answers if <code>group</code> contains a member group named
971: * <code>name</code>.
972: * @return boolean
973: * @param group org.jasig.portal.groups.IEntityGroup
974: * @param name java.lang.String
975: */
976: public boolean containsGroupNamed(IEntityGroup group, String name)
977: throws GroupsException {
978: boolean found = false;
979: Iterator itr = findMemberGroups(group);
980: while (itr.hasNext() && !found) {
981: String otherName = ((IEntityGroup) itr.next()).getName();
982: found = otherName != null && otherName.equals(name);
983: }
984: return found;
985: }
986:
987: }
|