package org.exist.xquery;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.dom.DocumentImpl;
import org.exist.security.SecurityManager;
import org.exist.security.xacml.AccessContext;
import org.exist.source.StringSource;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.test.TestConstants;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.value.Sequence;
import org.exist.util.Configuration;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.xml.sax.InputSource;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Properties;
import javax.xml.transform.OutputKeys;
import junit.framework.TestCase;
import junit.textui.TestRunner;
/** Tests for recovery of database corruption after constructed node operations (in-memory nodes)
* @author Adam Retter <adam.retter@devon.gov.uk>
*/
public class ConstructedNodesRecoveryTest extends TestCase
{
private final static String xquery =
"declare variable $categories := \n" +
" <categories>\n" +
" <category uid=\"1\">Fruit</category>\n" +
" <category uid=\"2\">Vegetable</category>\n" +
" <category uid=\"3\">Meat</category>\n" +
" <category uid=\"4\">Dairy</category>\n" +
" </categories>\n" +
";\n\n" +
"for $category in $categories/category return\n" +
" element option {\n" +
" attribute value {\n" +
" $category/@uid\n" +
" },\n" +
" text { $category }\n" +
" }";
private final static String expectedResults [] = {
"Fruit",
"Vegetable",
"Meat",
"Dairy"
};
private final static String testDocument =
"<fruit>" +
"<apple colour=\"green\"/>" +
"<pear colour=\"green\"/>" +
"<orange colour=\"orange\"/>" +
"<dragonfruit colour=\"pink\"/>" +
"<grapefruit colour=\"yellow\"/>" +
"</fruit>";
public static void main(String[] args)
{
TestRunner.run(ConstructedNodesRecoveryTest.class);
}
/**
* Issues a query against constructed nodes and then corrupts the database (intentionally)
*/
public void testConstructedNodesCorrupt()
{
constructedNodeQuery(true);
}
/**
* Recovers from corruption (intentional) and then issues a query against constructed nodes
*/
public void testConstructedNodesRecover()
{
constructedNodeQuery(false);
}
private void storeTestDocument(DBBroker broker, TransactionManager transact, String documentName) throws Exception
{
//create a transaction
Txn transaction = transact.beginTransaction();
assertNotNull(transaction);
System.out.println("Transaction started ...");
//get the test collection
Collection root = broker.getOrCreateCollection(transaction, TestConstants.TEST_COLLECTION_URI);
assertNotNull(root);
broker.saveCollection(transaction, root);
//store test document
IndexInfo info = root.validateXMLResource(transaction, broker, XmldbURI.create(documentName), testDocument);
assertNotNull(info);
root.store(transaction, broker, info, new InputSource(new StringReader(testDocument)), false);
//commit the transaction
transact.commit(transaction);
System.out.println("Transaction commited ...");
}
private void createTempChildCollection(DBBroker broker, TransactionManager transact, String childCollectionName) throws Exception
{
//create a transaction
Txn transaction = transact.beginTransaction();
assertNotNull(transaction);
System.out.println("Transaction started ...");
//get the test collection
Collection root = broker.getOrCreateCollection(transaction, XmldbURI.TEMP_COLLECTION_URI.append(childCollectionName));
assertNotNull(root);
broker.saveCollection(transaction, root);
//commit the transaction
transact.commit(transaction);
System.out.println("Transaction commited ...");
}
private void testDocumentIsValid(DBBroker broker, TransactionManager transact, String documentName) throws Exception
{
//create a transaction
Txn transaction = transact.beginTransaction();
assertNotNull(transaction);
System.out.println("Transaction started ...");
//get the test collection
Collection root = broker.getOrCreateCollection(transaction, TestConstants.TEST_COLLECTION_URI);
assertNotNull(root);
broker.saveCollection(transaction, root);
//get the test document
DocumentImpl doc = root.getDocumentWithLock(broker, XmldbURI.create(documentName), Lock.READ_LOCK);
Serializer serializer = broker.getSerializer();
serializer.reset();
SAXSerializer sax = null;
StringWriter writer = new StringWriter();
sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
Properties outputProperties = new Properties();
outputProperties.setProperty(OutputKeys.INDENT, "no");
outputProperties.setProperty(OutputKeys.ENCODING, "UTF-8");
sax.setOutput(writer, outputProperties);
serializer.setProperties(outputProperties);
serializer.setSAXHandlers(sax, sax);
serializer.toSAX(doc);
SerializerPool.getInstance().returnObject(sax);
assertEquals(testDocument, writer.toString());
transact.commit(transaction);
}
private void testTempChildCollectionExists(DBBroker broker, TransactionManager transact, String childCollectionName) throws Exception
{
//create a transaction
Txn transaction = transact.beginTransaction();
assertNotNull(transaction);
System.out.println("Transaction started ...");
//get the temp child collection
Collection tempChildCollection = broker.getOrCreateCollection(transaction, XmldbURI.TEMP_COLLECTION_URI.append(childCollectionName));
assertNotNull(tempChildCollection);
broker.saveCollection(transaction, tempChildCollection);
transact.commit(transaction);
}
/**
* Performs a query against constructed nodes, with the option of forcefully corrupting the database
*
* @param forceCorruption Should the database be forcefully corrupted
*/
private void constructedNodeQuery(boolean forceCorruption)
{
BrokerPool.FORCE_CORRUPTION = forceCorruption;
BrokerPool pool = null;
DBBroker broker = null;
try
{
pool = startDB();
assertNotNull(pool);
broker = pool.get(SecurityManager.SYSTEM_USER);
TransactionManager transact = pool.getTransactionManager();
assertNotNull(transact);
//only store the documents the first time
if(forceCorruption)
{
//store a first test document
storeTestDocument(broker, transact, "testcr1.xml");
//store a second test document
storeTestDocument(broker, transact, "testcr2.xml");
}
//create some child collections in TEMP collection
createTempChildCollection(broker, transact, "testchild1");
createTempChildCollection(broker, transact, "testchild2");
//execute an xquery
XQuery service = broker.getXQueryService();
assertNotNull(service);
CompiledXQuery compiled = service.compile(service.newContext(AccessContext.TEST), new StringSource(xquery));
assertNotNull(compiled);
Sequence result = service.execute(compiled, null);
assertNotNull(result);
assertEquals(expectedResults.length, result.getItemCount());
for(int i = 0; i < result.getItemCount(); i++)
{
assertEquals(expectedResults[i], (String)result.itemAt(i).getStringValue());
}
//read the first test document
testDocumentIsValid(broker, transact, "testcr1.xml");
//read the second test document
testDocumentIsValid(broker, transact, "testcr1.xml");
//test the child collections exist
testTempChildCollectionExists(broker, transact, "testchild1");
testTempChildCollectionExists(broker, transact, "testchild2");
transact.getJournal().flushToLog(true);
}
catch(Exception e)
{
fail(e.getMessage());
e.printStackTrace();
}
finally
{
if (pool != null) pool.release(broker);
}
}
protected BrokerPool startDB() {
try {
Configuration config = new Configuration();
BrokerPool.configure(1, 5, config);
return BrokerPool.getInstance();
} catch (Exception e) {
fail(e.getMessage());
}
return null;
}
protected void tearDown() {
BrokerPool.stopAll(false);
}
}