/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.groups.filesystem; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.portal.EntityIdentifier; import org.apereo.portal.groups.EntityGroupImpl; import org.apereo.portal.groups.EntityImpl; import org.apereo.portal.groups.GroupServiceConfiguration; import org.apereo.portal.groups.GroupsException; import org.apereo.portal.groups.ICompositeGroupService; import org.apereo.portal.groups.IEntity; import org.apereo.portal.groups.IEntityGroup; import org.apereo.portal.groups.IEntityGroupStore; import org.apereo.portal.groups.IEntitySearcher; import org.apereo.portal.groups.IEntityStore; import org.apereo.portal.groups.IGroupMember; import org.apereo.portal.groups.ILockableEntityGroup; import org.apereo.portal.security.IPerson; import org.apereo.portal.services.GroupService; import org.apereo.portal.spring.locator.EntityTypesLocator; /** * This class is an <code>IEntityGroupStore</code> that uses the native file system for its back * end. It also implements <code>IEntityStore</code> and a no-op <code>IEntitySearcher</code>. You * can substitute a functional entity searcher by adding it to the group service element for this * component in the configuration document, <code>compositeGroupServices.xml</code>. * * <p>A groups file system looks like this: * * <p><code> * <hr width="100%"> * -- groups root<br> * <blockquote> -- org.apereo.portal.ChannelDefinition<br> * <blockquote> -- channel definition file<br> *  -- channel definition file<br> * ...<br> * </blockquote> *  -- org.apereo.portal.security.IPerson<br> * <blockquote> -- person directory<br> * <blockquote> -- person file <br> *  -- person file <br> * ...<br> * </blockquote> *  -- person directory <br> * </blockquote> * etc.<br> * </blockquote> * <hr width="100%"> * </code> * * <p>The groups root is a file system directory declared in the group service configuration * document, where it is an attribute of the filesystem group service element. This directory has * sub-directories, each named for the underlying entity type that groups in that sub-directory * contain. If a service only contains groups of IPersons, the groups root would have 1 * sub-directory named org.apereo.portal.security.IPerson. * * <p>A directory named for a type may contain both sub-directories and files. The sub-directories * represent groups that can contain other groups. The files represent groups that can contain * entity as well as group members. The files contain keys, one to a line, and look like this: * * <p><code> * <hr width="100%"> * # this is a comment<br> * # another comment<br> * <br> * key1 Key One<br> * key2<br> * group:org$jasig$portal$security$IPerson/someDirectory/someFile<br> * key3<br> *  # comment <br> * <hr width="100%"> * </code> * * <p>Blank lines and lines that start with the <code>COMMENT</code> String (here <code>#</code>) * are ignored. The first token on a non-ignored line is assumed to be a group member key. If the * key starts with the <code>GROUP_PREFIX</code> (here <code>:group</code>), it is treated as a * local group key. Otherwise, it is assumed to be an entity key. The rest of the tokens on the line * are ignored. * * <p>The file above contains 3 entity keys, <code>key1</code>, <code>key2</code>, and <code>key3 * </code>, and 1 group key, <code>org$jasig$portal$security$IPerson/someDirectory/someFile</code>. * It represents a group with 3 entity members and 1 group member. The local key of a group is its * file path starting at the type name, with the <code>FileSystemGroupStore.SUBSTITUTE_PERIOD</code> * character substituted for the real period character. * * <p>The store is not implemented as a singleton, so you can have multiple concurrent instances * pointing to different groups root directories. */ public class FileSystemGroupStore implements IEntityGroupStore, IEntityStore, IEntitySearcher { private static final Log log = LogFactory.getLog(FileSystemGroupStore.class); // File system constants for unix/windows compatibility: protected static char FORWARD_SLASH = '/'; protected static char BACK_SLASH = '\\'; // Group file constants: protected static String COMMENT = "#"; protected static String GROUP_PREFIX = "group:"; // The period is legal in filesystem names but could conflict with // the node separator in the group key. protected static char PERIOD = '.'; protected static char SUBSTITUTE_PERIOD = '$'; protected boolean useSubstitutePeriod = false; private static String DEBUG_CLASS_NAME = "FileSystemGroupStore"; // Path to groups root directory. private String groupsRootPath; // Either back slash or forward slash. protected char goodSeparator; protected char badSeparator; // Cache of retrieved groups. private Map cache; private FilenameFilter fileFilter = new FileFilter(); private Class defaultEntityType; // Value holder adds last modified timestamp. private class GroupHolder { private long lastModified = 0; private IEntityGroup group; protected GroupHolder(IEntityGroup g, long lm) { this.group = g; this.lastModified = lm; } protected IEntityGroup getGroup() { return group; } protected long getLastModified() { return lastModified; } } private class FileFilter implements FilenameFilter { /** * Tests if a specified file should be included in a file list. * * @param dir the directory in which the file was found. * @param name the name of the file. * @return <code>true</code> if and only if the name should be included in the file list; * <code>false</code> otherwise. */ public boolean accept(File dir, String name) { return (!name.startsWith("#")) && (!name.startsWith("%")) && (!name.startsWith(".")) && (!name.endsWith("~")) && (!name.endsWith(".tmp")) && (!name.endsWith(".temp")) && (!name.endsWith(".txt")); } } /** FileSystemGroupStore constructor. */ public FileSystemGroupStore() { this(null); } /** FileSystemGroupStore constructor. */ public FileSystemGroupStore(GroupServiceConfiguration cfg) { super(); initialize(cfg); } /** @return GroupHolder */ protected GroupHolder cacheGet(String key) { return (GroupHolder) getCache().get(key); } protected void cachePut(String key, Object val) { getCache().put(key, val); } protected String conformSeparatorChars(String s) { return s.replace(getBadSeparator(), getGoodSeparator()); } /** * Delete this <code>IEntityGroup</code> from the data store. We assume that groups will be * deleted via the file system, not the group service. * * @param group org.apereo.portal.groups.IEntityGroup */ public void delete(IEntityGroup group) throws GroupsException { throw new UnsupportedOperationException("FileSystemGroupStore.delete() not supported"); } /** * Returns an instance of the <code>IEntityGroup</code> from the data store. * * @return org.apereo.portal.groups.IEntityGroup * @param file java.io.File */ private IEntityGroup find(File file) throws GroupsException { return find(getKeyFromFile(file)); } /** * Returns an instance of the <code>IEntityGroup</code> from the data store. * * @return org.apereo.portal.groups.IEntityGroup * @param key java.lang.String */ public IEntityGroup find(String key) throws GroupsException { if (log.isDebugEnabled()) { log.debug(DEBUG_CLASS_NAME + ".find(): group key: " + key); } String path = getFilePathFromKey(key); File f = new File(path); GroupHolder groupHolder = cacheGet(key); if (groupHolder == null || (groupHolder.getLastModified() != f.lastModified())) { if (log.isDebugEnabled()) { log.debug( DEBUG_CLASS_NAME + ".find(): retrieving group from file system for " + path); } if (!f.exists()) { if (log.isDebugEnabled()) { log.debug(DEBUG_CLASS_NAME + ".find(): file does not exist: " + path); } return null; } IEntityGroup group = newInstance(f); groupHolder = new GroupHolder(group, f.lastModified()); cachePut(key, groupHolder); } return groupHolder.getGroup(); } /** * Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntityGroups * </code> that the <code>IEntity</code> belongs to. * * @return java.util.Iterator * @param ent org.apereo.portal.groups.IEntityGroup */ protected Iterator findParentGroups(IEntity ent) throws GroupsException { if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + ".findParentGroups(): for " + ent); List groups = new ArrayList(); File root = getFileRoot(ent.getType()); if (root != null) { File[] files = getAllFilesBelow(root); try { for (int i = 0; i < files.length; i++) { Collection ids = getEntityIdsFromFile(files[i]); if (ids.contains(ent.getKey())) { groups.add(find(files[i])); } } } catch (IOException ex) { throw new GroupsException("Problem reading group files", ex); } } return groups.iterator(); } /** * Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntityGroups * </code> that the <code>IGroupMember</code> belongs to. * * @return java.util.Iterator * @param group org.apereo.portal.groups.IEntityGroup */ protected Iterator findParentGroups(IEntityGroup group) throws GroupsException { if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + ".findParentGroups(): for " + group); List groups = new ArrayList(); { String typeName = group.getLeafType().getName(); File parent = getFile(group).getParentFile(); if (!parent.getName().equals(typeName)) { groups.add(find(parent)); } File root = getFileRoot(group.getLeafType()); File[] files = getAllFilesBelow(root); try { for (int i = 0; i < files.length; i++) { Collection ids = getGroupIdsFromFile(files[i]); if (ids.contains(group.getLocalKey())) { groups.add(find(files[i])); } } } catch (IOException ex) { throw new GroupsException("Problem reading group files", ex); } } return groups.iterator(); } /** * Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntityGroups * </code> that the <code>IGroupMember</code> belongs to. * * @return java.util.Iterator * @param gm org.apereo.portal.groups.IEntityGroup */ public Iterator findParentGroups(IGroupMember gm) throws GroupsException { if (gm.isGroup()) { IEntityGroup group = (IEntityGroup) gm; return findParentGroups(group); } else { IEntity ent = (IEntity) gm; return findParentGroups(ent); } } /** * Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntities</code> * that are members of this <code>IEntityGroup</code>. * * @return java.util.Iterator * @param group org.apereo.portal.groups.IEntityGroup */ public java.util.Iterator findEntitiesForGroup(IEntityGroup group) throws GroupsException { if (log.isDebugEnabled()) log.debug( DEBUG_CLASS_NAME + ".findEntitiesForGroup(): retrieving entities for group " + group); Collection entities = null; File f = getFile(group); if (f.isDirectory()) { entities = Collections.EMPTY_LIST; } else { entities = getEntitiesFromFile(f); } return entities.iterator(); } /** * Returns an instance of the <code>ILockableEntityGroup</code> from the data store. * * @return org.apereo.portal.groups.IEntityGroup * @param key java.lang.String */ public ILockableEntityGroup findLockable(String key) throws GroupsException { throw new UnsupportedOperationException(DEBUG_CLASS_NAME + ".findLockable() not supported"); } /** * Returns a <code>String[]</code> containing the keys of <code>IEntityGroups</code> that are * members of this <code>IEntityGroup</code>. In a composite group system, a group may contain a * member group from a different service. This is called a foreign membership, and is only * possible in an internally-managed service. A group store in such a service can return the key * of a foreign member group, but not the group itself, which can only be returned by its local * store. * * @return String[] * @param group org.apereo.portal.groups.IEntityGroup */ public java.lang.String[] findMemberGroupKeys(IEntityGroup group) throws GroupsException { String[] keys; File f = getFile(group); if (f.isDirectory()) { File[] files = f.listFiles(); keys = new String[files.length]; for (int i = 0; i < files.length; i++) { keys[i] = getKeyFromFile(files[i]); } } else { try { Collection groupKeys = getGroupIdsFromFile(f); keys = (String[]) groupKeys.toArray(new String[groupKeys.size()]); } catch (IOException ex) { throw new GroupsException( DEBUG_CLASS_NAME + ".findMemberGroupKeys(): " + "problem finding group members", ex); } } return keys; } /** * Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntityGroups * </code> that are members of this <code>IEntityGroup</code>. * * @return java.util.Iterator * @param group org.apereo.portal.groups.IEntityGroup */ public java.util.Iterator findMemberGroups(IEntityGroup group) throws GroupsException { String[] keys = findMemberGroupKeys(group); // No foreign groups here. List groups = new ArrayList(keys.length); for (int i = 0; i < keys.length; i++) { groups.add(find(keys[i])); } return groups.iterator(); } /** * Recursive search of directories underneath dir for files that match filter. * * @return java.util.Set */ public Set getAllDirectoriesBelow(File dir) { Set allDirectories = new HashSet(); if (dir.isDirectory()) { primGetAllDirectoriesBelow(dir, allDirectories); } return allDirectories; } /** Recursive search of directories underneath dir for files that match filter. */ public File[] getAllFilesBelow(File dir) { Set allFiles = new HashSet(); if (dir.isDirectory()) { primGetAllFilesBelow(dir, allFiles); } return (File[]) allFiles.toArray(new File[allFiles.size()]); } /** * Returns the filesystem separator character NOT in use. * * @return char */ protected char getBadSeparator() { return badSeparator; } /** @return java.util.Map */ protected java.util.Map getCache() { return cache; } /** * Returns a Class representing the default entity type. * * @return Class */ protected Class getDefaultEntityType() { return defaultEntityType; } /** * @param idFile java.io.File - a file of ids. * @return entities Collection. */ protected Collection getEntitiesFromFile(File idFile) throws GroupsException { if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + "getEntitiesFromFile(): for " + idFile.getPath()); Collection ids = null; Class type = getEntityType(idFile); if (EntityTypesLocator.getEntityTypes().getEntityIDFromType(type) == null) { throw new GroupsException("Invalid entity type: " + type); } try { ids = getEntityIdsFromFile(idFile); } catch (Exception ex) { throw new GroupsException("Problem retrieving keys from file", ex); } Collection entities = new ArrayList(ids.size()); for (Iterator itr = ids.iterator(); itr.hasNext(); ) { String key = (String) itr.next(); entities.add(GroupService.getEntity(key, type)); } if (log.isDebugEnabled()) log.debug( DEBUG_CLASS_NAME + "getEntitiesFromFile(): Retrieved " + entities.size() + " entities"); return entities; } /** * @param idFile java.io.File - a file of ids. * @return String[] ids. */ protected Collection getEntityIdsFromFile(File idFile) throws IOException, FileNotFoundException { if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + "getEntityIdsFromFile(): Reading " + idFile.getPath()); Collection ids = getIdsFromFile(idFile, false); if (log.isDebugEnabled()) log.debug( DEBUG_CLASS_NAME + "getEntityIdsFromFile(): Retrieved " + ids.size() + " IDs"); return ids; } /** * @param f File * @return java.lang.Class The Class is the first node of the full path name. */ protected Class getEntityType(File f) { String path = f.getPath(); String afterRootPath = null; Class type = null; if (path.startsWith(getGroupsRootPath())) { afterRootPath = path.substring(getGroupsRootPath().length()); int end = afterRootPath.indexOf(File.separatorChar); String typeName = afterRootPath.substring(0, end); try { type = Class.forName(typeName); } catch (ClassNotFoundException cnfe) { } } return type; } /** * @param group IEntityGroup. * @return File */ protected File getFile(IEntityGroup group) { String key = getFilePathFromKey(group.getLocalKey()); return new File(key); } protected String getFilePathFromKey(String key) { if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + ".getFilePathFromKey(): for key: " + key); String groupKey = useSubstitutePeriod ? key.replace(SUBSTITUTE_PERIOD, PERIOD) : key; String fullKey = getGroupsRootPath() + groupKey; if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + ".getFilePathFromKey(): full key: " + fullKey); return conformSeparatorChars(fullKey); } /** Returns a File that is the root for groups of the given type. */ protected File getFileRoot(Class type) { String path = getGroupsRootPath() + type.getName(); File f = new File(path); return (f.exists()) ? f : null; } /** * Returns the filesystem separator character in use. * * @return char */ protected char getGoodSeparator() { return goodSeparator; } /** * @param idFile java.io.File - a file of ids. * @return String[] ids. */ protected Collection getGroupIdsFromFile(File idFile) throws IOException, FileNotFoundException { if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + "getGroupIdsFromFile(): Reading " + idFile.getPath()); Collection ids = getIdsFromFile(idFile, true); if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + "getGroupIdsFromFile(): Retrieved " + ids.size() + " IDs"); return ids; } /** @return java.lang.String */ public java.lang.String getGroupsRootPath() { return groupsRootPath; } /** * @param idFile java.io.File - a file of ids. * @return String[] ids. */ protected Collection getIdsFromFile(File idFile, boolean groupIds) throws IOException, FileNotFoundException { Collection ids = new HashSet(); BufferedReader br = new BufferedReader(new FileReader(idFile)); String line, tok; line = br.readLine(); while (line != null) { line = line.trim(); if (!line.startsWith(COMMENT) && (line.length() > 0)) { StringTokenizer st = new StringTokenizer(line); tok = st.nextToken(); if (tok != null) { if (tok.startsWith(GROUP_PREFIX)) { if (groupIds) { ids.add(tok.substring(GROUP_PREFIX.length())); } } else { if (!groupIds) { ids.add(tok); } } } } line = br.readLine(); } br.close(); return ids; } protected String getKeyFromFile(File f) { String key = null; if (f.getPath().startsWith(getGroupsRootPath())) { key = f.getPath().substring(getGroupsRootPath().length()); if (useSubstitutePeriod) { key = key.replace(PERIOD, SUBSTITUTE_PERIOD); } } return key; } protected void initialize(GroupServiceConfiguration cfg) { cache = Collections.synchronizedMap(new HashMap()); goodSeparator = File.separatorChar; badSeparator = (goodSeparator == FORWARD_SLASH) ? BACK_SLASH : FORWARD_SLASH; defaultEntityType = IPerson.class; GroupServiceConfiguration config = cfg; if (config == null) { try { config = GroupServiceConfiguration.getConfiguration(); } catch (Exception ex) { throw new RuntimeException(ex); } } String sep = config.getNodeSeparator(); if (sep != null) { String period = String.valueOf(PERIOD); useSubstitutePeriod = sep.equals(period); } } /** @return org.apereo.portal.groups.IEntityGroup */ private IEntityGroup newInstance(File f) throws GroupsException { String key = getKeyFromFile(f); String name = f.getName(); Class cl = getEntityType(f); return newInstance(key, cl, name); } /** * @return org.apereo.portal.groups.IEntityGroup We assume that new groups will be created * updated via the file system, not the group service. */ public IEntityGroup newInstance(Class entityType) throws GroupsException { throw new UnsupportedOperationException( DEBUG_CLASS_NAME + ".newInstance(Class cl) not supported"); } public IEntity newInstance(String key, Class type) throws GroupsException { if (EntityTypesLocator.getEntityTypes().getEntityIDFromType(type) == null) { throw new GroupsException("Invalid group type: " + type); } return new EntityImpl(key, type); } /** @return org.apereo.portal.groups.IEntityGroup */ private IEntityGroup newInstance(String newKey, Class newType, String newName) throws GroupsException { EntityGroupImpl egi = new EntityGroupImpl(newKey, newType); egi.primSetName(newName); return egi; } /** Returns all directories under dir. */ private void primGetAllDirectoriesBelow(File dir, Set allDirectories) { File[] files = dir.listFiles(fileFilter); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { primGetAllDirectoriesBelow(files[i], allDirectories); allDirectories.add(files[i]); } } } /** Returns all files (not directories) underneath dir. */ private void primGetAllFilesBelow(File dir, Set allFiles) { File[] files = dir.listFiles(fileFilter); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { primGetAllFilesBelow(files[i], allFiles); } else { allFiles.add(files[i]); } } } /** * Find EntityIdentifiers for entities whose name matches the query string according to the * specified method and is of the specified type */ public EntityIdentifier[] searchForEntities(String query, int method, Class type) throws GroupsException { return new EntityIdentifier[0]; } /** * Returns an EntityIdentifier[] of groups of the given leaf type whose names match the query * string according to the search method. * * @param query String the string used to match group names. * @param searchMethod see org.apereo.portal.groups.IGroupConstants. * @param leafType the leaf type of the groups we are searching for. * @return EntityIdentifier[] */ public EntityIdentifier[] searchForGroups(String query, int searchMethod, Class leafType) throws GroupsException { List ids = new ArrayList(); File baseDir = getFileRoot(leafType); if (log.isDebugEnabled()) log.debug( DEBUG_CLASS_NAME + "searchForGroups(): " + query + " method: " + searchMethod + " type: " + leafType); if (baseDir != null) { String nameFilter = null; switch (searchMethod) { case IS: nameFilter = query; break; case STARTS_WITH: nameFilter = query + ".*"; break; case ENDS_WITH: nameFilter = ".*" + query; break; case CONTAINS: nameFilter = ".*" + query + ".*"; break; default: throw new GroupsException( DEBUG_CLASS_NAME + ".searchForGroups(): Unknown search method: " + searchMethod); } final Pattern namePattern = Pattern.compile(nameFilter); final FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return namePattern.matcher(name).matches(); } }; Set allDirs = getAllDirectoriesBelow(baseDir); allDirs.add(baseDir); for (Iterator itr = allDirs.iterator(); itr.hasNext(); ) { File[] files = ((File) itr.next()).listFiles(filter); for (int filesIdx = 0; filesIdx < files.length; filesIdx++) { String key = getKeyFromFile(files[filesIdx]); EntityIdentifier ei = new EntityIdentifier(key, ICompositeGroupService.GROUP_ENTITY_TYPE); ids.add(ei); } } } if (log.isDebugEnabled()) log.debug(DEBUG_CLASS_NAME + ".searchForGroups(): found " + ids.size() + " files."); return (EntityIdentifier[]) ids.toArray(new EntityIdentifier[ids.size()]); } /** @param newCache java.util.Map */ protected void setCache(java.util.Map newCache) { cache = newCache; } /** @param newGroupsRootPath java.lang.String */ protected void setGroupsRootPath(java.lang.String newGroupsRootPath) { groupsRootPath = conformSeparatorChars(newGroupsRootPath) + getGoodSeparator(); } /** * Adds or updates the <code>IEntityGroup</code> AND ITS MEMBERSHIPS to the data store, as * appropriate. We assume that groups will be updated via the file system, not the group * service. * * @param group org.apereo.portal.groups.IEntityGroup */ public void update(IEntityGroup group) throws GroupsException { throw new UnsupportedOperationException(DEBUG_CLASS_NAME + ".update() not supported"); } /** * Commits the group memberships of the <code>IEntityGroup</code> to the data store. We assume * that groups will be updated via the file system, not the group service. * * @param group org.apereo.portal.groups.IEntityGroup */ public void updateMembers(IEntityGroup group) throws GroupsException { throw new UnsupportedOperationException( DEBUG_CLASS_NAME + ".updateMembers() not supported"); } /** * Answers if <code>group</code> contains <code>member</code>. * * @return boolean * @param group org.apereo.portal.groups.IEntityGroup * @param member org.apereo.portal.groups.IGroupMember */ public boolean contains(IEntityGroup group, IGroupMember member) throws GroupsException { File f = getFile(group); return (f.isDirectory()) ? directoryContains(f, member) : fileContains(f, member); } /** * Answers if <code>file</code> contains <code>member</code>. * * @param file * @param member * @return boolean */ private boolean fileContains(File file, IGroupMember member) throws GroupsException { Collection ids = null; try { ids = member.isGroup() ? getGroupIdsFromFile(file) : getEntityIdsFromFile(file); } catch (Exception ex) { throw new GroupsException("Error retrieving ids from file", ex); } return ids.contains(member.getKey()); } /** * Answers if <code>directory</code> contains <code>member</code>. A directory can only contain * (other) groups. * * @param directory java.io.File * @param member * @return boolean */ private boolean directoryContains(File directory, IGroupMember member) { boolean found = false; if (member.isGroup()) { File memberFile = getFile((IEntityGroup) member); File[] files = directory.listFiles(); for (int i = 0; i < files.length & !found; i++) { found = files[i].equals(memberFile); } } return found; } }