org.callimachusproject.rdfa.test.RDFaGenerationTest.java Source code

Java tutorial

Introduction

Here is the source code for org.callimachusproject.rdfa.test.RDFaGenerationTest.java

Source

/*
 * Portions Copyright (c) 2010-11 Talis Inc, Some Rights Reserved
 *
 * Licensed 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.callimachusproject.rdfa.test;

import static org.callimachusproject.rdfa.test.Utility.asDocument;
import static org.callimachusproject.rdfa.test.Utility.exportGraph;
import static org.callimachusproject.rdfa.test.Utility.loadRepository;
import static org.callimachusproject.rdfa.test.Utility.parseRDFa;
import static org.callimachusproject.rdfa.test.Utility.readDocument;
import static org.callimachusproject.rdfa.test.Utility.write;
import static org.junit.Assume.assumeTrue;
import static org.openrdf.query.QueryLanguage.SPARQL;
import info.aduna.text.StringUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.events.XMLEvent;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;

import org.apache.http.client.HttpClient;
import org.callimachusproject.client.HttpClientFactory;
import org.callimachusproject.engine.RDFaReader;
import org.callimachusproject.engine.Template;
import org.callimachusproject.engine.TemplateEngine;
import org.callimachusproject.engine.helpers.XMLEventList;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.impl.MapBindingSet;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.repository.util.RepositoryUtil;
import org.openrdf.sail.memory.MemoryStore;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * Dynamic Test Suite for the generation of XHTML+RDFa pages from RDFa templates.
 * 
 * @author Steve Battle
 * 
 */

// This is a parameterized test that runs over the test directory (test_dir)
public class RDFaGenerationTest extends TestCase {
    // location of the XSLT transform relative to the test directory
    private static final String TEMPLATE_FILE_SUFFIX = "-test.xhtml";
    private static final String SOURCE_FILE_SUFFIX = "-source.xhtml";
    private static final String EXPECTED_FILE_SUFFIX = ".xhtml";
    private static final String DATA_ATTRIBUTE_TEST_BASE = "http://example.org/test";

    private final HttpClient client = HttpClientFactory.getInstance().createHttpClient("http://example.com/");
    // private static properties defined in @BeforeClass setUp()
    private XMLInputFactory xmlInputFactory;
    private XPathFactory xPathFactory;
    private Repository sourceRepository;
    private Repository expectedRepository;
    private Repository actualRepository;

    // private static flags set in main()
    private static boolean verbose = false;
    private static boolean show_rdf = false;
    private static boolean show_sparql = false;
    private static boolean show_xml = false;
    private static boolean show_results = false;

    // this is the default test directory
    private static String test_dir = "RDFaGeneration/test-suite/";
    static {
        // override default test-dir with VM arg
        if (System.getProperty("dir") != null)
            test_dir = System.getProperty("dir");
    }

    // the default test set to run on the default test_dir (other tests are assumed disabled)
    //private static String test_set = "legacy construct fragment select data";
    private static String test_set = "select data";
    static {
        // override default test-dir with VM arg
        if (System.getProperty("test") != null)
            test_set = System.getProperty("test");
    }

    // object properties defined in @Before initialize() 
    private RepositoryConnection source;
    private RepositoryConnection expected;
    private RepositoryConnection actual;
    private String base;

    // properties defined by constructor
    // XHTML RDFa template used to derive SPARQL query 
    private File template;
    // expectedFile XHTML with embedded RDFa
    private File expectedFile;
    // source XHTML with embedded RDFa used as RDF data source
    private File sourceFile;

    public RDFaGenerationTest(String name) {
        super(name);
        this.template = new File(name.substring(name.indexOf('!') + 1));
        this.expectedFile = new File(template.getPath().replace(TEMPLATE_FILE_SUFFIX, EXPECTED_FILE_SUFFIX));
        this.sourceFile = new File(template.getPath().replace(TEMPLATE_FILE_SUFFIX, SOURCE_FILE_SUFFIX));
        if (!sourceFile.exists()) {
            sourceFile = expectedFile;
        }
    }

    XPathExpression conjoinXPaths(Document fragment, String base) throws Exception {
        String path = "//*";
        final Element e = fragment.getDocumentElement();
        if (e == null)
            return null;
        final XPathIterator conjunction = new XPathIterator(e, base);
        if (conjunction.hasNext()) {
            path += "[";
            boolean first = true;
            while (conjunction.hasNext()) {
                if (!first)
                    path += " and ";
                XPathExpression x = conjunction.next();
                String exp = x.toString();
                boolean positive = true;
                if (exp.startsWith("-")) {
                    positive = false;
                    exp = exp.substring(1);
                }
                // remove the initial '/'
                exp = exp.substring(1);
                if (positive)
                    path += exp;
                else
                    path += "not(" + exp + ")";
                first = false;
            }
            path += "]";
        }
        XPath xpath = xPathFactory.newXPath();
        // add namespace prefix resolver to the xpath based on the current element
        xpath.setNamespaceContext(new AbstractNamespaceContext() {
            public String getNamespaceURI(String prefix) {
                // for the empty prefix lookup the default namespace
                if (prefix.isEmpty())
                    return e.lookupNamespaceURI(null);
                for (int i = 0; i < conjunction.contexts.size(); i++) {
                    NamespaceContext c = conjunction.contexts.get(i);
                    String ns = c.getNamespaceURI(prefix);
                    if (ns != null)
                        return ns;
                }
                return null;
            }
        });
        final String exp = path;
        final XPathExpression compiled = xpath.compile(path);
        if (verbose)
            System.out.println(exp);

        return new XPathExpression() {
            public String evaluate(Object source) throws XPathExpressionException {
                return compiled.evaluate(source);
            }

            public String evaluate(InputSource source) throws XPathExpressionException {
                return compiled.evaluate(source);
            }

            public Object evaluate(Object source, QName returnType) throws XPathExpressionException {
                return compiled.evaluate(source, returnType);
            }

            public Object evaluate(InputSource source, QName returnType) throws XPathExpressionException {
                return compiled.evaluate(source, returnType);
            }

            public String toString() {
                return exp;
            }
        };
    }

    /* define dynamically generated parameters {{ template, expectedFile } ... } passed to the constructor
     * list test-cases by enumerating test files in the test directory 
     * A test file has the TEST_FILE_SIGNIFIER in the filename 
     * A test file serves as an RDFa template
     * A expectedFile filename has the TEST_FILE_SIGNIFIER removed 
     */
    public static TestSuite suite() throws URISyntaxException {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        URL path = cl.getResource(test_dir);
        File testDir = new File(path.toURI());
        if (testDir.exists() && testDir.isDirectory()) {
            TestSuite suite = listCases(testDir);
            suite.setName(RDFaGenerationTest.class.getName());
            return suite;
        }
        return new TestSuite(RDFaGenerationTest.class.getName());
    }

    private static TestSuite listCases(File dir) {
        TestSuite cases = new TestSuite(dir.getName());

        File[] testFiles = dir.listFiles(new FilenameFilter() {
            public boolean accept(File file, String filename) {
                return (filename.endsWith(TEMPLATE_FILE_SUFFIX)
                        || (new File(file, filename).isDirectory() && !filename.startsWith(".")));
            }
        });
        // enumerate test files (RDFa templates)
        for (File f : testFiles) {
            if (f.isDirectory()) {
                cases.addTest(listCases(f));
            } else {
                cases.addTest(new RDFaGenerationTest(f.getName() + "!" + f.getPath()));
            }
        }
        return cases;
    }

    /* return only failing XPaths */

    XPathExpression evaluateXPaths(XPathIterator iterator, Document doc) throws Exception {
        while (iterator.hasNext()) {
            XPathExpression exp = iterator.next();
            NodeList result = (NodeList) exp.evaluate(doc, XPathConstants.NODESET);
            boolean negativeTest = exp.toString().startsWith("-");
            int solutions = result.getLength();
            // a positive test should return exactly one solution
            boolean failure = ((solutions < 1 && !negativeTest)
                    // a negative test should return no solutions
                    || (solutions > 0 && negativeTest));
            if (failure)
                return exp; // fail
        }
        return null;
    }

    /* a document is only viewable if it defines a fragment that is about '?this'*/

    boolean isViewable(Document doc) throws Exception {
        XPath xpath = xPathFactory.newXPath();
        // there are no namespaces in this xpath so a prefix resolver is not required
        String exp = "//*[@about='?this']";
        XPathExpression compiled = xpath.compile(exp);
        Object result = compiled.evaluate(doc, XPathConstants.NODE);
        return result != null;
    }

    /* order independent equivalence */
    /* check that all elements in the expectedFile appear in the output, and vice versa */

    void assertEqualDocuments(Document expectedDoc, Document actualDoc, String base) throws Exception {
        XPathExpression unexpected = null, missing = null;
        assertNotNull(actualDoc);
        assertNotNull(expectedDoc);

        // Match actual to expectedFile
        unexpected = evaluateXPaths(new XPathIterator(actualDoc, base), expectedDoc);

        if (verbose)
            System.out.println();
        // Match expectedFile to actual
        missing = evaluateXPaths(new XPathIterator(expectedDoc, base), actualDoc);

        if ((unexpected != null || missing != null || verbose) && !show_sparql) {
            if (!verbose)
                System.out.println("\nTEMPLATE: " + template);

            System.out.println("\nOUTPUT");
            if (unexpected != null)
                System.out.println("UNEXPECTED: " + unexpected);
            write(actualDoc, System.out);
            System.out.println();

            System.out.println("\nEXPECTED: " + expectedFile);
            if (missing != null)
                System.out.println("MISSING: " + missing);
            write(expectedDoc, System.out);
            System.out.println();
        }
        assertEquals(null, unexpected);
        assertEquals(null, missing);
    }

    public void setUp() throws Exception {
        xmlInputFactory = XMLInputFactory.newInstance();
        xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        xmlInputFactory.setProperty(XMLInputFactory.IS_VALIDATING, false);
        xmlInputFactory.setProperty("http://java.sun.com/xml/stream/properties/ignore-external-dtd", true);
        xmlInputFactory.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", true);

        // XPath
        xPathFactory = XPathFactory.newInstance();

        // initialize an in-memory store
        sourceRepository = new SailRepository(new MemoryStore());
        sourceRepository.initialize();
        source = sourceRepository.getConnection();
        expectedRepository = new SailRepository(new MemoryStore());
        expectedRepository.initialize();
        expected = expectedRepository.getConnection();
        actualRepository = new SailRepository(new MemoryStore());
        actualRepository.initialize();
        actual = actualRepository.getConnection();
        initialize();
    }

    public void tearDown() throws Exception {
        source.close();
        expected.close();
        actual.close();
        sourceRepository.shutDown();
        expectedRepository.shutDown();
        actualRepository.shutDown();
    }

    private void initialize() throws Exception {
        // use the expectedFile filename as the base URL if not 'legacy', 'construct' or 'fragment' (all construct queries) ;
        if (!test_set.contains("legacy") && !test_set.contains("construct") && !test_set.contains("fragment"))
            base = DATA_ATTRIBUTE_TEST_BASE;
        else
            base = expectedFile.toURI().toURL().toString();

        // if the sourceFile is supplied parse it for RDF and load the sourceRepository
        if (sourceFile.exists()) {
            loadRepository(source, parseRDFa(sourceFile, base));
            testPI(xmlInputFactory.createXMLEventReader(new FileReader(sourceFile)));
        }
        if (expectedFile.exists()) {
            loadRepository(expected, parseRDFa(expectedFile, base));
        }
    }

    /* test Processing Instructions */
    void testPI(XMLEventReader xml) throws Exception {
        while (xml.hasNext()) {
            XMLEvent e = xml.nextEvent();
            if (e.isProcessingInstruction()) {
                if (e.toString().contains("repository clear") || e.toString().contains("clear repository")) {
                    source.clear();
                }
            }
        }
    }

    /* Produce SPARQL from the RDFa template using SPARQLProducer.
     * Generate XHTML+RDFa using RDFaProducer.
     * Test equivalence of the expectedFile and generated output.
     */
    public void runTest() throws Exception {
        assumeTrue(test_set.contains("select"));
        if (verbose || show_rdf || show_sparql || show_xml || show_results) {
            System.out.println("\nUNION SELECT TEST: " + template);
            write(readDocument(template), System.out);
        }
        // produce SPARQL from the RDFa template
        TemplateEngine engine = TemplateEngine.newInstance(client);
        Template temp = engine.getTemplate(new FileInputStream(template), base);

        //XMLEventReader xml = xmlInputFactory.createXMLEventReader(src); 
        ValueFactory vf = source.getValueFactory();
        URI self = vf.createURI(base);
        //xml = xmlInputFactory.createXMLEventReader(new FileReader(template));
        MapBindingSet bindings = new MapBindingSet();
        bindings.addBinding("this", self);
        XMLEventList xrdfa = new XMLEventList(temp.openResult(bindings, source));
        assert xrdfa.get(0).getLocation().getCharacterOffset() >= 0;

        loadRepository(actual, new RDFaReader(base, xrdfa.iterator(), base));
        Document outputDoc = asDocument(xrdfa.iterator());

        AssertionFailedError failed = null;
        try {
            assertEqualDocuments(readDocument(expectedFile), outputDoc, base);
            assertEqualRepositories(expected, actual);
        } catch (AssertionFailedError e) {
            failed = e;
            System.out.println("UNION SELECT TEST: " + template);
            throw e;
        } finally {
            boolean ok = failed == null;
            if (!ok || verbose || show_rdf) {
                System.out.println("Source File: " + sourceFile);
                System.out.println("RDF (from Source File):");
                write(exportGraph(source), System.out);
            }
            if (!ok) {
                System.out.println("\nTEMPLATE:");
                write(readDocument(template), System.out);
            }
            if (!ok || verbose || show_sparql) {
                System.out.println("\nSPARQL (from template):");
                System.out.println(temp.getQueryString() + "\n");
            }
            if (!ok || verbose || show_xml) {
                System.out.println("\nOUTPUT:");
                write(outputDoc, System.out);
            }
            if (!ok || verbose || show_results) {
                System.out.println("\nRESULTS:");
                TupleQuery q = source.prepareTupleQuery(SPARQL, temp.getQueryString(), base);
                q.setBinding("this", self);
                TupleQueryResult results = q.evaluate();
                results = q.evaluate();
                while (results.hasNext())
                    System.out.println(results.next());
            }
        }
    }

    private void assertEqualRepositories(RepositoryConnection expected, RepositoryConnection actual)
            throws RepositoryException {
        if (!RepositoryUtil.equals(expected.getRepository(), actual.getRepository())) {
            List<Statement> expectedList = expected.getStatements(null, null, null, true).asList();
            List<Statement> actualList = actual.getStatements(null, null, null, true).asList();
            StringBuilder message = new StringBuilder(128);
            message.append("\n============ ");
            message.append(getName());
            message.append(" =======================\n");
            message.append("Expected: \n");
            for (Statement st : expectedList) {
                message.append(st.toString());
                if (!actualList.contains(st)) {
                    message.append("*");
                }
                message.append("\n");
            }
            message.append("=============");
            StringUtil.appendN('=', getName().length(), message);
            message.append("========================\n");

            message.append("Actual: \n");
            for (Statement st : actualList) {
                message.append(st.toString());
                if (!expectedList.contains(st)) {
                    message.append("*");
                }
                message.append("\n");
            }
            message.append("=============");
            StringUtil.appendN('=', getName().length(), message);
            message.append("========================\n");

            fail(message.toString());
        }
    }

    public static void main(String[] args) {
        try {

            for (int i = 0; i < args.length; i++) {
                String arg = args[i];
                if (arg.equals("-verbose"))
                    verbose = true;
                // just show the generated queries (don't run the test)
                else if (arg.equals("-rdf"))
                    show_rdf = true;
                else if (arg.equals("-sparql"))
                    show_sparql = true;
                else if (arg.equals("-xml"))
                    show_xml = true;
                else if (arg.equals("-results"))
                    show_results = true;
                //else if (arg.equals("-legacy")) test_set = "legacy";
                //else if (arg.equals("-construct")) test_set = "construct";
                else if (arg.equals("-select"))
                    test_set = "select";
                //else if (arg.equals("-fragment")) test_set = "fragment";
                else if (arg.equals("-data"))
                    test_set = "data";
                else if (!arg.startsWith("-"))
                    test_dir = arg;
            }

            // run the dynamically generated test-cases
            System.exit(TestRunner.run(RDFaGenerationTest.suite()).wasSuccessful() ? 0 : 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}