org.ozsoft.xantippe.DatabaseTest.java Source code

Java tutorial

Introduction

Here is the source code for org.ozsoft.xantippe.DatabaseTest.java

Source

// This file is part of the Xantippe XML database.
//
// Copyright 2008 Oscar Stigter
//
// 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.ozsoft.xantippe;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Test suite for the database.
 * 
 * @author Oscar Stigter
 */
public class DatabaseTest {

    /** Directory with the resource files. */
    private static final String DB_DIR = "src/test/resources/db";

    /** Database data directory. */
    private static final String DATA_DIR = "target/data.tmp";

    /** XML declaration. */
    private static final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

    /** Number of parallel client threads. */
    private static final int NO_OF_THREADS = 10;

    /** Number of iterations per client thread. */
    private static final int NO_OF_THREAD_ITERATIONS = 100;

    /** Buffer size. */
    private static final int BUFFER_SIZE = 8192; // 8 kB

    /** Log. */
    private static final Log LOG = LogFactory.getLog(DatabaseTest.class);

    /** Database. */
    private static Database database;

    //------------------------------------------------------------------------
    //  Setup & cleanup methods
    //------------------------------------------------------------------------

    @BeforeClass
    public static void beforeClass() {
        Util.initLog4j();
        database = new DatabaseImpl();
        database.setDatabaseLocation(DATA_DIR);
    }

    @Before
    public void before() {
        Util.deleteFile(DATA_DIR);
    }

    @After
    public void after() {
        Util.deleteFile(DATA_DIR);
    }

    //------------------------------------------------------------------------
    //  Tests
    //------------------------------------------------------------------------

    @Test
    public void states() {
        LOG.debug("Test suite 'states' started");

        // Check database is not running.
        Assert.assertFalse(database.isRunning());

        // Shutdown database that is not running.
        try {
            database.shutdown();
        } catch (IllegalStateException e) {
            Assert.assertEquals("Database not running", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Invalid database location.
        try {
            database.setDatabaseLocation(null);
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Null or empty database location", e.getMessage());
        }
        try {
            database.setDatabaseLocation("");
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Null or empty database location", e.getMessage());
        }

        // Get root collections while database is not running.
        try {
            database.getRootCollection();
        } catch (IllegalStateException e) {
            Assert.assertEquals("Database not running", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Execute query while database is not running.
        try {
            database.executeQuery(null);
        } catch (IllegalStateException e) {
            Assert.assertEquals("Database not running", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Start database (successfully).
        try {
            database.start();
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }

        // Check database is running.
        Assert.assertTrue(database.isRunning());

        // Start database that is already running.
        try {
            database.start();
        } catch (IllegalStateException e) {
            Assert.assertEquals("Database already running", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Set database location while database is running.
        try {
            database.setDatabaseLocation(DATA_DIR);
        } catch (IllegalStateException e) {
            Assert.assertEquals("Database is running", e.getMessage());
        }

        // Shutdown database (successful).
        try {
            database.shutdown();
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }

        // Check database is shut down.
        Assert.assertFalse(database.isRunning());

        LOG.debug("Test suite 'states' finished");
    }

    @Test
    public void badWeather() {
        LOG.debug("Test suite 'badWeather' started");

        try {
            database.start();
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }

        // Get collection with null URI.
        try {
            database.getCollection(null);
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Null or empty URI", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Get collection with empty URI.
        try {
            database.getCollection("");
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Null or empty URI", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Get collection with invalid URI.
        try {
            database.getCollection("foobar");
        } catch (XmldbException e) {
            Assert.assertEquals("Invalid URI: foobar", e.getMessage());
        }

        // Get non-existing collection.
        try {
            database.getCollection("/foo");
        } catch (XmldbException e) {
            Assert.assertEquals("Collection not found: /foo", e.getMessage());
        }

        // Get document with null URI.
        try {
            database.getDocument(null);
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Null or empty URI", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Get document with empty URI.
        try {
            database.getDocument("");
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Null or empty URI", e.getMessage());
        } catch (Exception e) {
            Assert.fail("Incorrect exception: " + e);
        }

        // Get document with invalid URI.
        try {
            database.getDocument("foo");
        } catch (XmldbException e) {
            Assert.assertEquals("Invalid URI: foo", e.getMessage());
        }

        // Get non-existing document.
        try {
            database.getDocument("/foo.xml");
        } catch (XmldbException e) {
            Assert.assertEquals("Document not found: /foo.xml", e.getMessage());
        }

        try {
            database.shutdown();
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }

        LOG.debug("Test suite 'badWeather' finished");
    }

    /**
     * Tests various basic storage aspects.
     */
    @Test
    public void storage() {
        LOG.debug("Test suite 'storage' started");

        File file;
        Document doc;

        try {
            database.start();

            // Basic tests.
            Assert.assertTrue(database.exists("/"));
            try {
                Assert.assertFalse(database.exists("foo"));
                Assert.fail("No exception thrown!");
            } catch (IllegalArgumentException e) {
                // OK
            }
            Assert.assertFalse(database.exists("/foo"));
            Assert.assertFalse(database.exists("/foo/bar"));
            Assert.assertTrue(database.isCollection("/"));
            Assert.assertFalse(database.isCollection("/foo"));
            Assert.assertFalse(database.isDocument("/"));

            // Create collections.
            Collection rootCol = database.getRootCollection();
            Assert.assertNotNull(rootCol);
            rootCol = database.getCollection("/");
            Assert.assertNotNull(rootCol);
            Assert.assertEquals("", rootCol.getName());
            Assert.assertEquals("/", rootCol.getUri());
            Assert.assertNull(rootCol.getParent());
            Assert.assertEquals(0, rootCol.getDocuments().size());
            Assert.assertEquals(0, rootCol.getCollections().size());
            Assert.assertEquals(0, rootCol.getIndices(true).size());
            Collection dataCol = rootCol.createCollection("data");
            Assert.assertTrue(database.exists("/data"));
            Assert.assertTrue(database.isCollection("/data"));
            Assert.assertFalse(database.isDocument("/data"));
            Assert.assertEquals("data", dataCol.getName());
            Assert.assertEquals("/data", dataCol.getUri());
            Assert.assertEquals("/", dataCol.getParent().getUri());
            Assert.assertEquals(0, dataCol.getDocuments().size());
            Assert.assertEquals(0, dataCol.getCollections().size());
            Assert.assertEquals(0, dataCol.getIndices(true).size());
            dataCol.setCompressionMode(CompressionMode.DEFLATE);
            dataCol.createCollection("foo");
            Assert.assertTrue(database.exists("/data/foo"));
            Collection fooCol = database.getCollection("/data/foo");
            Assert.assertEquals("foo", fooCol.getName());
            Assert.assertEquals("/data/foo", fooCol.getUri());
            Assert.assertEquals("/data", fooCol.getParent().getUri());
            Assert.assertEquals(0, fooCol.getDocuments().size());
            Assert.assertEquals(0, fooCol.getCollections().size());
            Assert.assertEquals(0, fooCol.getIndices(true).size());
            fooCol.setCompressionMode(CompressionMode.NONE);
            Collection barCol = dataCol.createCollection("bar");
            Assert.assertEquals("bar", barCol.getName());
            Assert.assertEquals("/data/bar", barCol.getUri());
            Assert.assertEquals("/data", barCol.getParent().getUri());
            Assert.assertEquals(0, barCol.getDocuments().size());
            Assert.assertEquals(0, barCol.getCollections().size());
            Assert.assertEquals(0, barCol.getIndices(true).size());
            Collection emptyCol = dataCol.createCollection("empty");
            Assert.assertEquals("/data/empty", emptyCol.getUri());

            // Create indices.
            Assert.assertEquals(0, rootCol.getIndices(true).size());
            Assert.assertEquals(0, dataCol.getIndices(true).size());
            Assert.assertEquals(0, fooCol.getIndices(true).size());
            dataCol.addIndex("DocumentId", "//Header/Id", IndexType.INTEGER);
            Assert.assertEquals(1, dataCol.getIndices(true).size());
            Index index = dataCol.getIndex("DocumentId");
            Assert.assertNotNull(index);
            Assert.assertEquals("DocumentId", index.getName());
            Assert.assertEquals("//Header/Id", index.getPath());
            Assert.assertEquals(IndexType.INTEGER, index.getType());
            dataCol.addIndex("DocumentType", "//Header/Type", IndexType.STRING);
            dataCol.addIndex("DocumentVersion", "//Header/Version", IndexType.STRING);
            Assert.assertEquals(3, dataCol.getIndices(false).size());
            Assert.assertEquals(3, dataCol.getIndices(true).size());
            Assert.assertEquals(0, fooCol.getIndices(false).size());
            Assert.assertEquals(3, fooCol.getIndices(true).size());
            index = fooCol.getIndex("DocumentId");
            Assert.assertNotNull(index);
            Assert.assertEquals("DocumentId", index.getName());
            Assert.assertEquals("//Header/Id", index.getPath());
            index = fooCol.getIndex("DocumentType");
            Assert.assertNotNull(index);
            Assert.assertEquals("DocumentType", index.getName());
            Assert.assertEquals("//Header/Type", index.getPath());
            index = fooCol.getIndex("DocumentVersion");
            Assert.assertNotNull(index);
            Assert.assertEquals("DocumentVersion", index.getName());
            Assert.assertEquals("//Header/Version", index.getPath());
            index = fooCol.getIndex("NonExisting");
            Assert.assertNull(index);

            // Add documents.

            file = new File(DB_DIR + "/data/foo/Foo-0001.xml");
            doc = fooCol.createDocument(file.getName());
            Assert.assertTrue(database.exists("/data/foo/Foo-0001.xml"));
            Assert.assertTrue(database.isDocument("/data/foo/Foo-0001.xml"));
            Assert.assertFalse(database.isCollection("/data/foo/Foo-0001.xml"));
            Assert.assertEquals("Foo-0001.xml", doc.getName());
            Assert.assertEquals(0, doc.getLength());
            Assert.assertEquals(0, doc.getStoredLength());
            Assert.assertNotNull(doc.getContent());
            doc.setContent(file);
            assertEquals(doc.getContent(), file);
            Assert.assertEquals(file.length(), doc.getLength());
            Assert.assertEquals(file.length(), doc.getStoredLength()); // no compression

            file = new File(DB_DIR + "/data/foo/Foo-0002.xml");
            doc = fooCol.createDocument(file.getName());
            doc.setContent(file);
            assertEquals(doc.getContent(), file);
            doc.setKey("My_Manual_Key", "Foo-0002_manual_key");

            doc = barCol.createDocument("Bar-0001.xml");
            OutputStream os = doc.setContent();
            os.write(("<BarDocument><Header><Id>3</Id><Type>Bar</Type>"
                    + "<Version>v1.0</Version></Header></BarDocument>").getBytes());
            os.close();
            Assert.assertEquals(93, doc.getLength());
            Assert.assertTrue(doc.getStoredLength() < 93); // compressed

            // Shutdown and restart database to test persistency.
            database.shutdown();
            database.start();

            // Refresh collection references.
            rootCol = database.getRootCollection();
            dataCol = database.getCollection("/data");
            fooCol = dataCol.getCollection("foo");

            // Find documents by keys (using indices).

            IndexKey[] keys = new IndexKey[] { new IndexKey("DocumentId", 2) };
            Set<Document> docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(1, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/foo/Foo-0002.xml", doc.getUri());

            keys = new IndexKey[] { new IndexKey("DocumentType", "Foo") };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(2, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/foo/Foo-0001.xml", doc.getUri());
            doc = (Document) docs.toArray()[1];
            Assert.assertEquals("/data/foo/Foo-0002.xml", doc.getUri());
            docs = fooCol.findDocuments(keys, false);
            Assert.assertEquals(2, docs.size());

            keys = new IndexKey[] { new IndexKey("DocumentId", 2), new IndexKey("DocumentType", "Foo") };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(1, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/foo/Foo-0002.xml", doc.getUri());

            keys = new IndexKey[] { new IndexKey("DocumentVersion", "v1.0") };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(2, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/bar/Bar-0001.xml", doc.getUri());
            doc = (Document) docs.toArray()[1];
            Assert.assertEquals("/data/foo/Foo-0001.xml", doc.getUri());

            docs = fooCol.findDocuments(keys, true);
            Assert.assertEquals(1, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/foo/Foo-0001.xml", doc.getUri());

            keys = new IndexKey[] { new IndexKey("My_Manual_Key", "Foo-0002_manual_key") };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(1, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/foo/Foo-0002.xml", doc.getUri());

            keys = new IndexKey[] { new IndexKey("DocumentType", "Foo"),
                    new IndexKey("My_Manual_Key", "Foo-0002_manual_key") };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(1, docs.size());
            doc = (Document) docs.toArray()[0];
            Assert.assertEquals("/data/foo/Foo-0002.xml", doc.getUri());

            keys = new IndexKey[] { new IndexKey("DocumentType", "Bar"),
                    new IndexKey("My_Manual_Key", "Foo-0002_manual_key") };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(0, docs.size());

            keys = new IndexKey[] { new IndexKey("DocumentType", "Foo") };
            docs = dataCol.findDocuments(keys, false);
            Assert.assertEquals(0, docs.size());

            keys = new IndexKey[] { new IndexKey("DocumentType", "NonExisting"), };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(0, docs.size());

            keys = new IndexKey[] { new IndexKey("DocumentId", 2), new IndexKey("DocumentType", "NonExisting"), };
            docs = rootCol.findDocuments(keys, true);
            Assert.assertEquals(0, docs.size());

            //FIXME: Fix bug with deleting resources!
            // Delete documents and collections.

            //            // Delete a non-existing resource by its URI.
            //            try {
            //                database.delete("/unknown");
            //                Assert.fail("No exception thrown!");
            //            } catch (XmldbException e) {
            //                // OK
            //            }
            //
            //            // Delete a document by its URI.
            //            database.delete("/data/bar/Bar-0001.xml");
            //            Assert.assertFalse(database.exists("/data/bar/Bar-0001.xml"));
            //            
            //            // Delete a single, empty collection by its URI.
            //            database.delete("/data/bar");
            //            Assert.assertFalse(database.exists("/data/bar"));
            //
            //            // Delete a non-existing document from a collection.
            //            Assert.assertFalse(fooCol.deleteDocument("NonExisting"));
            //
            //            // Delete a non-existing collection from a collection.
            //            Assert.assertFalse(fooCol.deleteCollection("NonExisting"));
            //
            //            // Delete a document from a collection.
            //            Assert.assertTrue(fooCol.deleteDocument("Foo-0001.xml"));
            //            Assert.assertFalse(database.exists("/data/foo/Foo-0001.xml"));
            //
            //            // Delete a single, empty collection.
            //            Assert.assertTrue(dataCol.deleteCollection("empty"));
            //            Assert.assertFalse(database.exists("/data/empty"));
            //            
            //            // Delete a single, non-empty collection.
            //            Assert.assertTrue(dataCol.deleteCollection("foo"));
            //            Assert.assertFalse(database.exists("/data/foo"));
            //
            //            // Delete a non-empty collection tree (recursive).
            //            Assert.assertTrue(rootCol.deleteCollection("data"));
            //            try {
            //                database.getCollection("/data");
            //                Assert.fail("No exception thrown!");
            //            } catch (XmldbException e) {
            //                // OK
            //            }

            database.shutdown();

        } catch (Exception e) {
            LOG.error("Unexpected exception", e);
            Assert.fail(e.getMessage());
        }

        LOG.debug("Test suite 'storage' finished");
    }

    /**
     * Tests the validation of documents against their schema. 
     */
    @Test
    public void validation() {
        LOG.debug("Test suite 'validation' started");

        File file;
        Document doc;

        try {
            database.start();

            Collection col = database.getRootCollection();
            col.setValidationMode(ValidationMode.ON);

            // Self-contained schema.
            file = new File(DB_DIR + "/schemas/Address_v1.0.xsd");
            doc = col.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/data/addressbook/Address_0001.xml");
            doc = col.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/data/addressbook/Address_0002.xml");
            doc = col.createDocument(file.getName());
            doc.setContent(file);

            // Multi-file schema (with 'include' statement).
            file = new File(DB_DIR + "/schemas/Generic_v1.0.xsd");
            doc = col.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/schemas/TestResult_v1.0.xsd");
            doc = col.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/data/testresults/TestResult_0001.xml");
            doc = col.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/data/testresults/TestResult_0002.xml");
            doc = col.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/data/testresults/TestResult_0003.xml");
            doc = col.createDocument(file.getName());
            doc.setContent(file);

            database.shutdown();

        } catch (XmldbException e) {
            Assert.fail(e.getMessage());
        }

        LOG.debug("Test suite 'validation' finished");
    }

    /**
     * Tests the execution of queries.
     */
    @Test
    public void xquery() {
        LOG.debug("Test suite 'xquery' started");

        File file;
        Document doc;

        try {
            database.start();

            // Fill database.
            Collection rootCol = database.getRootCollection();
            Collection dataCol = rootCol.createCollection("data");
            Collection fooCol = dataCol.createCollection("foo");
            file = new File(DB_DIR + "/data/foo/Foo-0001.xml");
            doc = fooCol.createDocument(file.getName());
            doc.setContent(file);
            file = new File(DB_DIR + "/data/foo/Foo-0002.xml");
            doc = fooCol.createDocument(file.getName());
            doc.setContent(file);
            Collection barCol = dataCol.createCollection("bar");
            file = new File(DB_DIR + "/data/bar/Bar-0001.xml");
            doc = barCol.createDocument(file.getName());
            doc.setContent(file);
            Collection modulesCol = rootCol.createCollection("modules");
            file = new File(DB_DIR + "/modules/greeting.xqy");
            doc = modulesCol.createDocument(file.getName());
            doc.setContent(file);

            // Simple query.
            String query = "<Result>{2 + 3}</Result>";
            String result = database.executeQuery(query).toString();
            Assert.assertEquals(XML_DECLARATION + "\n<Result>5</Result>", result);

            // Local function call.
            query = "declare namespace m = 'urn:math';\n\n"
                    + "declare function m:sum($x as xs:integer, $y as xs:integer) as xs:integer { \n"
                    + "  let $result := $x + $y \n" + "  return $result \n" + "}; \n\n"
                    + "<Result>{m:sum(2, 3)}</Result>";
            result = database.executeQuery(query).toString();
            Assert.assertEquals(XML_DECLARATION + "\n<Result>5</Result>", result);

            // doc() function.
            query = "doc('/data/foo/Foo-0001.xml')/element()/Header/Id/text()";
            result = database.executeQuery(query).toString();
            Assert.assertEquals(XML_DECLARATION + "1", result);

            // collection() function.
            query = "count(collection('/data?recurse=yes'))";
            result = database.executeQuery(query).toString();
            Assert.assertEquals(XML_DECLARATION + "3", result);
            query = "collection('/data/foo')/element()/Header/Id/text()";
            result = database.executeQuery(query).toString();
            Assert.assertEquals(XML_DECLARATION + "12", result);

            // Stored XQuery module
            query = "import module namespace gr = 'http://www.example.com/greeting' \n"
                    + "  at '/modules/greeting.xqy'; \n\n" + "gr:greeting('Mr Smith')";
            result = database.executeQuery(query).toString();
            Assert.assertEquals(XML_DECLARATION + "Hello, Mr Smith!", result);

            database.shutdown();

        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }

        LOG.debug("Test suite 'xquery' finished");
    }

    /**
     * Tests the database's multithreading behavior.
     * 
     * Uses multiple simulated clients who each perform actions in a number of
     * iterations.
     */
    @Test
    public void multiThreading() {
        LOG.debug("Test suite 'multiThreading' started");

        try {
            database.start();

            // Fill database.
            Collection col = database.getRootCollection();
            col.setValidationMode(ValidationMode.OFF);
            col.setCompressionMode(CompressionMode.NONE);

            col.createDocument("doc.xml");

            ClientThread[] clients = new ClientThread[NO_OF_THREADS];
            for (int i = 0; i < NO_OF_THREADS; i++) {
                clients[i] = new ClientThread(i, col);
            }
            for (ClientThread client : clients) {
                client.start();
            }
            for (ClientThread client : clients) {
                client.join();
                Assert.assertFalse("Error encountered in thread", client.hasErrors());
            }

            database.shutdown();

        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }

        LOG.debug("Test suite 'multiThreading' finished");
    }

    //------------------------------------------------------------------------
    //  Private methods
    //------------------------------------------------------------------------

    private static void assertEquals(InputStream is, File file) {
        try {
            InputStream is2 = new FileInputStream(file);
            byte[] buffer1 = new byte[BUFFER_SIZE];
            byte[] buffer2 = new byte[BUFFER_SIZE];
            int bytesRead1;
            int bytesRead2;
            while ((bytesRead1 = is.read(buffer1)) > 0) {
                bytesRead2 = is2.read(buffer2);
                Assert.assertEquals(bytesRead1, bytesRead2);
                Assert.assertArrayEquals(buffer1, buffer2);
            }
            is2.close();
            is.close();
        } catch (IOException e) {
            Assert.fail(e.getMessage());
        }
    }

    //------------------------------------------------------------------------
    //  Private classes
    //------------------------------------------------------------------------

    /**
     * Thread simulating a database client connection reading and writing the
     * same document many times.
     * 
     * The database is assumed to be running at all times.
     */
    private static class ClientThread extends Thread {

        private static final String DOC_NAME = "doc.xml";

        private final int id;

        private final Collection col;

        private final byte[] buffer = new byte[BUFFER_SIZE];

        private boolean hasErrors = false;

        public ClientThread(int id, Collection col) {
            this.id = id;
            this.col = col;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i < NO_OF_THREAD_ITERATIONS; i++) {
                    // Retrieve document.
                    Document doc = col.getDocument(DOC_NAME);
                    // Read document content.
                    String content = getDocumentContent(doc);
                    // Write document content.
                    content += "Written by thread " + id + "\n";
                    setDocumentContent(doc, content);
                }
            } catch (Exception e) {
                String msg = String.format("Exception in thread %d: %s", id, e.getMessage());
                LOG.error(msg, e);
                hasErrors = true;
            }
        }

        public boolean hasErrors() {
            return hasErrors;
        }

        private String getDocumentContent(Document doc) throws XmldbException, IOException {
            String content = null;

            InputStream is = doc.getContent();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int length;
            while ((length = is.read(buffer)) > 0) {
                baos.write(buffer, 0, length);
            }
            is.close();
            content = new String(baos.toByteArray());
            baos.close();

            return content;
        }

        private void setDocumentContent(Document doc, String content) throws XmldbException, IOException {
            OutputStream os = doc.setContent();
            byte[] buffer = content.getBytes();
            os.write(buffer);
            os.close();
        }

    } // ClientThread

}