moved Resultlist to result package,
[opensuse:sxkeeper.git] / src / de / suse / backends / DbxmlBackend.java
1 /*
2  * Copyright (c) - 2005 by Klaas Freitag <freitag@suse.de>
3  * 
4  * This file is part of the SUSE XML (SX) project. 
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of version 2 of the GNU General Public License as
8  * published by the Free Software Foundation.
9  */
10 package de.suse.backends;
11
12 import java.io.*;
13 import java.util.*;
14
15 import org.apache.log4j.*;
16 import org.jdom.*;
17 import org.jdom.output.*;
18
19 import com.sleepycat.db.*;
20 import com.sleepycat.dbxml.*;
21
22 import de.suse.security.*;
23 import de.suse.sx.result.*;
24 import de.suse.xml.*;
25
26 /**
27  * This is part of a base library of the SUSE XML server. The dataManager
28  * provides storage- and query capabilities to the SX database in a very generic
29  * way.
30  * 
31  * @author Klaas Freitag &lt;freitag@suse.de&gt;
32  * @author Thomas Schmidt &lt;tschmidt@suse.de&gt;
33  */
34 public class DbxmlBackend extends SxBackendIface {
35
36     private final Logger log = Logger.getLogger("de.suse.backends.dbxml");
37     private XmlManager xmlMan = null;
38     public static String dbSuffix = ".dbxml";
39     // list of opened containers, key=containername, value=container object
40     protected HashMap containers = new HashMap();
41     
42     /**
43      * The (private) constructor opens all containers that are available in 
44      * the configure containerpath. A container is identified by its .dbxml 
45      * ending. 
46      * A History file for each available container is automatically created 
47      * with the name <containername>-history.dbxml.
48      * Other classes use getInstance() to get the instance of the data manager.
49      */
50     public DbxmlBackend() throws Exception {
51         super();
52         try {
53             // open shared manager
54             xmlMan = createXMLManager();
55             // iterate over the directory and open every found xmldb container.
56             File[] containerFiles = new File(containerPath).listFiles();
57             XmlContainerConfig containerConf = getXMLContainerConfig();
58             for (int i=0; containerFiles != null && i < containerFiles.length; i++) {
59                 if (!containerFiles[i].isDirectory() && 
60                         containerFiles[i].getName().endsWith(dbSuffix)) {
61                     log.debug("Found containerfile: " + containerFiles[i].getName());
62                     // try to load config file for container: 
63                     String containerName = containerFiles[i].getName().
64                         substring(0, containerFiles[i].getName().length() - dbSuffix.length());
65                     propMan.loadConfig(containerName);
66                     try {
67                         containers.put(containerName, xmlMan.openContainer(containerFiles[i].getName(), containerConf));
68                     } catch (Exception e) {
69                         log.error("Opening container: " + containerFiles[i].getName() + " failed: " + e.getMessage());
70                         e.printStackTrace();
71                     }
72                 }
73             }
74         } catch (Exception e) {
75             log.error("Error setting up initial XMLManager: " + e.getMessage());
76             e.printStackTrace();
77             throw new Exception(e.getMessage());
78         }
79     }
80
81
82     
83     
84     /**
85      * Completely deletes the container. Use with caution ;-)
86      * @param containerName
87      * @throws SXException
88      */
89     public synchronized void deleteContainer(String containerName)
90             throws SXException {
91         log.debug("Attention: going to delete container: " + containerName);
92         if (containers.containsKey(containerName)) {
93             XmlContainer container = (XmlContainer) containers
94                     .get(containerName);
95             containers.remove(containerName);
96             XmlTransaction txn = null;
97             try {
98                 txn = xmlMan.createTransaction();
99                 container.delete();
100                 xmlMan.removeContainer(containerName + dbSuffix);
101                 txn.commit();
102                 txn.delete();
103             } catch (XmlException e) {
104                 try {
105                     txn.abort();
106                 } catch (XmlException e1) {
107                     e1.printStackTrace();
108                 }
109                 e.printStackTrace();
110                 throw new SXException(e);
111             }
112         } else {
113             log.error("cannot find container " + containerName
114                     + ", will try to delete the file anyway");
115         }
116         File dbContainer = new File(containerPath + containerName + dbSuffix);
117         if (dbContainer.exists()) {
118             log.debug("Deleting containerfile manually.");
119             dbContainer.delete();
120         }
121     }
122     
123     /**
124      * create a new container
125      * 
126      * @param containerName name of the container (e.g. "feature")
127      * @throws SXException
128      */
129     public synchronized void createContainer(String containerName) throws SXException {
130         log.debug("Creating new container " + containerName);
131         String containerFileName = containerName + dbSuffix;
132         XmlContainer container = null;
133         XmlTransaction txn = null;
134         try {
135             new File(containerPath + containerFileName);
136             txn = xmlMan.createTransaction();
137             container = xmlMan.createContainer(containerFileName, getXMLContainerConfig());
138             containers.put(containerName, container);
139             txn.commit();
140             txn.delete();
141             addDefaultIndexes(containerName);
142         } catch (Exception e) {
143             log.error("Error in creating new container: " + e.getMessage());
144             if (txn != null){
145                 try {
146                     txn.abort();
147                 } catch (XmlException e1) {
148                     e1.printStackTrace();
149                     txn.delete();
150                     throw new SXException(e1);
151                 }
152                 txn.delete();
153             }
154             e.printStackTrace();
155             throw new SXException(e);
156         }
157         log.debug("created new container: " + containerName);
158     }
159     
160     
161     /**
162      * Stores a XML snippet to the database.
163      * 
164      * @param collection
165      *            the namespace of the document, ie. feature
166      * @param doc
167      *            the XML snippet as a string. Note that the doc must be a valid
168      *            XML document. It should maintain the namespace correctly to
169      *            distinguish between different sx storage types, ie. features,
170      *            contacts or package descriptions.
171      *            <p>
172      *            A good example document looks like this:
173      *            <p>
174      *            <code>
175      *        &lt;features xmlns:sxfeatures="http://sx.suse.de/features"&gt;<br>
176      *           &lt;description&gt;<br>
177      *             ...<br>
178      *           &lt;/description&gt;<br>
179      *        &lt;/features&gt;<br>
180      * </code>
181      *            <p>
182      *            It uses the xsfeatures namespace.
183      * 
184      * @return the unique name of the doc
185      */
186      public String storeDoc(SXDocumentType collection, Document xdoc, String id, 
187              FeatureUser user, boolean doNotify) throws SXException {
188          
189          if (user == null){
190                 SXException ex = new SXException("Please specify a valid user to update a document");
191                 ex.setHTTPResponseCode(400);
192                 throw ex;
193          }
194          
195         XmlContainer container = null;
196         XmlTransaction txn = null;
197          try {
198             txn = xmlMan.createTransaction();
199             container = (XmlContainer) containers.get(collection.getName());
200
201             // if we have no id, generate one
202             if (id == null || id.equals("")){
203                 id = collection.createDocumentName(null);
204             }
205             
206             log.debug("Storing new document with new id " + id); 
207             // always set the id in the document: 
208             setIDInDoc(xdoc, id);
209             // revision for new docs is always 1
210             long newRev = 1;
211             XmlDocument xmldoc = xmlMan.createDocument();
212             // write the new Revision to the document
213             setRevInDoc(xdoc, newRev);
214             setEnterDateInDoc(xdoc);
215             setEnteredByInDoc(xdoc, user.getUserId());
216             setLastModifiedInDoc(xdoc);
217             setLastChangedByInDoc(xdoc, user.getUserId());
218             XMLOutputter out = new XMLOutputter();
219             xmldoc.setContent(out.outputString(xdoc));
220             xmldoc.setName(id);
221             log.debug("Writing with ID and Ref: " + id + " " + newRev);
222             addToContainer(txn, container, xmldoc);
223             xmldoc.delete();
224             if (doNotify) {
225                 notifyOnInsert(collection, id, xdoc, user);
226             }
227             txn.commit();
228             txn.delete();
229             syncContainers();
230         } catch (Exception e1) {
231             if (txn != null){
232                 try {
233                     txn.abort();
234                 } catch (XmlException e2) {
235                     e1.printStackTrace();
236                     txn.delete();
237                     throw new SXException(e2);
238                 }
239                 txn.delete();
240             }
241             throw new SXException(e1);
242         }
243         return id;
244     }
245     
246     
247
248      /**
249      * Importing a document which means the same as storeDoc, but the document has 
250      * an id + revision set. 
251      * If revision is 0, it will be set to 1.
252      * Modified timestamp will not be set.
253      * We assume that imported docs have <versioningSummary> fields already set.   
254     */  
255     public String importDocument(SXDocumentType collection, String id, long rev, 
256                 Document xdoc) throws SXException {
257         XmlTransaction txn = null;
258         try {
259         txn = xmlMan.createTransaction();
260         id = importDocument(txn, collection, id, rev, xdoc);
261         txn.commit();
262         //syncContainers();
263         txn.delete();
264         } catch (Exception e1) {
265             try {
266                 txn.abort();
267                 txn.delete();
268             } catch (XmlException e) {
269                 e.printStackTrace();
270             }
271             throw new SXException(e1);
272         }
273         return id;
274     }
275      
276     
277      /**
278      * importing a document inside a transaction
279      */
280     private String importDocument(XmlTransaction txn, SXDocumentType collection, 
281             String id, long rev, Document xdoc) throws SXException {
282         // contraints
283         if (id == null){
284             SXException e = new SXException("Illegal ID=null - for import mode!");
285             e.setHTTPResponseCode(400);
286             throw e;
287         }
288         log.debug("Importing doc with id: " + id + " rev: " + rev);
289         XmlContainer container = null;        
290         try {
291             container = (XmlContainer) containers.get(collection.getName());
292             String docName = collection.createDocumentName(id);
293             setIDInDoc(xdoc, id);
294             XmlDocument xmldoc = xmlMan.createDocument();
295             if (rev == 0){
296                 rev = 1;
297             }
298             setRevInDoc(xdoc, rev);
299             XMLOutputter out = new XMLOutputter();
300             out.setFormat(Format.getRawFormat());
301             xmldoc.setContent(out.outputString(xdoc));
302             xmldoc.setName( docName );    
303             log.debug("Writing with ID and Ref: " + id + " " + rev);
304             addToContainer(txn, container, xmldoc);
305             xmldoc.delete();
306         } catch (Exception e1) {
307             throw new SXException(e1);
308         }
309         return id;
310     }
311     
312     
313     
314     /**
315      * Updating a document that already exists inside the db. The revisions of
316      * the stored and the new document must match, otherwise we have a revision
317      * conflict.
318      */
319     public void updateDocument(SXDocumentType collection, String id, long rev, Document xdoc,
320             FeatureUser user, boolean doNotify) throws SXException {
321
322         // check contraints
323         if (rev == 0) {
324             SXException ex = new SXException("Please specify your document revision");
325             ex.setHTTPResponseCode(400);
326             throw ex;
327         } else if (!(id != null && rev > 0)) {
328             SXException ex = new SXException("Please specify your document revision and id");
329             ex.setHTTPResponseCode(400);
330             throw ex;
331         } else if (user == null) {
332             SXException ex = new SXException("Please specify a valid user to update a document");
333             ex.setHTTPResponseCode(400);
334             throw ex;
335         }
336
337         // check if container is read-only
338         Properties conf = propMan.getConfig(collection.getName());
339         if (conf != null && conf.containsKey("read-only") && 
340                 conf.getProperty("read-only").equalsIgnoreCase("true")) {
341             SXException ex = new SXException("This container (" + 
342                     collection.getName() + ") is not configured for writing.");
343             ex.setHTTPResponseCode(400);
344             throw ex;
345         }
346
347         log.debug("updating doc with id: " + id + " and old-rev: " + rev);
348         XmlContainer container = null;
349         XmlTransaction txn = null;
350
351         try {
352             txn = xmlMan.createTransaction();
353             String docName = collection.createDocumentName(id);
354             container = (XmlContainer) containers.get(collection.getName());
355
356             // get the original document
357             // TODO: theoretically we have to lock the db for this doc-id here
358             // to avoid concurrent updates
359             SXDocument origDoc = getDocument(collection, id, 0);
360             Document histDoc = origDoc.getJDomDocument();
361             long origRevLong = origDoc.getRevision();
362             if (origRevLong != rev) {
363                 SXException ex = new SXException("Revision conflict: Your revision: " + rev
364                         + ", Server revision " + origRevLong);
365                 ex.setHTTPResponseCode(409);
366                 ex.setErrCode(21);
367                 throw ex;
368             }
369             // Ok, original document revision and the new revision are the same.
370             // That means we can update and have to return a new revision here.
371
372             XmlDocument xmldoc = xmlMan.createDocument();
373             XMLOutputter out = new XMLOutputter();
374             out.setFormat(Format.getRawFormat());
375             // write the new Revision to the document
376             setRevInDoc(xdoc, origRevLong + 1);
377             setIDInDoc(xdoc, id);
378             setEnterDateInDoc(xdoc, origDoc.getEnterDate());
379             setEnteredByInDoc(xdoc, origDoc.getEnterPerson());
380             setLastModifiedInDoc(xdoc);
381             setLastChangedByInDoc(xdoc, user.getUserId());
382             xmldoc.setContent(out.outputString(xdoc));
383             xmldoc.setName(docName);
384
385             log.debug("Writing with ID and new Rev: " + id + " " + (origRevLong + 1));
386             updateContainer(txn, container, xmldoc);
387             xmldoc.delete();
388
389             // now store the old doc in history:
390             String histName = collection.getName() + "-history";
391             log.debug("History container is: " + histName);
392             if (!containers.containsKey(histName)) {
393                 // container does not exist yet, -> create
394                 log.debug("Creating new history container for " + collection.getName());
395                 createContainer(histName);
396             }
397             log.debug("Storing doc to history");
398             importDocument(txn, new SXDocumentType(histName), id + "_" + origRevLong, 0, (Document) histDoc
399                     .clone());
400             if (doNotify) {
401                 // start update notifications
402                 notifyOnUpdate(collection, id, histDoc, xdoc, user);
403             }
404             txn.commit();
405             syncContainers();
406         } catch (SXException e1) {
407             try {
408                 txn.abort();
409                 txn.delete();
410             } catch (XmlException e) {
411                 throw new SXException(e);
412             }
413             throw new SXException(e1);
414         } catch (Exception e1) {
415             try {
416                 txn.abort();
417                 txn.delete();
418             } catch (XmlException e) {
419                 throw new SXException(e);
420             }
421             throw new SXException(e1);
422         }
423     }
424     
425     
426     
427     /**
428      * Updates the file in the container. Xmlman and container must be opened
429      * and closed in calling method. Can be part of a Transaktion created in the
430      * calling method.
431      */
432     private void updateContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) throws Exception {
433         XmlUpdateContext context = null;
434         try {
435             context = xmlMan.createUpdateContext();
436             container.updateDocument(txn, xmldoc, context);
437             context.delete();
438         } catch (XmlException e) {
439             if (context != null){
440                 context.delete();
441             }
442             log.error("Exception during container update: " + e.getMessage());
443             throw e;
444         }
445     }
446     
447     /**
448      * Stores the file in the container. 
449      * Xmlman and container must be opened and closed in calling method. 
450      * Can be part of a Transaktio created in the calling method.
451      */
452     private void addToContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) 
453         throws Exception {
454         XmlUpdateContext context = null;
455         try {
456             context = xmlMan.createUpdateContext();
457             container.putDocument(txn, xmldoc, context);
458             context.delete();
459         } catch (XmlException e) {
460             if (context != null){
461                 context.delete();
462             }
463             log.error("Exception during container update: " + e.getMessage());
464             throw e;
465         }
466     }
467     
468     
469     
470     /**
471      * Delete a document in a container
472      * @param docType
473      * @param id
474      * @throws SXException
475      */
476     public void deleteDocument (SXDocumentType docType, String id) throws SXException {
477         XmlUpdateContext ctxt = null;
478         XmlTransaction txn = null;
479         log.debug("Deleting document: " + docType.getName() + " " + id);
480         try {
481             txn = xmlMan.createTransaction();
482             ctxt = xmlMan.createUpdateContext();
483             // get the right container
484             XmlContainer container = (XmlContainer) containers.get(docType.getName());
485             container.deleteDocument(txn, id, ctxt);
486             txn.commit();
487             txn.delete();
488             //syncContainers();
489         } catch (XmlException e){
490             try {
491                 txn.abort();
492                 txn.delete();
493             } catch (XmlException ex) {
494                 throw new SXException(ex);
495             }
496             SXException sxe = new SXException("Document with id " + id + 
497                     " could not be deleted: " + e.getMessage());
498             throw sxe;
499         } finally {
500             if (ctxt != null){
501                 ctxt.delete();
502             }
503         }
504     }
505     
506         
507     /**
508      * return a single document identified by its name and revision. 
509      * The name is the unique id of the document in the db..
510      * 
511      * @param the document type (collection)
512      * @param id the document id in that collection
513      * @param the revision as String (0 means latest revision)
514      * @return - a SX document which is basically a XmlDocument object
515      * @throws Exception - Whrn document is not found or anoth error happens
516      */
517     public SXDocument getDocument(SXDocumentType docType, String id, long rev)
518             throws SXException {
519         SXDocument latestDoc, targetDoc;
520         String name = docType.createDocumentName(id);
521         XmlContainer container = null;
522         XmlContainer histcontainer = null;
523         try {
524             // get the right container
525             container = (XmlContainer) containers.get(docType.getName());
526
527             log.debug("Reading document " + docType.getName() + "/" + name);
528             // do not use XMLDocumentConfig
529             XmlDocument doc = container.getDocument(name);
530             XmlValue xmlValue = new XmlValue(doc);
531             latestDoc = new DbxmlSXDocument(docType.getName(), xmlValue.getFirstChild().getNextSibling());
532             doc.delete();
533             xmlValue.delete();
534             // check if document is requested from history or production
535             // container:
536             long latestrev = latestDoc.getRevision();
537             // rev == 0 means latest document
538             if (rev != 0 && rev < latestrev) {
539                 log.debug("Old revision requested, switching to history container");
540                 histcontainer = (XmlContainer) containers.get(docType.getName() + "-history");
541                 if (histcontainer != null) {
542                     String histName = id + "_" + rev;
543                     log.debug("Reading document " + histName);
544                     XmlDocument histDoc = histcontainer.getDocument(histName);
545                     targetDoc = new SXDocument(docType.getName(), histDoc.getContentAsString());
546                     histDoc.delete();
547                     // replace id and revision in history-doc, so that it looks
548                     // like one from the target container:
549                     Document jdomDoc = targetDoc.getJDomDocument();
550                     setIDInDoc(jdomDoc, id);
551                     setRevInDoc(jdomDoc, rev);
552                     targetDoc = new SXDocument(docType.getName(), new XMLOutputter().outputString(jdomDoc));
553                 } else {
554                     throw new SXException("History Container Object is null!");
555                 }
556             } else if (rev > latestrev) {
557                 throw new SXException("Requested revision could not be found!");
558             } else {
559                 targetDoc = latestDoc;
560             }
561         } catch (XmlException e){
562             SXException sxe = new SXException("Document with id " + id + 
563                     " could not be found in container: " + docType.getName());
564             sxe.setErrCode(11);
565             throw sxe;
566         } catch (Exception e){
567             // rethrow Exception
568             throw new SXException(e);
569         }
570         return targetDoc;
571     }
572
573     
574     
575     /**
576      * Returning all stored documents. 
577      * @param docType
578      * @return
579      * @throws Exception
580      */
581     public SXResultList getAllDocuments(SXDocumentType docType) throws Exception {
582         DbxmlResultList  res;
583         XmlContainer container = null;
584         try {
585             container = (XmlContainer) containers.get(docType.getName());
586             XmlQueryContext qc = xmlMan.createQueryContext();
587             qc.setEvaluationType(XmlQueryContext.Lazy);            
588             XmlDocumentConfig conf = new XmlDocumentConfig();
589             XmlResults results = container.getAllDocuments(conf);
590             res = new DbxmlResultList(docType.getName(), results);
591             qc.delete();
592         } catch (Exception e) {
593             log.error("Failed to query all documents: " + e.getMessage());
594             throw new SXException(e);
595         }
596         return res;
597     }
598
599     
600     /**
601      * The next methods do XQuery on the database
602      */
603     private StringBuffer getContainerForQuery(SXDocumentType collection) throws SXException {
604             StringBuffer cStr = new StringBuffer("collection('").
605                 append(collection.getContainerFileName()).append("')");
606         return cStr;
607     }
608
609     /**
610      * Perform a XQuery-query on the database
611      * @param query - a xquery string
612      * @return a ArrayList of SXDocuments that represent the query.
613      */
614     public SXResultList queryDocuments(SXDocumentType collection, String query) throws SXException {
615         SXResultList res = null;
616
617         StringBuffer col = new StringBuffer();
618         query = query.trim();
619
620         // only allow queries for /<containername>[] atm:
621         /*
622          * if (!(query.startsWith("/" + collection.getName() + "[") &&
623          * query.endsWith("]"))){ SXException x = new SXException("Invalid
624          * querystring sent"); x.setErrCode(6); x.setHTTPResponseCode(400);
625          * throw x; }
626          */
627
628         try {
629             if (query.toLowerCase().startsWith("for ")) {
630                 // it is a flowr expression. That means that the container
631                 // string needs to be inserted.
632                 int insertPos = 0, pos = 0; 
633                 
634                 pos = query.toLowerCase().indexOf("in");
635                 if (pos > -1){
636                     insertPos = pos + 2;
637                 }
638                 
639                 // FIXME: workaround for functions:
640                 pos = query.toLowerCase().indexOf("in distinct-values(");
641                 if (pos > -1){
642                     insertPos = pos + 19;
643                 }
644                 
645                 if (insertPos > 0) {
646                     col.append(query.substring(0, insertPos));
647                     col.append(" ");
648                     col.append(getContainerForQuery(collection));
649                     col.append(query.substring(insertPos ));
650                 } else {
651                     throw new SXException("malformed flowr statemenet: Can not find keyword in");
652                 }
653             } else {
654                 // it is a normal xquery.
655                 col.append(getContainerForQuery(collection));
656                 col.append(query);
657             }
658             log.info("Executing query: " + col.toString());
659
660             if (query.length() > 0) {
661                 XmlQueryContext cntxt = xmlMan.createQueryContext();
662                 cntxt.setEvaluationType(XmlQueryContext.Lazy);
663                 cntxt.setNamespace("k", DataManager.kNameUrl);
664                 // Prepare (compile) the query
665                 XmlQueryExpression qe = xmlMan.prepare(col.toString(), cntxt);
666                 XmlResults resultSet = qe.execute(cntxt);
667                 res = new DbxmlResultList(collection.getName(), resultSet);
668                 qe.delete();
669                 cntxt.delete();
670             }
671         } catch (Exception e) {
672             e.printStackTrace();
673             throw new SXException(e);
674         }
675         return res;
676     }
677    
678     
679     
680     /**
681      * The XMLManager needs to open the dbxml files with the very same
682      * environment options as the environment the container was created with!
683      * 
684      * @param readonly -
685      *            the transactional subsystem.
686      */
687     private XmlManager createXMLManager() throws SXException {
688         Environment dbenv = null;
689         XmlManager xmlman = null;
690         EnvironmentConfig envConf = new EnvironmentConfig();
691         envConf.setAllowCreate(true);
692         envConf.setInitializeCache(true); // Turn on the shared memory region.
693         int maxCache = new Integer(PropertyManager.getInstance().
694                 getValue("db_cache_size", "30000000")).intValue();
695         envConf.setCacheSize(maxCache); 
696         envConf.setInitializeLocking(true); // Turn on the locking subsystem.
697         envConf.setInitializeLogging(true); // Turn on the logging subsystem.
698         envConf.setLogAutoRemove(true);
699
700         envConf.setTransactional(true); // Turn on the transactional subsystem.
701         envConf.setTxnMaxActive(20); // set to see if we are losing transactions
702         // envConf.setVerboseDeadlock(true);
703         // envConf.setVerboseRecovery(true);
704         // envConf.setVerboseReplication(true);
705         // envConf.setVerboseWaitsFor(true);
706
707         // we need enough locks
708         envConf.setMaxLockers(15000);
709         envConf.setMaxLockObjects(15000);
710         envConf.setMaxLocks(15000);
711
712         //envConf.setRunRecovery(true);
713         // if private=false we can attach with scripts to the db and do
714         // backups while keeper is running
715         envConf.setThreaded(true); // already default
716         envConf.setPrivate(false);
717         File containerDir = new File(containerPath);
718
719         try {
720             dbenv = new Environment(containerDir, envConf);
721             xmlman = new XmlManager(dbenv, null);
722             // set internal storage type of container
723             xmlman.setDefaultContainerType(XmlContainer.NodeContainer);
724         } catch (FileNotFoundException e) {
725             log.error("getXMLManager: Cannot open file: " + e.getMessage());
726             e.printStackTrace();
727             throw new SXException(e);
728         } catch (Exception e) {
729             log.error("getXMLManager: " + e.getMessage());
730             e.printStackTrace();
731             throw new SXException(e);
732         }
733         return xmlman;
734     }   
735     
736     
737     
738     /**
739      * Close all containers and the xmlmanager. This is needed to clearly shut
740      * down the db.
741      */
742     public void close() {
743         try {
744             syncContainers();
745             getXmlMan().getEnvironment().checkpoint(new CheckpointConfig());
746         } catch (Exception e1) {
747             e1.printStackTrace();
748             log.error("Checkpointing failed: " + e1.getMessage());
749         }
750         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
751             XmlContainer container = (XmlContainer) it.next();
752             try {
753                 log.debug("Closing container: " + container.getName());
754                 container.close();
755             } catch (Exception e) {
756                 e.printStackTrace();
757             }
758         }
759         try {
760             log.debug("Closing xmlmanager...");
761             xmlMan.close();
762         } catch (XmlException e) {
763             e.printStackTrace();
764         }
765     }
766     
767     
768     public void syncContainers() throws Exception {
769         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
770             XmlContainer container = (XmlContainer) it.next();
771             try {
772                 container.sync();
773             } catch (Exception e) {
774                 e.printStackTrace();
775             }
776         }
777         // checkpointing only if 500kb in buffer
778         CheckpointConfig cpc = new CheckpointConfig();
779         cpc.setKBytes(500);
780         getXmlMan().getEnvironment().checkpoint(cpc);
781         getXmlMan().getEnvironment().removeOldLogFiles();
782     }
783     
784
785     private XmlContainerConfig getXMLContainerConfig() throws SXException {
786         XmlContainerConfig config = new XmlContainerConfig();
787         config.setReadOnly(false);
788         config.setAllowValidation(false);
789         config.setIndexNodes(false);
790         config.setTransactional(true);
791         return config; 
792     }
793
794
795     public HashMap getContainers() {
796         return containers;
797     }
798
799     public Set getContainerNames() {
800         return containers.keySet();
801     }
802     
803
804     public XmlManager getXmlMan() {
805         return xmlMan;
806     }
807
808     public String getType() {
809         return "dbxml";
810     }
811     
812     /**
813      * Add index on an element. 
814      * @param dataType - string, integer
815      * @param indexType - value, substring, presence
816      */
817     public void addIndexElement(String containerName, String elementName, String nameSpace, String dataType,
818             String indexType) throws Exception {
819         
820         if (dataType == null) dataType = "string";
821         if (indexType.equals("presence")) {
822             indexType = "node-element-presence-none";
823         } else if (indexType.equals("substring")) {
824             indexType = "node-element-substring-string";
825         } else if (indexType.equals("value")) {
826             if (dataType.equals("string")) {
827                 indexType = "node-element-equality-string";
828             } else if (dataType.equals("integer")) {
829                 indexType = "node-element-equality-decimal";
830             }
831         }
832         addDbxmlIndex(containerName, nameSpace, elementName, indexType);
833     }
834
835     
836     /**
837      * Add index on an element. 
838      * @param dataType - string, integer
839      */
840     public void addIndexAttribute(String containerName, String attributeName, String nameSpace, String dataType) throws Exception {
841         if (dataType == null) dataType = "string";
842         String indexType = null;
843         if (dataType.equals("string")) {
844                 indexType = "node-attribute-equality-string";
845         } else if (dataType.equals("integer")) {
846                 indexType = "node-attribute-equality-decimal";
847         }
848         addDbxmlIndex(containerName, nameSpace, attributeName, indexType);    
849         
850     }
851     
852     
853     private void addDbxmlIndex(String containerName, String nameSpace, String elementName, String indexType) throws Exception {
854         log.debug("Adding dbxml index: " + indexType + " on " + elementName);
855         if (!containers.containsKey(containerName)) {
856             throw new Exception("Container: " + containerName + " not found.");
857         }
858         XmlContainer container = (XmlContainer) containers.get(containerName);
859         XmlTransaction txn = null;
860         if (nameSpace == null) nameSpace = "";
861         try {
862             txn = xmlMan.createTransaction();
863             // Retrieve the index specification from the container
864             XmlIndexSpecification idxSpec = container.getIndexSpecification(txn);
865
866             // Add the index to the specification.
867             // If it already exists, then this does nothing.
868             idxSpec.addIndex(nameSpace, elementName, indexType);
869
870             // need update context for modifications
871             XmlUpdateContext uc = xmlMan.createUpdateContext();
872             // Set the specification back to the container
873             container.setIndexSpecification(txn, idxSpec, uc);
874
875             // Commit the index adds
876             txn.commit();
877             txn.delete();
878             uc.delete();
879         } catch (Exception e) {
880             log.error("Error adding index: " + indexType + " on " + elementName);
881             log.error("Message: " + e.getMessage());
882             if (txn != null) {
883                 txn.abort();
884                 txn.delete();
885             }
886             throw e;
887         }
888     }
889     
890     
891 }