/*
 * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
 * Distributed under the terms of either:
 * - the common development and distribution license (CDDL), v1.0; or
 * - the GNU Lesser General Public License, v2.1 or later
 */
package winstone.realm;

import org.eclipse.jetty.security.HashLoginService;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import winstone.Logger;
import winstone.WinstoneException;
import winstone.WinstoneResourceBundle;
import winstone.cmdline.Option;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import static org.eclipse.jetty.util.security.Credential.*;

/**
 * @author rickk
 * @version $Id: FileRealm.java,v 1.4 2006/08/30 04:07:52 rickknowles Exp $
 */
public class FileRealm extends HashLoginService {
    private static final WinstoneResourceBundle REALM_RESOURCES = new WinstoneResourceBundle("winstone.realm.LocalStrings");
    
    final String DEFAULT_FILE_NAME = "users.xml";
    final String ELEM_USER = "user";
    final String ATT_USERNAME = "username";
    final String ATT_PASSWORD = "password";
    final String ATT_ROLELIST = "roles";

    /**
     * Constructor - this sets up an authentication realm, using the file
     * supplied on the command line as a source of userNames/passwords/roles.
     */
    public FileRealm(Map args) {
        // Get the filename and parse the xml doc
        File realmFile = Option.FILEREALM_CONFIGFILE.get(args);
        if (realmFile==null)    realmFile = new File(DEFAULT_FILE_NAME);
        if (!realmFile.exists())
            throw new WinstoneException(REALM_RESOURCES.getString(
                    "FileRealm.FileNotFound", realmFile.getPath()));
        try {
            int count=0;
            InputStream inFile = new FileInputStream(realmFile);
            Document doc = this.parseStreamToXML(inFile);
            inFile.close();
            Node rootElm = doc.getDocumentElement();
            for (int n = 0; n < rootElm.getChildNodes().getLength(); n++) {
                Node child = rootElm.getChildNodes().item(n);

                if ((child.getNodeType() == Node.ELEMENT_NODE)
                        && (child.getNodeName().equals(ELEM_USER))) {
                    String userName = null;
                    String password = null;
                    String roleList = null;
                    // Loop through for attributes
                    for (int j = 0; j < child.getAttributes().getLength(); j++) {
                        Node thisAtt = child.getAttributes().item(j);
                        if (thisAtt.getNodeName().equals(ATT_USERNAME))
                            userName = thisAtt.getNodeValue();
                        else if (thisAtt.getNodeName().equals(ATT_PASSWORD))
                            password = thisAtt.getNodeValue();
                        else if (thisAtt.getNodeName().equals(ATT_ROLELIST))
                            roleList = thisAtt.getNodeValue();
                    }

                    if ((userName == null) || (password == null)
                            || (roleList == null))
                        Logger.log(Logger.FULL_DEBUG, REALM_RESOURCES,
                                "FileRealm.SkippingUser", userName);
                    else {
                        // Parse the role list into an array and sort it
                        StringTokenizer st = new StringTokenizer(roleList, ",");
                        List<String> rl = new ArrayList<String>();
                        for (; st.hasMoreTokens();) {
                            String currentRole = st.nextToken();
                            rl.add(currentRole);
                        }
                        String[] roleArray = rl.toArray(new String[rl.size()]);
                        Arrays.sort(roleArray);

                        putUser(userName, getCredential(password), roleArray);
                        count++;
                    }
                }
            }
            Logger.log(Logger.DEBUG, REALM_RESOURCES, "FileRealm.Initialised",
                    "" + count);
        } catch (java.io.IOException err) {
            throw new WinstoneException(REALM_RESOURCES
                    .getString("FileRealm.ErrorLoading"), err);
        }
    }

    /**
     * Get a parsed XML DOM from the given inputstream. Used to process the
     * web.xml application deployment descriptors.
     */
    private Document parseStreamToXML(InputStream in) {
        try {
            // Use JAXP to create a document builder
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setExpandEntityReferences(false);
            factory.setValidating(false);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setCoalescing(true);
            factory.setIgnoringElementContentWhitespace(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            return builder.parse(in);
        } catch (Throwable errParser) {
            throw new WinstoneException(REALM_RESOURCES
                    .getString("FileRealm.XMLParseError"), errParser);
        }
    }
}
