package ring.persistence;
import java.io.PrintStream;
import java.util.Map;
import java.util.Map.Entry;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Database;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.ResourceIterator;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.CollectionManagementService;
import org.xmldb.api.modules.XMLResource;
import org.xmldb.api.modules.XPathQueryService;
import org.xmldb.api.modules.XQueryService;
import ring.deployer.DeployedMUD;
import ring.system.MUDConfig;
/**
* Class used to access the eXist XND. Provides a cleaner method of querying the database.
* @author projectmoon
*
*/
public class ExistDB {
//Constants mapping to various strings necessary for XML:DB API.
//Database login mappings:
//Normally these are copied to the instance variables below.
//But they can be overridden by constructors that allow specifying of
//username and password.
private static final String DB_URI = MUDConfig.getDatabaseURI();
private static final String DB_USER = MUDConfig.getDatabaseUser();
private static final String DB_PASSWORD = MUDConfig.getDatabasePassword();
//Collection mappings
private static String ROOT_COLLECTION = "db/";
//Service mappings
private static final int SVCNAME = 0;
private static final int SVCVER = 1;
private static final String[] XQUERY_SERVICE = { "XQueryService", "1.0" };
private static final String[] COLLECTION_MGMT_SERVICE = { "CollectionManagementService", "1.0" };
//Service caches: Not necessary right now
//private static Map<Collection, XQueryService> xqServiceCache = new WeakHashMap<Collection, XQueryService>();
//Other stuff
//private static boolean shutdownHookExists;
//private static boolean shutdown = false;
//Object instance-specific stuff:
private String dbURI = DB_URI;
private String dbUser = DB_USER;
private String dbPassword = DB_PASSWORD;
static {
//System.setProperty("exist.initdb", "true");
//System.setProperty("exist.home", "/etc/ringmud/");
try {
Class<?> cl = Class.forName("org.exist.xmldb.DatabaseImpl");
Database xmlDB = (Database)cl.newInstance();
xmlDB.setProperty("create-database", "true");
DatabaseManager.registerDatabase(xmlDB);
}
catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (XMLDBException e) {
System.err.println("XMLDB Error:" + e.getMessage());
System.err.println("Is the XML database running?");
System.exit(1);
}
catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (Exception e) {
System.err.println("lol broke" + e);
System.exit(1);
}
//Hook doesn't exist until the constructor is first called.
//shutdownHookExists = false;
}
public ExistDB() {
/*
//Add a shutdown hook for the root collection only, if it's not already there
if (!shutdownHookExists) {
try {
setupShutdownHook(getRootCollection());
} catch (XMLDBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
*/
}
public ExistDB(String uri, String username, String password) {
dbURI = uri;
dbUser = username;
dbPassword = password;
}
/*
public void shutdown() {
System.out.print("Shutting down eXist... ");
try {
DatabaseInstanceManager manager = (DatabaseInstanceManager)getRootCollection().getService("DatabaseInstanceManager", "1.0");
manager.shutdown();
}
catch (XMLDBException e) {
System.err.println("There was an error shutting down eXist:");
e.printStackTrace();
}
shutdown = true;
System.out.println("Done.");
}
*/
/*
//This will come back later when support for embedded DBs is reintroduced.
private void setupShutdownHook(final Collection col) {
Runnable hook = new Runnable() {
@Override
public void run() {
//This only runs in the event of an abnormal shutdown.
if (!shutdown) {
shutdown();
}
}
};
Runtime.getRuntime().addShutdownHook(new Thread(hook));
shutdownHookExists = true;
}
*/
public static String getRootURI() {
return ROOT_COLLECTION;
}
/**
* Possibly the most important method in this class. The root collection URI determines
* which collection to pull data from. Collections are divided by MUD names, so this is
* set at server start when a deployed MUD is discovered.
* @param uri
*/
public static void setRootURI(String uri) {
if (!uri.endsWith("/")) {
uri = "db/" + uri + "/";
}
else {
uri = "db/" + uri;
}
ROOT_COLLECTION = uri;
}
public XQueryService getXQueryService(Collection col) throws XMLDBException {
return (XQueryService)col.getService(XQUERY_SERVICE[SVCNAME], XQUERY_SERVICE[SVCVER]);
}
public XPathQueryService getXPathQueryService(Collection col) throws XMLDBException {
return (XPathQueryService)col.getService("XPathQueryService", "1.0");
}
public Collection getRootCollection() throws XMLDBException {
Collection root = (Collection)DatabaseManager.getCollection(craftCollectionURI(null), dbUser, dbPassword);
return root;
}
public Collection getDatabaseRootCollection() throws XMLDBException {
Collection root = (Collection)DatabaseManager.getCollection(craftRootURI("db"), dbUser, dbPassword);
return root;
}
public Collection getCollection(String name) throws XMLDBException {
//String colName = ROOT_COLLECTION + name;
Collection col = DatabaseManager.getCollection(craftCollectionURI(name), dbUser, dbPassword);
if (col == null) {
throw new XMLDBException(-1, "Collection " + name + " is null. Is the database running?");
}
return col;
}
private String craftRootURI(String name) {
String ret = dbURI;
if (!dbURI.endsWith("/")) {
ret += "/";
}
return ret + name;
}
private String craftCollectionURI(String name) {
if (name != null) {
name = ROOT_COLLECTION + name;
}
else {
name = ROOT_COLLECTION;
}
String ret = dbURI;
if (!dbURI.endsWith("/")) {
ret += "/";
}
return ret + name;
}
public void removeAllResources() {
try {
Collection root = getRootCollection();
CollectionManagementService service = (CollectionManagementService)root.getService(
COLLECTION_MGMT_SERVICE[SVCNAME], COLLECTION_MGMT_SERVICE[SVCVER]);
for (String collectionName : root.listChildCollections()) {
Collection col = root.getChildCollection(collectionName);
removeCollection(service, col);
}
for (String resName : root.listResources()) {
Resource resource = root.getResource(resName);
root.removeResource(resource);
}
root.close();
}
catch (XMLDBException e) {
System.err.println("DB Warning: was unable to remove all collections");
e.printStackTrace();
}
}
private void removeCollection(CollectionManagementService svc, Collection col) throws XMLDBException {
for (String collectionName : col.listChildCollections()) {
Collection childCol = col.getChildCollection(collectionName);
removeCollection(svc, childCol);
}
svc.removeCollection(col.getName());
}
public XMLResource querySingleResource(String xquery) throws XMLDBException {
Collection col = getRootCollection();
System.out.println("Query: " + xquery);
XQueryService service = getXQueryService(col);
if (service != null) {
ResourceSet res = service.query(xquery);
System.out.println("Resource size: " + res.getSize());
ResourceIterator i = res.getIterator();
while (i.hasMoreResources()) {
Resource r = i.nextResource();
if (r instanceof XMLResource) {
return (XMLResource)r;
}
}
}
//Nothing found, return null.
return null;
}
public ResourceSet query(Collection col, String xquery) throws XMLDBException {
return query(col, xquery, null);
}
public ResourceSet query(Collection col, String xquery, Map<String, Object> declaredVariables) throws XMLDBException {
XQueryService service = getXQueryService(col);
if (declaredVariables != null) {
for (Entry<String, Object> entry : declaredVariables.entrySet()) {
service.declareVariable(entry.getKey(), entry.getValue());
}
}
if (service != null) {
ResourceSet res = service.query(xquery);
return res;
}
else {
//Couldn't get an XQueryService, apparently...
return null;
}
}
public void addRootNode(String rootNode) throws XMLDBException {
Collection col = getRootCollection();
XMLResource res = (XMLResource)col.createResource(null, "XMLResource");
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xml += "<" + rootNode + "></" + rootNode + ">";
res.setContent(xml);
col.storeResource(res);
}
public void addRootNode(Collection col, String rootNode) throws XMLDBException {
XMLResource res = (XMLResource)col.createResource("root", "XMLResource");
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xml += "<" + rootNode + "></" + rootNode + ">";
res.setContent(xml);
col.storeResource(res);
}
private void createDatabaseIndexDocument(Collection col) throws XMLDBException {
XMLResource res = (XMLResource)col.createResource("did.xml", "XMLResource");
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xml += "<index></index>";
res.setContent(xml);
col.storeResource(res);
}
/**
* Creates a new database by setting up collections. Doesn't check
* if one already exists... yet.
*/
public void createRingDatabase() throws XMLDBException {
Collection root = getDatabaseRootCollection();
CollectionManagementService service = (CollectionManagementService)root.getService(
COLLECTION_MGMT_SERVICE[SVCNAME], COLLECTION_MGMT_SERVICE[SVCVER]);
Collection mudRoot = service.createCollection(craftCollectionURI(""));
addRootNode(mudRoot, "ring");
CollectionManagementService mudsvc = (CollectionManagementService)mudRoot.getService(
COLLECTION_MGMT_SERVICE[SVCNAME], COLLECTION_MGMT_SERVICE[SVCVER]);
//Static content: loaded during server boot
Collection staticCol = mudsvc.createCollection(craftCollectionURI(ExistDBStore.STATIC_COLLECTION));
addRootNode(staticCol, "ring");
//Game collection: Stores world state and DiD
Collection gameCol = mudsvc.createCollection(craftCollectionURI(ExistDBStore.GAME_COLLECTION));
addRootNode(gameCol, "ring");
createDatabaseIndexDocument(gameCol);
//Players collection: Stores player information.
Collection playersCol = mudsvc.createCollection(craftCollectionURI(ExistDBStore.PLAYERS_COLLECTION));
addRootNode(playersCol, "ring");
}
public void listResources(PrintStream out) {
try {
Collection col = getCollection(ExistDBStore.STATIC_COLLECTION);
out.println("Static Collection:");
String[] res = col.listResources();
for (String r : res) {
out.println(r);
}
col = getCollection(ExistDBStore.GAME_COLLECTION);
out.println("Game Collection:");
res = col.listResources();
for (String r : res) {
out.println(r);
}
}
catch (XMLDBException e) {
e.printStackTrace();
}
}
}