/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * $Id: FilesRepo.java,v 1.22 2008/07/02 17:21:21 kenwho Exp $
 *
 * Portions Copyrighted 2011-2016 ForgeRock AS.
 */
package com.sun.identity.idm.plugins.files;

import static org.forgerock.openam.utils.Time.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;

import com.iplanet.am.sdk.AMEvent;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.authentication.spi.AuthLoginException;
import com.sun.identity.common.CaseInsensitiveHashMap;
import com.sun.identity.common.CaseInsensitiveHashSet;
import com.sun.identity.common.GeneralTaskRunnable;
import com.sun.identity.common.SystemTimer;
import com.sun.identity.idm.IdOperation;
import com.sun.identity.idm.IdRepo;
import com.sun.identity.idm.IdRepoBundle;
import com.sun.identity.idm.IdRepoDuplicateObjectException;
import com.sun.identity.idm.IdRepoErrorCode;
import com.sun.identity.idm.IdRepoException;
import com.sun.identity.idm.IdRepoListener;
import com.sun.identity.idm.IdRepoUnsupportedOpException;
import com.sun.identity.idm.IdType;
import com.sun.identity.idm.RepoSearchResults;
import com.sun.identity.security.DecodeAction;
import com.sun.identity.security.EncodeAction;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.encode.Hash;
import com.sun.identity.shared.jaxrpc.SOAPClient;
import com.sun.identity.sm.SchemaType;
import org.forgerock.openam.utils.CrestQuery;

/**
 * This class stores identity information in flat files using
 * <code>java.io.File</code> classes. The directory structure is organized as
 * follows: The root directory is specified by the instance configuration
 * parameter <code>"sunFilesIdRepoDirectory"</code>. If not specified, it
 * defaults to <code>"/var/opt/SUNWam/idm/flatfiles"</code>.
 * Under the root directory are sub-directories for each identity type (i.e.,
 * users, roles, agents, etc). In these sub-directories an identity is stored
 * as a properties file.
 */
public class FilesRepo extends IdRepo {
    // Class name
    public static final String NAME =
        "com.sun.identity.idm.plugins.files.FilesRepo";

    public static final Debug debug = Debug.getInstance("amIdRepoFiles");

    // SMS Configurations
    // Root directory
    public static final String DIRECTORY = "sunFilesIdRepoDirectory";

    // User object classes
    public static final String OBJECTCLASS = "sunFilesObjectClasses";

    // Password attribute
    public static final String PASSWORD = "sunFilesPasswordAttr";

    // Status attribute
    public static final String STATUS = "sunFilesStatusAttr";

    // Attributes to be hashed
    public static final String HASH = "sunFilesHashAttrs";

    // Attributes to be encrypted
    public static final String ENCRYPT = "sunFilesEncryptAttrs";

    // Attribute to enable cache update
    public static final String UPDATE_CACHE = "sunFilesMonitorForChanges";

    // Attribute to define cache update time
    public static final String UPDATE_CACHE_TIME = "sunFilesMonitoringTime";

    // Objectclass attribute
    public static final String OC = "objectclass";

    // Supported operations
    private static Map supportedOps = new CaseInsensitiveHashMap();

    // Cache of Identity objects
    private static Map identityCache = new CaseInsensitiveHashMap();

    // Cache of time last modified identity objects
    private Map identityTimeCache = new HashMap();

    // Flag to run a thread to validate the cache
    boolean cacheUpdateEnabled;
    int updateCacheInterval;


    // Default directory to store identity data
    private String directory = "/var/opt/SUNWam/idm/flatfiles";

    // Password attribute
    private String passwordAttribute = "userPassword";

    // Status attribute
    private String statusAttribute = "inetUserStatus";
    private String statusActive = "Active";
    private String statusInactive = "Inactive";

    // User objectclasses attribute
    Set userOCs;

    // Role membership attribute
    private String roleMembershipAttribute = "nsRoleDN";

    // Group members attribute
    private String groupMembersAttribute = "memberOfGroup";

    // Attributes to be hashed
    private Set hashAttributes = new CaseInsensitiveHashSet();

    // Attributes to be encrypted
    private Set encryptAttributes = new CaseInsensitiveHashSet();

    // IdRepo listner to send notifications
    IdRepoListener repoListener;

    // Initialization exception
    IdRepoException initializationException;

    // Initialize supported types and operations
    static {
        loadSupportedOps();
    }

    public FilesRepo() {
        // do nothing
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#initialize(java.util.Map)
     */
    public void initialize(Map configParams) throws IdRepoException {
        super.initialize(configParams);
        // Get the directory to store the identity information
        Set set = (Set) configParams.get(DIRECTORY);
        if (set != null && !set.isEmpty()) {
            directory = (String) set.iterator().next();
            try {
                initDir(directory);
            } catch (IdRepoException ide) {
                initializationException = ide;
                debug.error("FilesRepo: Init exception", ide);
            }
        }

        // Get password attribute
        set = (Set) configParams.get(PASSWORD);
        if ((set != null) && !set.isEmpty()) {
            passwordAttribute = (String)set.iterator().next();
        }

        // Get objectclass attribute
        userOCs = (Set) configParams.get(OBJECTCLASS);
        if (userOCs == null) {
            userOCs = Collections.EMPTY_SET;
        }

        // Get status attribute
        set = (Set) configParams.get(STATUS);
        if (set != null && !set.isEmpty()) {
            statusAttribute = (String) set.iterator().next();
        }
        // Get attributes to be hashed
        set = (Set) configParams.get(HASH);
        if (set != null && !set.isEmpty()) {
            hashAttributes.addAll(set);
        }
        // Get attributes to be encyrpted
        set = (Set) configParams.get(ENCRYPT);
        if (set != null && !set.isEmpty()) {
            encryptAttributes.addAll(set);
        }

        // Flag to update cache
        set = (Set) configParams.get(UPDATE_CACHE);
        if ((set != null) && !set.isEmpty()) {
            String value = (String) set.iterator().next();
            if (value.equalsIgnoreCase("true")) {
                cacheUpdateEnabled = true;
                set = (Set) configParams.get(UPDATE_CACHE_TIME);
                if ((set != null) && !set.isEmpty()) {
                    value = (String) set.iterator().next();
                    try {
                        updateCacheInterval = Integer.parseInt(
                            value);
                     } catch (NumberFormatException nfe) {
                        // Default to 10 minutes
                        updateCacheInterval = 10;
                     }
                }
                SystemTimer.getTimer().schedule(
                    new CacheUpdateRunnable(this), new Date(
                        ((currentTimeMillis() + (updateCacheInterval *
                                60 * 1000)) / 1000) * 1000));
            }
        }

        if (debug.messageEnabled()) {
            debug.message("FlatFiles: Root dir: " + directory +
                "\n\tUser Objectclasses: " + userOCs +
                "\n\tPassword Attr: " + passwordAttribute +
                "\n\tStatus Attr: " + statusAttribute +
                "\n\tAttrs Hashed: " + hashAttributes +
                "\n\tAttrs Encyrpted: " + encryptAttributes +
                "\n\tUpdate Cache: " + cacheUpdateEnabled +
                "\n\tUpdate Interval: " + updateCacheInterval);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#addListener(com.iplanet.sso.SSOToken,
     *      com.iplanet.am.sdk.IdRepoListener)
     */
    public int addListener(SSOToken token, IdRepoListener listener)
            throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo addListener called");
        }
        repoListener = listener;
        return 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#assignService(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.lang.String,
     *      com.sun.identity.sm.SchemaType, java.util.Map)
     */
    public void assignService(SSOToken token, IdType type, String name,
            String serviceName, SchemaType stype, Map attrMap)
            throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("Assign service called for: " + type.getName() + ":"
                    + name + "\n\t" + serviceName + "=" + attrMap
                    + "\n\tSchema=" + stype);
        }
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }

        if (type.equals(IdType.USER) || type.equals(IdType.ROLE)
                || type.equals(IdType.REALM)) {
            // Update the objectclass and set attributes
            Set set = new HashSet();
            set.add(OC);
            Map attrs = getAttributes(token, type, name, set);
            Set objectclasses = (Set) attrs.get(OC);
            CaseInsensitiveHashMap sAttrs = new CaseInsensitiveHashMap();
            sAttrs.putAll(attrMap);
            Set serviceOcs = (Set) sAttrs.get(OC);
            if (objectclasses != null && !objectclasses.isEmpty()
                    && serviceOcs != null) {
                // Update objectclasses
                serviceOcs.addAll(objectclasses);
            }
            setAttributes(token, type, name, attrMap, false);
        } else {
            Object args[] = { NAME, IdOperation.SERVICE.getName() };
            throw new IdRepoUnsupportedOpException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.PLUGIN_OPERATION_NOT_SUPPORTED, args);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#create(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.util.Map)
     */
    public String create(SSOToken token, IdType type, String name, Map attrMap)
            throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }
        if (supportedOps.keySet().contains(type)) {
            // Check if identity exists
            File file = constructFile(directory, type, name);
            if (!file.exists()) {
                // If type is user, add the configured object classes
                CaseInsensitiveHashMap nAttrs =
                    new CaseInsensitiveHashMap(attrMap);
                Set ocs = (Set) nAttrs.get(OC);
                if (ocs == null) {
                    nAttrs.put(OC, userOCs);
                } else {
                    CaseInsensitiveHashSet ocv =new CaseInsensitiveHashSet(ocs);
                    ocv.addAll(userOCs);
                }
                // Create the identity
                attrMap = processAttributes(nAttrs, hashAttributes,
                    encryptAttributes);
                writeFile(file, attrMap);
                // %%% Send notification (must be via a different thread)
                if (repoListener != null) {
                    repoListener.objectChanged(name, type, AMEvent.OBJECT_ADDED,
                            repoListener.getConfigMap());
                }
            } else {
                // throw exception
                throw IdRepoDuplicateObjectException.nameAlreadyExists(file.getAbsolutePath());
            }
        } else {
            Object args[] = { NAME, IdOperation.SERVICE.getName(),
                type.getName() };
            throw new IdRepoUnsupportedOpException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.PLUGIN_OPERATION_NOT_SUPPORTED, args);
        }
        return (name);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#delete(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String)
     */
    public void delete(SSOToken token, IdType type, String name)
            throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo.delete: throwing initialization exception");
            throw (initializationException);
        }
        File file = constructFile(directory, type, name);
        file.delete();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getAssignedServices(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.util.Map)
     */
    public Set getAssignedServices(SSOToken token, IdType type, String name,
            Map mapOfServicesAndOCs) throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.getAssignedService called: " + name
                    + "\n\tmapOfServicesAndOCs=" + mapOfServicesAndOCs);
        }
        if (initializationException != null) {
            debug.error(
            "FilesRepo.getAssignedServices: throwing initialization exception");
            throw (initializationException);
        }

        // Get objectclasses for the identity
        Set attrNames = new HashSet();
        attrNames.add(OC);
        Map ocs = getAttributes(token, type, name, attrNames);
        Set presentOcs = (Set) ocs.get(OC);
        if (presentOcs == null || presentOcs.isEmpty()) {
            return (Collections.EMPTY_SET);
        }

        // Check ocs against the mapOfServicesAndOCs
        Set answer = new HashSet();
        for (Iterator items = mapOfServicesAndOCs.keySet().iterator(); items
                .hasNext();) {
            String serviceName = (String) items.next();
            Set reqOcs = (Set) mapOfServicesAndOCs.get(serviceName);
            if (reqOcs != null && !reqOcs.isEmpty()
                    && presentOcs.containsAll(reqOcs)) {
                answer.add(serviceName);
            }
        }
        return (answer);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getAttributes(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.util.Set)
     */
    public Map getAttributes(SSOToken token, IdType type, String name,
            Set attrNames) throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.getAttributes called: " + name
                    + "\n\treturn attributes=" + attrNames);
        }
        if (initializationException != null) {
            debug.error(
                "FilesRepo.getAttributes: throwing initialization exception");
            throw (initializationException);
        }

        // Get all the attributes and return the subset
        Map answer = (attrNames == null) ? null : new HashMap();
        Map map = getAttributes(token, type, name);
        if (attrNames == null) {
            answer = map;
        } else {
            for (Iterator items = attrNames.iterator(); items.hasNext();) {
                Object key = items.next();
                Object value = map.get(key);
                if (value != null) {
                    answer.put(key, value);
                }
            }
        }
        return (answer);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getAttributes(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String)
     */
    public Map getAttributes(SSOToken token, IdType type, String name)
            throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo: get all attributes called: "
                    + type.getName() + "::" + name);
        }
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }
        File file = constructFile(directory, type, name);
        return (decodeAttributes(readFile(file), encryptAttributes));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getBinaryAttributes(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.util.Set)
     */
    public Map getBinaryAttributes(SSOToken token, IdType type, String name,
            Set attrNames) throws IdRepoException, SSOException {
        Map stringAttributes = getAttributes(token, type, name, attrNames);
        Map binaryAttributes = new HashMap();
        for (Iterator items = stringAttributes.keySet().iterator(); items
                .hasNext();) {
            String attrName = (String) items.next();
            Set values = (Set) stringAttributes.get(attrName);
            byte[][] binValues = new byte[values.size()][];
            int i = 0;
            try {
                for (Iterator it = values.iterator(); it.hasNext(); i++) {
                    binValues[i] = ((String) it.next()).getBytes("UTF-8");
                }
            } catch (UnsupportedEncodingException e) {
                // Ignore the exception
            }
            binaryAttributes.put(attrName, binValues);
        }
        return (binaryAttributes);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#setBinaryAttributes(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.util.Map, boolean)
     */
    public void setBinaryAttributes(
        SSOToken token,
        IdType type,
        String name,
        Map attributes,
        boolean isAdd
    ) throws IdRepoException, SSOException {
        if (attributes == null) {
            return;
        }
        // Convert byte[][] attributes values to Set and save it
        Map attrs = new HashMap();
        for (Iterator items = attributes.keySet().iterator(); items.hasNext();)
        {
            String attrName = (String) items.next();
            byte[][] attrBytes = (byte[][]) attributes.get(attrName);
            Set attrValues = new HashSet();
            int size = attrBytes.length;
            try {
                for (int i = 0; i < size; i++) {
                    attrValues.add(new String(attrBytes[i], "UTF-8"));
                }
            } catch (UnsupportedEncodingException e) {
                // Ignore the exception
            }
            attrs.put(attrName, attrValues);
        }
        setAttributes(token, type, name, attrs, isAdd);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getMembers(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String,
     *      com.sun.identity.idm.IdType)
     */
    public Set getMembers(
        SSOToken token,
        IdType type,
        String name,
        IdType membersType
    ) throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.getMembers called" + type + ": " + name +
                ": " + membersType);
        }
        if (initializationException != null) {
            debug.error(
                "FilesRepo.getMembers: throwing initialization exception");
            throw (initializationException);
        }

        // Memers can be returned for roles and groups
        if (!type.equals(IdType.ROLE) && !type.equals(IdType.GROUP)) {
            debug.message(
                "FilesRepo.getMembers supported for roles and groups");
            throw new IdRepoException(IdRepoBundle.getString(IdRepoErrorCode.MEMBERSHIP_TO_USERS_AND_AGENTS_NOT_ALLOWED),
                    IdRepoErrorCode.MEMBERSHIP_TO_USERS_AND_AGENTS_NOT_ALLOWED);
        }

        // Set to maintain the members
        Set results = new HashSet();

        // Process group members
        if (type.equals(IdType.GROUP)) {
            // Read the group files and return the membership attribute
            File file = constructFile(directory, type, name);
            Map attrs = decodeAttributes(readFile(file), encryptAttributes);
            Set members = (Set) attrs.get(groupMembersAttribute);
            // Iterate through members and add to results only if "membersType"
            // matches
            if (members != null && !members.isEmpty()) {
                String mtype = membersType.getName();
                int mtypeLen = mtype.length();
                for (Iterator items = members.iterator(); items.hasNext();) {
                    String sname = (String) items.next();
                    if (sname.startsWith(mtype)) {
                        results.add(sname.substring(mtypeLen));
                    }
                }
            }
        } else if (type.equals(IdType.ROLE)) {
            // Get the list of all "membersType" and check if they belong
            // to the group
            Set returnAttrs = new HashSet();
            returnAttrs.add(roleMembershipAttribute);
            RepoSearchResults allUsers = search(token, membersType, "*", 0, 0,
                    returnAttrs, false, IdRepo.OR_MOD, null, false);
            Map userAttributes = null;
            if ((allUsers != null) &&
                ((userAttributes = allUsers.getResultAttributes()) != null)
            ) {
                for (Iterator i = userAttributes.keySet().iterator();
                    i.hasNext(); ) {
                    String sname = (String)i.next();
                    Map attrs = (Map) userAttributes.get(sname);
                    // Check if user belongs to the role
                    Set roles = (Set) attrs.get(roleMembershipAttribute);
                    if (roles != null && roles.contains(name)) {
                        results.add(sname);
                    }
                }
            }
        } else {
            // throw unsupported operation exception
            Object args[] = {NAME, IdOperation.READ.getName(), type.getName()};
            throw new IdRepoUnsupportedOpException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.PLUGIN_OPERATION_NOT_SUPPORTED, args);
        }
        return (results);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getMemberships(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, com.sun.identity.idm.IdType)
     */
    public Set getMemberships(SSOToken token, IdType type, String name,
            IdType membershipType) throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.getMemberships called " + type + ": " +
                name + ": " + membershipType);
        }
        if (initializationException != null) {
            debug.error(
                "FilesRepo.getMemeberships: throwing initialization exception");
            throw (initializationException);
        }

        // Memerships can be returned for users and agents
        if (!type.equals(IdType.USER) && !type.equals(IdType.AGENT)) {
            debug.message(
                "FilesRepo:getMemberships supported for users and agents");
            Object args[] = { NAME };
            throw (new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.MEMBERSHIPS_FOR_NOT_USERS_NOT_ALLOWED, args));
        }

        // Set to maintain the members
        Set results = new HashSet();
        if (membershipType.equals(IdType.ROLE)) {
            // Get the role attribute and return
            Set returnAttrs = new HashSet();
            returnAttrs.add(roleMembershipAttribute);
            Map attrs = getAttributes(token, type, name, returnAttrs);
            if (attrs != null) {
                Set roles = (Set) attrs.get(roleMembershipAttribute);
                if (roles != null) {
                    results = roles;
                }
            }
        } else if (membershipType.equals(IdType.GROUP)) {
            // Get the list of groups and search for memberships
            Set returnAttrs = new HashSet();
            returnAttrs.add(groupMembersAttribute);
            RepoSearchResults allGroups = search(token, membershipType, "*", 0,
                    0, returnAttrs, false, IdRepo.OR_MOD, null, false);
            Map groupAttrs = null;
            if ((allGroups != null) &&
                ((groupAttrs = allGroups.getResultAttributes()) != null)
            ) {
                // Prefix name with IdType
                name = type.getName() + name;
                for (Iterator i = groupAttrs.keySet().iterator(); i.hasNext();){
                    String sname = (String)i.next();
                    Map attrs = (Map) groupAttrs.get(sname);
                    Set ids = (Set) attrs.get(groupMembersAttribute);
                    if (ids != null && ids.contains(name)) {
                        results.add(sname);
                    }
                }
            }
        } else {
            // throw unsupported operation exception
            Object args[] = { NAME, IdOperation.READ.getName(),
                membershipType.getName() };
            throw new IdRepoUnsupportedOpException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.PLUGIN_OPERATION_NOT_SUPPORTED, args);
        }
        return (results);
    }


    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getServiceAttributes(
     *      com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.lang.String,
     *      java.util.Set)
     */
    public Map getServiceAttributes(SSOToken token, IdType type, String name,
        String serviceName, Set attrNames) throws IdRepoException,
        SSOException {
        return(getServiceAttributes(token, type, name, serviceName,
            attrNames, true));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getServiceAttributes(
     *      com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.lang.String,
     *      java.util.Set)
     */
    public Map getBinaryServiceAttributes(SSOToken token, IdType type,
        String name, String serviceName, Set attrNames)
        throws IdRepoException, SSOException {
        return(getServiceAttributes(token, type, name, serviceName,
            attrNames, false));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getServiceAttributes(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.lang.String, java.util.Set)
     */
    private Map getServiceAttributes(SSOToken token, IdType type, String name,
        String serviceName, Set attrNames, boolean isString)
        throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.getServiceAttributes called. " + name +
                "\n\t" + serviceName + "=" + attrNames);
        }
        if (initializationException != null) {
            debug.error(
           "FilesRepo.getServiceAttributes: throwing initialization exception");
            throw (initializationException);
        }

        if (!type.equals(IdType.USER) && !type.equals(IdType.ROLE) &&
            !type.equals(IdType.REALM)
        ) {
            // Unsupported Operation
            Object args[] = { NAME, IdOperation.SERVICE.getName() };
            throw new IdRepoUnsupportedOpException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.PLUGIN_OPERATION_NOT_SUPPORTED, args);
        }

        // Get attributes from Identity Object
        Map results = (isString ?
            getAttributes(token, type, name, attrNames)
            : getBinaryAttributes(token, type, name, attrNames));
        if (results == null) {
            results = new HashMap();
        }

        // For types role and realm, return the attributes
        if (!type.equals(IdType.USER)) {
            return (results);
        }

        // Get the roles for the identity and add the service attributes
        Set roles = getMemberships(token, type, name, IdType.ROLE);
        for (Iterator items = roles.iterator(); items.hasNext();) {
            String role = (String) items.next();
            Map roleAttrs = Collections.EMPTY_MAP;
            try {
                roleAttrs = (isString ?
                    getAttributes(token, IdType.ROLE, role, attrNames)
                    : getBinaryAttributes(token, IdType.ROLE,
                        role, attrNames));
            } catch (FilesRepoEntryNotFoundException fnf) {
                roleAttrs = Collections.EMPTY_MAP;
            }
            // Add the attributes to results
            for (Iterator ris = roleAttrs.keySet().iterator(); ris.hasNext();) {
                Object roleAttrName = ris.next();
                Object roleAttrValues = (Object) roleAttrs.get(roleAttrName);
                Object idAttrValues = (Object) results.get(roleAttrName);
                if (idAttrValues == null) {
                    results.put(roleAttrName, roleAttrValues);
                } else {
                    if (isString) {
                        ((Set) idAttrValues).addAll((Set) roleAttrValues);
                    } else {
                        byte[][] resultsArr =
                            (byte[][])results.get(roleAttrName);
                        byte[][] roleArr =
                            (byte[][])roleAttrs.get(roleAttrName);
                        resultsArr = combineByteArray(resultsArr, roleArr);
                        results.put(roleAttrName, resultsArr);
                    }
                }
            }
        }

        // Get the service attributes for the realm and add it
        Map realmAttrs = (isString ?
            getAttributes(token, IdType.REALM,
                "ContainerDefaultTemplateRole", attrNames)
            : getBinaryAttributes(token, IdType.REALM,
                "ContainerDefaultTemplateRole", attrNames));
        // Add the attributes to results
        for (Iterator ris = realmAttrs.keySet().iterator(); ris.hasNext();) {
            Object realmAttrName = ris.next();
            Object realmAttrValues = (Object) realmAttrs.get(realmAttrName);
            Object idAttrValues = (Object) results.get(realmAttrName);
            if (idAttrValues == null) {
                results.put(realmAttrName, realmAttrValues);
            } else {
                // combine the values
                if (isString) {
                    ((Set) idAttrValues).addAll((Set) realmAttrValues);
                } else {
                    byte[][] resultsArr = (byte[][]) results.get(realmAttrName);
                    byte[][] realmArr = (byte[][]) realmAttrs.get(realmAttrName);
                    resultsArr = combineByteArray(resultsArr, realmArr);
                    results.put(realmAttrName, resultsArr);
                }
            }
        }
        return (results);
    }

    private byte[][] combineByteArray(byte[][] resultsArr, byte[][] realmArr) {
        int combinedSize = realmArr.length;
        if (resultsArr != null) {
            combinedSize = resultsArr.length + realmArr.length;
            byte[][] tmpSet = new byte[combinedSize][];
            for (int i = 0; i < resultsArr.length; i++) {
                tmpSet[i] = (byte[]) resultsArr[i];
            }
            for (int i = 0; i < realmArr.length; i++) {
                tmpSet[i] = (byte[]) realmArr[i];
            }
            resultsArr = tmpSet;
        } else {
            resultsArr = (byte[][]) realmArr.clone();
        }
        return(resultsArr);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#isExists(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String)
     */
    public boolean isExists(SSOToken token, IdType type, String name)
            throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }

        boolean entryExists = true;
        try {
            getAttributes(token, type, name);
        } catch (FilesRepoEntryNotFoundException fe) {
            entryExists = false;
        }

        return entryExists;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#modifyMemberShip(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.util.Set, com.sun.identity.idm.IdType, int)
     */
    public void modifyMemberShip(SSOToken token, IdType type, String name,
            Set members, IdType membersType, int operation)
            throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.modifyMemberShip called. " + type +
                    "; name= " + name + "; members= " + members +
                    "; membersType= " + membersType + "; operation= " + operation);
        }
        if (initializationException != null) {
            debug.error(
               "FilesRepo.modifyMemberShip: throwing initialization exception");
            throw (initializationException);
        }
        if ((members == null) || members.isEmpty()) {
            debug.message("FilesRepo.modifyMemberShip: Members set is empty");
            throw new IdRepoException(IdRepoBundle.getString(IdRepoErrorCode.ILLEGAL_ARGUMENTS),
                    IdRepoErrorCode.ILLEGAL_ARGUMENTS);
        }
        if (type.equals(IdType.USER) || type.equals(IdType.AGENT)) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo.modifyMemberShip: "
                    + "Membership to users and agents is not supported");
            }
            throw new IdRepoException(IdRepoBundle.getString(IdRepoErrorCode.MEMBERSHIP_TO_USERS_AND_AGENTS_NOT_ALLOWED),
                    IdRepoErrorCode.MEMBERSHIP_TO_USERS_AND_AGENTS_NOT_ALLOWED);
        }
        if (!membersType.equals(IdType.USER) &&
            !membersType.equals(IdType.AGENT)
        ) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo.modifyMemberShip: A non user/agent " +
                    "type cannot  be made a member of any identity" +
                    membersType.getName());
            }
            Object[] args = { NAME };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.MEMBERSHIPS_FOR_NOT_USERS_NOT_ALLOWED, args);
        }
        if (type.equals(IdType.GROUP)) {
            // add the identities to the user's group membership
            File file = constructFile(directory, type, name);
            Map attrs = decodeAttributes(readFile(file), encryptAttributes);
            Set gmembers = (Set) attrs.get(groupMembersAttribute);
            if (gmembers == null) {
                gmembers = new HashSet();
                attrs.put(groupMembersAttribute, gmembers);
            }
            for (Iterator items = members.iterator(); items.hasNext();) {
                String item = membersType.getName() + items.next().toString();
                if (operation == ADDMEMBER) {
                    gmembers.add(item);
                } else {
                    gmembers.remove(item);
                }
            }
            // Save the results
            setAttributes(token, type, name, attrs, false);
        } else if (type.equals(IdType.ROLE)) {
            // add to user/agents's role attribute
            Set returnAttrs = new HashSet();
            returnAttrs.add(roleMembershipAttribute);
            for (Iterator items = members.iterator(); items.hasNext();) {
                String sname = (String) items.next();
                // Get role attribute
                Map roles = getAttributes(token, membersType, sname,
                        returnAttrs);
                if (roles == null) {
                    roles = new HashMap();
                }
                Set sroles = (Set) roles.get(roleMembershipAttribute);
                if (sroles == null) {
                    sroles = new HashSet();
                    roles.put(roleMembershipAttribute, sroles);
                }
                if (operation == ADDMEMBER) {
                    sroles.add(name);
                } else {
                    sroles.remove(name);
                }
                // Save the attribute
                setAttributes(token, membersType, sname, roles, false);
            }
        } else {
            Object[] args = { NAME, type.getName() };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.MEMBERSHIP_CANNOT_BE_MODIFIED, args);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#modifyService(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.lang.String,
     *      com.sun.identity.sm.SchemaType, java.util.Map)
     */
    public void modifyService(SSOToken token, IdType type, String name,
            String serviceName, SchemaType sType, Map attrMap)
            throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.modifyService called: " + name + "\n\t" +
                serviceName + "=" + attrMap + "\n\tSchema=" + sType);
        }
        if (initializationException != null) {
            debug.error(
                "FilesRepo.modifyService: throwing initialization exception");
            throw (initializationException);
        }

        if (type.equals(IdType.USER) || type.equals(IdType.ROLE) ||
            type.equals(IdType.REALM)
        ) {
            // Set the attributes
            assignService(token, type, name, serviceName, sType, attrMap);
        } else {
            Object args[] = { NAME, IdOperation.SERVICE.getName() };
            throw new IdRepoUnsupportedOpException(IdRepoBundle.BUNDLE_NAME,
                    "305", args);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#removeAttributes(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.util.Set)
     */
    public void removeAttributes(SSOToken token, IdType type, String name,
            Set attrNames) throws IdRepoException, SSOException {
        Map answer = getAttributes(token, type, name);
        for (Iterator items = attrNames.iterator(); items.hasNext();) {
            answer.remove(items.next());
        }
        File file = constructFile(directory, type, name);
        writeFile(file, processAttributes(answer, Collections.EMPTY_SET,
                encryptAttributes));
        // %%% Send notification (must be via a different thread)
        if (repoListener != null) {
            repoListener.objectChanged(name, type, AMEvent.OBJECT_CHANGED,
                    repoListener.getConfigMap());
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#removeListener()
     */
    public void removeListener() {
        repoListener = null;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#search(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, int, int,
     *      java.util.Set, boolean, int, java.util.Map, boolean)
     */
    public RepoSearchResults search(SSOToken token, IdType type, CrestQuery crestQuery, int maxTime,
                                    int maxResults, Set returnAttrs, boolean returnAllAttrs, int filterOp,
                                    Map avPairs, boolean recursive) throws IdRepoException, SSOException {

        if (crestQuery.hasQueryId()) {
            return search(token, type, crestQuery.getQueryId(), maxTime, maxResults, returnAttrs,
                                 returnAllAttrs, filterOp, avPairs, recursive);
        }
        throw new IdRepoException("FilesRepo.search does not support queryFilter searches");
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#search(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, int, int,
     *      java.util.Set, boolean, int, java.util.Map, boolean)
     */
    public RepoSearchResults search(SSOToken token, IdType type,
            String pattern, int maxTime, int maxResults, Set returnAttrs,
            boolean returnAllAttrs, int filterOp, Map avPairs,
            boolean recursive) throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo.search: throwing initialization exception");
            throw (initializationException);
        }

        if (debug.messageEnabled()) {
            debug.message("FilesRepo:search pattern=" + pattern +
                " type=" + type + " returnAttrs=" + returnAttrs +
                " filter= " + filterOp + " matchAttrs= " + avPairs);
        }

        // Directory to start the search
        File dir = new File(new File(directory), type.getName());
        String[] files = dir.list(new FileRepoFileFilter(pattern));
        if (files.length == 0) {
            return new RepoSearchResults(Collections.EMPTY_SET,
                    RepoSearchResults.SUCCESS, Collections.EMPTY_MAP, type);
        }

        // Check if attribute mapping has to be done
        Set results = new HashSet();
        if (avPairs != null && !avPairs.isEmpty()) {
            for (int i = 0; i < files.length; i++) {
                // Check if the attributes match
                Map allAttrs = getAttributes(token, type, files[i]);
                Set attrNames = new CaseInsensitiveHashSet();
                attrNames.addAll(allAttrs.keySet());
                boolean addResult = (filterOp == IdRepo.AND_MOD);
                for (Iterator items = avPairs.keySet().iterator();
                    items.hasNext();
                ) {
                    String attrName = (String)items.next();
                    Set attrValue = (Set)avPairs.get(attrName);
                    if ((attrValue == null) || attrValue.isEmpty() ||
                        attrValue.contains("*")
                    ) {
                        // Check if the attribute is present
                        if (attrNames.contains(attrName)) {
                            if (filterOp == IdRepo.OR_MOD) {
                                addResult = true;
                                break;
                            }
                        } else if (filterOp == IdRepo.AND_MOD) {
                            addResult = false;
                            break;
                        }
                    } else {
                        // Check if the values are present
                        Set matchValues = (Set) allAttrs.get(attrName);
                        if (matchValues != null &&
                                containsAttrValue (matchValues, attrValue)) {
                            if (filterOp == IdRepo.OR_MOD) {
                                addResult = true;
                                break;
                            }
                        } else if (filterOp == IdRepo.AND_MOD) {
                            addResult = false;
                            break;
                        }
                    }
                }
                if (addResult) {
                    results.add(files[i]);
                }
            }
        } else {
            results.addAll(Arrays.asList(files));
        }

        // Build RepoSearchResults
        Map resultsWithAttrs = new HashMap();
        for (Iterator items = results.iterator(); items.hasNext();) {
            String item = (String) items.next();
            if (returnAllAttrs || returnAttrs == null) {
                resultsWithAttrs.put(item, getAttributes(token, type, item));
            } else if (returnAttrs.isEmpty()) {
                resultsWithAttrs.put(item, Collections.EMPTY_MAP);
            } else {
                resultsWithAttrs.put(item, getAttributes(token, type, item,
                    returnAttrs));
            }
        }
        if (debug.messageEnabled()) {
            debug.message("FilesRepo:search results: " + results);
        }
        return (new RepoSearchResults(results, RepoSearchResults.SUCCESS,
                resultsWithAttrs, type));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#search(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.util.Map,
     *      boolean, int, int, java.util.Set)
     */
    public RepoSearchResults search(
        SSOToken token,
        IdType type,
        String pattern,
        Map avPairs,
        boolean recursive,
        int maxResults,
        int maxTime,
        Set returnAttrs
    ) throws IdRepoException, SSOException {
        return (search(token, type, pattern, maxTime, maxResults, returnAttrs,
                (returnAttrs == null), OR_MOD, avPairs, recursive));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#setAttributes(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String, java.util.Map,
     *      boolean)
     */
    public void setAttributes(
        SSOToken token,
        IdType type,
        String name,
        Map attributes,
        boolean isAdd
    ) throws IdRepoException, SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.setAttributes for " + type + " " + name +
                "\n\tAttributes=" + attributes.keySet() + " isAdd=" + isAdd);
        }
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }

        Map answer = getAttributes(token, type, name);
        if (answer == Collections.EMPTY_MAP) {
            answer = new HashMap();
        }

        // Hash the new attributes, if any
        attributes = processAttributes(attributes, hashAttributes,
                Collections.EMPTY_SET);
        if (!isAdd) {
            answer.putAll(attributes);
        } else {
            for (Iterator items = attributes.keySet().iterator();
                items.hasNext();
            ) {
                Object key = items.next();
                Set value = (Set) answer.get(key);
                if (value != null) {
                    value.addAll((Set) attributes.get(key));
                } else {
                    value = (Set) attributes.get(key);
                }
                answer.put(key, value);
            }
        }
        File file = constructFile(directory, type, name);
        writeFile(file, processAttributes(answer, Collections.EMPTY_SET,
                encryptAttributes));
        // %%% Send notification (must be via a different thread)
        if (repoListener != null) {
            repoListener.objectChanged(name, type, AMEvent.OBJECT_CHANGED,
                    repoListener.getConfigMap());
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#unassignService(
     *      com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
     *      java.lang.String, java.lang.String, java.util.Map)
     */
    public void unassignService(SSOToken token, IdType type, String name,
            String serviceName, Map attrMap) throws IdRepoException,
            SSOException {
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.unassignService called: " + name +
                "\n\t" + serviceName + "=" + attrMap);
        }
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }

        // Need to selectively remove the objectclassess
        // First get the objectclasses
        Set set = new HashSet();
        set.add(OC);
        Map attrs = getAttributes(token, type, name, set);
        Set objectclasses = (Set) attrs.get(OC);
        CaseInsensitiveHashMap sAttrs = new CaseInsensitiveHashMap();
        sAttrs.putAll(attrMap);
        Set serviceOCs = (Set) sAttrs.remove(OC);
        if ((objectclasses != null) && !objectclasses.isEmpty() &&
            (serviceOCs != null)
        ) {
            objectclasses.removeAll(serviceOCs);
            setAttributes(token, type, name, attrs, false);
        }
        removeAttributes(token, type, name, sAttrs.keySet());
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getSupportedOperations(
     *      com.sun.identity.idm.IdType)
     */
    public Set getSupportedOperations(IdType type) {
        return (Set) supportedOps.get(type);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#getSupportedTypes()
     */
    public Set getSupportedTypes() {
        return supportedOps.keySet();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sun.identity.idm.IdRepo#isActive(com.iplanet.sso.SSOToken,
     *      com.sun.identity.idm.IdType, java.lang.String)
     */
    public boolean isActive(SSOToken token, IdType type, String name)
            throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }
        Map attributes = getAttributes(token, type, name);
        if (attributes == null) {
            Object[] args = { NAME, name };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NOT_VALID_ENTRY, args);
        }
        Set activeVals = (Set) attributes.get(statusAttribute);
        if (activeVals == null || activeVals.isEmpty()) {
            return true;
        } else {
            Iterator it = activeVals.iterator();
            String active = (String) it.next();
            return (active.equalsIgnoreCase(statusActive) ? true : false);
        }
    }

    /* (non-Javadoc)
     * @see com.sun.identity.idm.IdRepo#setActiveStatus(
        com.iplanet.sso.SSOToken, com.sun.identity.idm.IdType,
        java.lang.String, boolean)
     */
    public void setActiveStatus(SSOToken token, IdType type,
        String name, boolean active)
        throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }
        Map attrs = new HashMap();
        Set vals = new HashSet();
        if (active) {
            vals.add(statusActive);
        } else {
            vals.add(statusInactive);
        }
        attrs.put(statusAttribute, vals);
        setAttributes(token, type, name, attrs, false);
    }

    private static void loadSupportedOps() {
        Set opSet = new HashSet();
        opSet.add(IdOperation.EDIT);
        opSet.add(IdOperation.READ);
        opSet.add(IdOperation.CREATE);
        opSet.add(IdOperation.DELETE);
        supportedOps.put(IdType.GROUP, Collections.unmodifiableSet(opSet));
        supportedOps.put(IdType.AGENT, Collections.unmodifiableSet(opSet));
        Set nopSet = new HashSet(opSet);
        nopSet.add(IdOperation.SERVICE);
        supportedOps.put(IdType.USER, Collections.unmodifiableSet(nopSet));
        supportedOps.put(IdType.ROLE, Collections.unmodifiableSet(nopSet));
        supportedOps.put(IdType.REALM, Collections.unmodifiableSet(nopSet));
        if (debug.messageEnabled()) {
            debug.message("FilesRepo.loadSupportedOps called" +
                "\n\tsupportedOps=" + supportedOps.toString());
        }
    }

    public String getFullyQualifiedName(SSOToken token, IdType type,
            String name) throws IdRepoException, SSOException {
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }
        RepoSearchResults results = search(token, type, name, null, true, 0, 0,
                null);
        Set dns = results.getSearchResults();
        if (dns != null || dns.size() != 1) {
            String[] args = { NAME, name };
            throw (new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_FIND_ENTRY, args));
        }
        return ("files://FilesRepo/" + type.getName() + "/" +
            dns.iterator().next().toString());
    }

    public boolean supportsAuthentication() {
        return (true);
    }

    public boolean authenticate(Callback[] credentials) throws IdRepoException,
            AuthLoginException {
        debug.message("FilesRepo:authenticate called");
        if (initializationException != null) {
            debug.error("FilesRepo: throwing initialization exception");
            throw (initializationException);
        }

        // Obtain user name and password from credentials and authenticate
        String username = null;
        String password = null;
        for (int i = 0; i < credentials.length; i++) {
            if (credentials[i] instanceof NameCallback) {
                username = ((NameCallback) credentials[i]).getName();
                if (debug.messageEnabled()) {
                    debug.message("FilesRepo:authenticate username: " +
                        username);
                }
            } else if (credentials[i] instanceof PasswordCallback) {
                char[] passwd =((PasswordCallback)credentials[i]).getPassword();
                if (passwd != null) {
                    password = new String(passwd);
                    debug.message("FilesRepo:authN passwd present");
                }
            }
        }
        if (username == null || password == null) {
            return (false);
        }

        // Get user's password attribute
        Map attrs = searchForAuthN(IdType.USER, username);

        if (attrs == null) {
            // Try agent
            attrs = searchForAuthN(IdType.AGENT, username);
        }

        if ((attrs == null) || attrs.isEmpty() ||
            !attrs.containsKey(passwordAttribute)
        ) {
            // Could not find user or agent, return false
            debug.message("FilesRepo:authenticate did not found user/agent");
            return (false);
        }
        Set storedPasswords = (Set) attrs.get(passwordAttribute);
        if (storedPasswords == null || storedPasswords.isEmpty()) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo:authenticate no stored password");
            }
            return (false);
        }
        String storedPassword = (String) storedPasswords.iterator().next();
        if (hashAttributes.contains(passwordAttribute)) {
            password = Hash.hash(password);
        }
        if (debug.messageEnabled()) {
            debug.message("FilesRepo:authenticate AuthN of " + username + "=" +
                password.equals(storedPassword));
        }
        return (password.equals(storedPassword));
    }

    private Map searchForAuthN(IdType type, String userName)
        throws IdRepoException
    {
        Map attributes = null;
        try {
            attributes = getAttributes(null, type, userName);
            if (debug.messageEnabled()) {
                debug.message("FilesRepo:searchForAuthN found " +
                        type.getName() + " entry: " + userName);
            }
        } catch (FilesRepoEntryNotFoundException fe) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo:searchForAuthn did not find " +
                        type.getName() + " entry: " + userName);
            }
        } catch (SSOException ssoe) {
            // Can ignore this as this won't happen. No token was passed.
        }

        return attributes;
    }

    // -----------------------------------------------
    // private methods to manage directory structure
    // -----------------------------------------------
    // Methods for cache management

    // Initialize, read and write methods
    void initDir(String rootDir) throws IdRepoException {
        // Check if roor dir exists, if not create
        File root = new File(rootDir);
        if (!root.exists() && !root.mkdirs()) {
            // Unable to create the directory
            Object args[] = { root.getAbsolutePath() };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_CREATE_DIRECTORY, args);
        } else if (!root.isDirectory()) {
            // Not a directory
            Object args[] = { root.getAbsolutePath() };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NOT_DIRECTORY, args);
        }
        // Check sub-directories
        Set types = supportedOps.keySet();
        for (Iterator items = types.iterator(); items.hasNext();) {
            String subDir = ((IdType) items.next()).getName();
            File dir = new File(root, subDir);
            if (!dir.exists() && !dir.mkdir()) {
                // Unable to create the directory
                String args[] = { dir.getAbsolutePath() };
                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_CREATE_DIRECTORY,
                    args);
            } else if (!dir.isDirectory()) {
                // Not a directory
                String args[] = { dir.getAbsolutePath() };
                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NOT_DIRECTORY,
                    args);
            }
            if (subDir.equals(IdType.REALM.getName())) {
                // Create realm ContainerDefaultTemplateRole
                File role = new File(dir, "ContainerDefaultTemplateRole");
                if (!role.exists()) {
                    // Write an empyt map to the file
                    writeFile(role, Collections.EMPTY_MAP);
                }
            }
        }

    }

    File constructFile(String rootDir, IdType type, String name) {
        // Construct file name
        File root = new File(rootDir);
        File subDir = new File(root, type.getName());
        return (new File(subDir, name));
    }

    void writeFile(File file, Map values) throws IdRepoException {
        // Convert Map to AttributeValuePairs and write to file
        PrintWriter pw = null;
        String fileName = file.getAbsolutePath();
        try {
            SOAPClient client = new SOAPClient();
            String encodedMap = client.encodeMap("result", values);
            pw = new PrintWriter(new FileOutputStream(fileName));
            pw.println(encodedMap);
            // update the cache
            identityCache.put(fileName, values);
            identityTimeCache.put(fileName, new Long(file.lastModified()));
            identityCache.put(fileName.toLowerCase(), values);
            if (debug.messageEnabled()) {
                debug.message("FilesRepo:writeFile-Identity Cache "+
                    identityCache);
            }
        } catch (IOException e) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo.writeFile: file = " + fileName, e);
            }
            String[] args = { NAME, fileName };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_FIND_ENTRY, args);
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }

    Map readFile(File file) throws IdRepoException {
        Map answer = Collections.EMPTY_MAP;
        String fileName = file.getAbsolutePath();
        // Check in cache & if time is curent
        Long lastModified = (Long) identityTimeCache.get(fileName);
        if ((lastModified != null) &&
            (lastModified.longValue() == file.lastModified()) &&
            ((answer = (Map) identityCache.get(fileName)) != null)) {
            return (answer);
        }
        for (Iterator it = identityCache.keySet().iterator(); it.hasNext();) {
            String origFileName = (String) it.next();
            // At times the incoming object name is all normalized to
            // lowercase. This avoids the filenotfound exception when the
            // object in flatfile repository is saved as mixed case filenames.
            if (!fileName.equals(origFileName)) {
                if (fileName.equalsIgnoreCase(origFileName)) {
                    fileName = origFileName;
                    break;
                }
            } else {
                break;
            }
        }
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(fileName));
            StringBuilder encodedMapBuffer = new StringBuilder(200);
            String line;
            while ((line = br.readLine()) != null) {
                encodedMapBuffer.append(line);
            }
            String encodedMap = encodedMapBuffer.toString();
            SOAPClient client = new SOAPClient();
            Map map = client.decodeMap(encodedMap);
            // Convert HashMap to CaseInsensitiveHashMap
            answer = new CaseInsensitiveHashMap();
            for (Iterator items = map.keySet().iterator(); items.hasNext();) {
                Object key = items.next();
                Set ovalue = (Set) map.get(key);
                Set nvalue = new CaseInsensitiveHashSet();
                nvalue.addAll(ovalue);
                answer.put(key, nvalue);
            }
            // Add to cache
            identityTimeCache.put(fileName, new Long(file.lastModified()));
            identityCache.put(fileName, answer);
        } catch (FileNotFoundException fn) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo.readFile: file not found: " +fileName);
            }
            String[] args = { NAME, fileName };
            throw new FilesRepoEntryNotFoundException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.UNABLE_FIND_ENTRY, args);
        } catch (IOException e) {
            if (debug.messageEnabled()) {
                debug.message("FilesRepo.readFile: error reading file: " +
                    fileName, e);
            }
            String[] args = { NAME, fileName };
            throw (new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.UNABLE_FIND_ENTRY, args));
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    if (debug.warningEnabled()) {
                        debug.warning(
                            "FilesRepo.redFile: read error: " + fileName, e);
                    }
                }
            }
        }
        return (answer);
    }

    public static void clearCache() {
        identityCache = new CaseInsensitiveHashMap();
    }

    static Map processAttributes(Map attrs, Set hashAttrs, Set encAttrs) {
        // Convert to CaseInsensitiveHashMap
        Map answer = new CaseInsensitiveHashMap();
        for (Iterator items = attrs.keySet().iterator(); items.hasNext();) {
            Object key = items.next();
            Set ovalue = (Set) attrs.get(key);
            Set nvalue = new CaseInsensitiveHashSet();
            if (hashAttrs.contains(key)) {
                for (Iterator i = ovalue.iterator(); i.hasNext();) {
                    nvalue.add(Hash.hash((String) i.next()));
                }
            } else if (encAttrs.contains(key)) {
                try {
                    for (Iterator i = ovalue.iterator(); i.hasNext();) {
                        nvalue.add((String)AccessController.doPrivileged(
                            new EncodeAction((String)i.next())));
                    }
                } catch (Throwable e) {
                    // Printing the attribute value could be security issue
                    debug.error(
                        "FilesRepo.processAttributes: unable to encode", e);
                }
            } else {
                nvalue.addAll(ovalue);
            }
            answer.put(key, nvalue);
        }
        return (answer);
    }

    static Map decodeAttributes(Map attrs, Set encAttrs) {
        if (encAttrs.isEmpty()) {
            return (attrs);
        }
        // Decode the attributes
        for (Iterator items = encAttrs.iterator(); items.hasNext();) {
            Object key = items.next();
            Set ovalue = (Set) attrs.get(key);
            if (ovalue != null && !ovalue.isEmpty()) {
                Set nvalue = new CaseInsensitiveHashSet();
                for (Iterator i = ovalue.iterator(); i.hasNext();) {
                    try {
                        nvalue.add((String)AccessController.doPrivileged(
                            new DecodeAction((String)i.next())));
                    } catch (Throwable e) {
                        // Printing the attribute value could be security issue
                        debug.error("FilesRepo: unable to decode", e);
                    }
                }
                attrs.put(key, nvalue);
            }
        }
        return (attrs);
    }

    // Method to compare wild card attribute values
    boolean containsAttrValue(Set attrValues, Set patterns) {
        for (Iterator ps = patterns.iterator(); ps.hasNext();) {
             String pattern = ps.next().toString();
            if (pattern.indexOf('*') != -1) {
                FileRepoFileFilter ff = new FileRepoFileFilter(pattern);
                for (Iterator items = attrValues.iterator();
                    items.hasNext();) {
                    if (ff.accept(null, items.next().toString())) {
                        return (true);
                    }
                }
            } else if (attrValues.contains(pattern)) {
                return (true);
            }
        }
        return (false);
    }

    // File name filter inner class
    class FileRepoFileFilter implements FilenameFilter {
        // Pattern to match
        Pattern pattern;

        // Default constructor
        FileRepoFileFilter(String p) {
            if (p != null && p.length() != 0 && !p.equals("*")) {
                // Replace "*" with ".*"
                int idx = p.indexOf('*');
                while (idx != -1) {
                    p = p.substring(0, idx) + ".*" + p.substring(idx + 1);
                    idx = p.indexOf('*', idx + 2);
                }
                pattern = Pattern.compile(p.toLowerCase());
            }
        }

        public boolean accept(File dir, String name) {
            if (pattern == null) {
                return (true);
            } else {
                return (pattern.matcher(name.toLowerCase()).matches());
            }
        }
    }

    // Cache update thread
    class CacheUpdateRunnable extends GeneralTaskRunnable {
        FilesRepo repo;

        CacheUpdateRunnable(FilesRepo r) {
            repo = r;
            debug.message("CacheUpdateRunnable initialized");
        }

        public void run() {
            Set fileNames = new HashSet(repo.identityTimeCache.keySet());
            for (Iterator i = fileNames.iterator(); i.hasNext();) {
                String fileName = i.next().toString();
                Long lastModified =
                    (Long) repo.identityTimeCache.get(fileName);
                File file = new File(fileName);
                if (!file.exists() || (lastModified.longValue() !=
                    file.lastModified())) {
                    if (debug.messageEnabled()) {
                        debug.message("CacheUpdateRunnable: " +
                            "File modified: " + fileName);
                    }
                    identityCache.remove(fileName);
                    repo.identityTimeCache.remove(fileName);

                    // send notficiation to supported type.
                    Set supportedTypes = repo.getSupportedTypes();
                    Iterator supTypeIter = supportedTypes.iterator();
                    while (supTypeIter.hasNext()) {
                        IdType idType = (IdType) supTypeIter.next();
                        // Send notification
                        if (file.exists()) {
                            repo.repoListener.objectChanged(file.getName(),
                                idType, AMEvent.OBJECT_CHANGED,
                                repo.repoListener.getConfigMap());
                        } else {
                            repo.repoListener.objectChanged(file.getName(),
                                idType, AMEvent.OBJECT_REMOVED,
                                repo.repoListener.getConfigMap());
                        }
                    }
                    if (debug.messageEnabled()) {
                        debug.message("CacheUpdateRunnable: " +
                            "Notification Sent: " + fileName);
                    }
                }
            }
        }

        public boolean addElement(Object key) {
            return false;
        }

        public boolean removeElement(Object key) {
            return false;
        }

        public boolean isEmpty() {
            return false;
        }

        public long getRunPeriod() {
            return repo.updateCacheInterval * 60 * 1000;
        }

    }
}
