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