
/*
 * @(#)BasicTest.java
 *
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistribution of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *   2. Redistribution in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

package com.sun.xacml.test;

import com.sun.xacml.PDP;

import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;

import java.io.FileInputStream;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * A simple implementation of a single conformance test case.
 *
 * @author Seth Proctor
 */
public class BasicTest implements Test
{

    // the PDP and module that manage this test's policies
    private PDP pdp;
    private TestPolicyFinderModule module;

    // the policies and references used by this test
    private Set policies;
    private Map policyRefs;
    private Map policySetRefs;

    // meta-data associated with this test
    private String name;
    private boolean errorExpected;
    private boolean experimental;

    /**
     * Constructor that accepts all values associatd with a test.
     *
     * @param pdp the pdp that manages this test's evaluations
     * @param module the module that manages this test's policies
     * @param policies the files containing the policies used by this test,
     *                 or null if we only use the default policy for this test
     * @param policyRefs the policy references used by this test
     * @param policySetRefs the policy set references used by this test
     * @param name the name of this test
     * @param errorExpected true if en error is expected from a normal run
     * @param experimental true if this is an experimental test
     */
    public BasicTest(PDP pdp, TestPolicyFinderModule module, Set policies,
                     Map policyRefs, Map policySetRefs, String name,
                     boolean errorExpected, boolean experimental) {
        this.pdp = pdp;
        this.module = module;
        this.policies = policies;
        this.policyRefs = policyRefs;
        this.policySetRefs = policySetRefs;
        this.name = name;
        this.errorExpected = errorExpected;
        this.experimental = experimental;
    }

    /**
     * Creates an instance of a test from its XML representation.
     *
     * @param root the root of the XML-encoded data for this test
     * @param pdp the <code>PDP</code> used by this test
     * @param module the module used for this test's policies
     */
    public static BasicTest getInstance(Node root, PDP pdp,
                                        TestPolicyFinderModule module) {
        NamedNodeMap map = root.getAttributes();
        
        // the name is required...
        String name = map.getNamedItem("name").getNodeValue();

        // ...but the other two aren't
        boolean errorExpected = isAttrTrue(map, "errorExpected");
        boolean experimental = isAttrTrue(map, "experimental");

        // see if there's any content
        Set policies = null;
        Map policyRefs = null;
        Map policySetRefs = null;
        if (root.hasChildNodes()) {
            NodeList children = root.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                String childName = child.getNodeName();
                
                if (childName.equals("policy")) {
                    if (policies == null)
                        policies = new HashSet();
                    policies.add(child.getFirstChild().getNodeValue());
                } else if (childName.equals("policyReference")) {
                    if (policyRefs == null)
                        policyRefs = new HashMap();
                    policyRefs.put(child.getAttributes().getNamedItem("ref").
                                   getNodeValue(),
                                   child.getFirstChild().getNodeValue());
                } else if (childName.equals("policySetReference")) {
                    if (policySetRefs == null)
                        policySetRefs = new HashMap();
                    policySetRefs.put(child.getAttributes().
                                      getNamedItem("ref").getNodeValue(),
                                      child.getFirstChild().getNodeValue());
                }
            }
        }

        return new BasicTest(pdp, module, policies, policyRefs, policySetRefs,
                             name, errorExpected, experimental);
    }

    /**
     * Private helper that reads a attribute to see if it's set, and if so
     * if its value is "true".
     */
    private static boolean isAttrTrue(NamedNodeMap map, String attrName) {
        Node attrNode = map.getNamedItem(attrName);

        if (attrNode == null)
            return false;

        return attrNode.getNodeValue().equals("true");
    }

    public String getName() {
        return name;
    }

    public boolean isErrorExpected() {
        return errorExpected;
    }

    public boolean isExperimental() {
        return experimental;
    }

    public int run(String testPrefix) {
        System.out.print("test " + name + ": ");
        int errorCount = 0;
        boolean failurePointReached = false;

        // FIXME: we sould get more specific with the exceptions, so we can
        // make sure that an error happened _for the right reason_

        try {
            // load the request for this test
            RequestCtx request =
                RequestCtx.getInstance(new FileInputStream(testPrefix + name +
                                                           "Request.xml"));
            
            // re-set the module to use this test's policies
            if (policies == null) {
                module.setPolicies(testPrefix + name + "Policy.xml");
            } else {
                Iterator it = policies.iterator();
                Set set = new HashSet();

                while (it.hasNext())
                    set.add(testPrefix + (String)(it.next()));

                module.setPolicies(set);
            }

            // re-set any references we're using
            module.setPolicyRefs(policyRefs, testPrefix);
            module.setPolicySetRefs(policySetRefs, testPrefix);

            // actually do the evaluation
            ResponseCtx response = pdp.evaluate(request);

            // if we're supposed to fail, we should have done so by now
            if (errorExpected) {
                System.out.println("failed");
                errorCount++;
            } else {
                failurePointReached = true;

                // load the reponse that we expectd to get
                ResponseCtx expectedResponse =
                    ResponseCtx.
                    getInstance(new FileInputStream(testPrefix + name +
                                                    "Response.xml"));

                // see if the actual result matches the expected result
                boolean equiv = TestUtil.areEquivalent(response,
                                                       expectedResponse);
                
                if (equiv) {
                    System.out.println("passed");
                } else {
                    System.out.println("failed:");
                    response.encode(System.out);
                    errorCount++;
                }
            }
        } catch (Exception e) {
            // any errors happen as exceptions, and may be successes if we're
            // supposed to fail and we haven't reached the failure point yet
            if (! failurePointReached) {
                if (errorExpected) {
                    System.out.println("passed");
                } else {
                    System.out.println("EXCEPTION: " + e.getMessage());
                    errorCount++;
                }
            } else {
                System.out.println("UNEXPECTED EXCEPTION: " + e.getMessage());
                errorCount++;
            }
        }
        
        // return the number of errors that occured
        return errorCount;
    }

}
