make db cache size a config option,
[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.xml.*;
24
25 /**
26  * This is part of a base library of the SUSE XML server. The dataManager
27  * provides storage- and query capabilities to the SX database in a very generic
28  * way.
29  * 
30  * @author Klaas Freitag &lt;freitag@suse.de&gt;
31  * @author Thomas Schmidt &lt;tschmidt@suse.de&gt;
32  */
33 public class DbxmlBackend extends SxBackendIface {
34
35     private final Logger log = Logger.getLogger("de.suse.backends.dbxml");
36     private XmlManager xmlMan = null;
37     public static String dbSuffix = ".dbxml";
38     // list of opened containers, key=containername, value=container object
39     protected HashMap containers = new HashMap();
40
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     
86     /**
87      * Completely deletes the container. Use with caution ;-)
88      * @param containerName
89      * @throws SXException
90      */
91     public synchronized void deleteContainer(String containerName)
92             throws SXException {
93         log.debug("Attention: going to delete container: " + containerName);
94         if (containers.containsKey(containerName)) {
95             XmlContainer container = (XmlContainer) containers
96                     .get(containerName);
97             containers.remove(containerName);
98             XmlTransaction txn = null;
99             try {
100                 txn = xmlMan.createTransaction();
101                 container.delete();
102                 xmlMan.removeContainer(containerName + dbSuffix);
103                 txn.commit();
104                 txn.delete();
105             } catch (XmlException e) {
106                 try {
107                     txn.abort();
108                 } catch (XmlException e1) {
109                     e1.printStackTrace();
110                 }
111                 e.printStackTrace();
112                 throw new SXException(e);
113             }
114         } else {
115             log.error("cannot find container " + containerName
116                     + ", will try to delete the file anyway");
117         }
118         File dbContainer = new File(containerPath + containerName + dbSuffix);
119         if (dbContainer.exists()) {
120             log.debug("Deleting containerfile manually.");
121             dbContainer.delete();
122         }
123     }
124     
125     /**
126      * create a new container
127      * 
128      * @param containerName name of the container (e.g. "feature")
129      * @throws SXException
130      */
131     public synchronized void createContainer(String containerName) throws SXException {
132         log.debug("Creating new container " + containerName);
133         String containerFileName = containerName + dbSuffix;
134         XmlContainer container = null;
135         XmlTransaction txn = null;
136         try {
137             new File(containerPath + containerFileName);
138             txn = xmlMan.createTransaction();
139             container = xmlMan.createContainer(containerFileName, getXMLContainerConfig());
140             containers.put(containerName, container);
141             txn.commit();
142             txn.delete();
143         } catch (Exception e) {
144             log.error("Error in creating new container: " + e.getMessage());
145             if (txn != null){
146                 try {
147                     txn.abort();
148                 } catch (XmlException e1) {
149                     e1.printStackTrace();
150                     txn.delete();
151                     throw new SXException(e1);
152                 }
153                 txn.delete();
154             }
155             e.printStackTrace();
156             throw new SXException(e);
157         }
158         log.debug("created new container: " + containerName);
159     }
160     
161     
162     /**
163      * Stores a XML snippet to the database.
164      * 
165      * @param collection
166      *            the namespace of the document, ie. feature
167      * @param doc
168      *            the XML snippet as a string. Note that the doc must be a valid
169      *            XML document. It should maintain the namespace correctly to
170      *            distinguish between different sx storage types, ie. features,
171      *            contacts or package descriptions.
172      *            <p>
173      *            A good example document looks like this:
174      *            <p>
175      *            <code>
176      *        &lt;features xmlns:sxfeatures="http://sx.suse.de/features"&gt;<br>
177      *           &lt;description&gt;<br>
178      *             ...<br>
179      *           &lt;/description&gt;<br>
180      *        &lt;/features&gt;<br>
181      * </code>
182      *            <p>
183      *            It uses the xsfeatures namespace.
184      * 
185      * @return the unique name of the doc
186      */
187      public String storeDoc(SXDocumentType collection, Document xdoc, String id, 
188              FeatureUser user, boolean doNotify) throws SXException {
189          
190          if (user == null){
191                 SXException ex = new SXException("Please specify a valid user to update a document");
192                 ex.setHTTPResponseCode(400);
193                 throw ex;
194          }
195          
196         XmlContainer container = null;
197         XmlTransaction txn = null;
198          try {
199             txn = xmlMan.createTransaction();
200             container = (XmlContainer) containers.get(collection.getName());
201
202             // if we have no id, generate one
203             if (id == null || id.equals("")){
204                 id = collection.createDocumentName(null);
205             }
206             
207             log.debug("Storing new document with new id " + id); 
208             // always set the id in the document: 
209             setIDInDoc(xdoc, id);
210             // revision for new docs is always 1
211             long newRev = 1;
212             XmlDocument xmldoc = xmlMan.createDocument();
213             // write the new Revision to the document
214             setRevInDoc(xdoc, newRev);
215             setEnterDateInDoc(xdoc);
216             setEnteredByInDoc(xdoc, user.getUserId());
217             setLastModifiedInDoc(xdoc);
218             setLastChangedByInDoc(xdoc, user.getUserId());
219             XMLOutputter out = new XMLOutputter();
220             xmldoc.setContent(out.outputString(xdoc));
221             xmldoc.setName(id);
222             log.debug("Writing with ID and Ref: " + id + " " + newRev);
223             addToContainer(txn, container, xmldoc);
224             xmldoc.delete();
225             if (doNotify) {
226                 notifyOnInsert(collection, id, xdoc, user);
227             }
228             txn.commit();
229             txn.delete();
230             syncContainers();
231         } catch (Exception e1) {
232             if (txn != null){
233                 try {
234                     txn.abort();
235                 } catch (XmlException e2) {
236                     e1.printStackTrace();
237                     txn.delete();
238                     throw new SXException(e2);
239                 }
240                 txn.delete();
241             }
242             throw new SXException(e1);
243         }
244         return id;
245     }
246     
247     
248
249      /**
250      * Importing a document which means the same as storeDoc, but the document has 
251      * an id + revision set. 
252      * If revision is 0, it will be set to 1.
253      * Modified timestamp will not be set.
254      * We assume that imported docs have <versioningSummary> fields already set.   
255     */  
256     public String importDocument(SXDocumentType collection, String id, long rev, 
257                 Document xdoc) throws SXException {
258         XmlTransaction txn = null;
259         try {
260         txn = xmlMan.createTransaction();
261         id = importDocument(txn, collection, id, rev, xdoc);
262         txn.commit();
263         syncContainers();
264         txn.delete();
265         } catch (Exception e1) {
266             try {
267                 txn.abort();
268                 txn.delete();
269             } catch (XmlException e) {
270                 e.printStackTrace();
271             }
272             throw new SXException(e1);
273         }
274         return id;
275     }
276      
277     
278      /**
279      * importing a document inside a transaction
280      */
281     private String importDocument(XmlTransaction txn, SXDocumentType collection, 
282             String id, long rev, Document xdoc) throws SXException {
283         // contraints
284         if (id == null){
285             SXException e = new SXException("Illegal ID=null - for import mode!");
286             e.setHTTPResponseCode(400);
287             throw e;
288         }
289         log.debug("Importing doc with id: " + id + " rev: " + rev);
290         XmlContainer container = null;        
291         try {
292             container = (XmlContainer) containers.get(collection.getName());
293             String docName = collection.createDocumentName(id);
294             setIDInDoc(xdoc, id);
295             XmlDocument xmldoc = xmlMan.createDocument();
296             if (rev == 0){
297                 rev = 1;
298             }
299             setRevInDoc(xdoc, rev);
300             XMLOutputter out = new XMLOutputter();
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             // write the new Revision to the document
375             setRevInDoc(xdoc, origRevLong + 1);
376             setIDInDoc(xdoc, id);
377             setEnterDateInDoc(xdoc, origDoc.getEnterDate());
378             setEnteredByInDoc(xdoc, origDoc.getEnterPerson());
379             setLastModifiedInDoc(xdoc);
380             setLastChangedByInDoc(xdoc, user.getUserId());
381             xmldoc.setContent(out.outputString(xdoc));
382             xmldoc.setName(docName);
383
384             log.debug("Writing with ID and new Rev: " + id + " " + (origRevLong + 1));
385             updateContainer(txn, container, xmldoc);
386             xmldoc.delete();
387
388             // now store the old doc in history:
389             String histName = collection.getName() + "-history";
390             log.debug("History container is: " + histName);
391             if (!containers.containsKey(histName)) {
392                 // container does not exist yet, -> create
393                 log.debug("Creating new history container for " + collection.getName());
394                 createContainer(histName);
395             }
396             log.debug("Storing doc to history");
397             importDocument(txn, new SXDocumentType(histName), id + "_" + origRevLong, 0, (Document) histDoc
398                     .clone());
399             if (doNotify) {
400                 // start update notifications
401                 notifyOnUpdate(collection, id, histDoc, xdoc, user);
402             }
403             txn.commit();
404             syncContainers();
405         } catch (SXException 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         } catch (Exception e1) {
414             try {
415                 txn.abort();
416                 txn.delete();
417             } catch (XmlException e) {
418                 throw new SXException(e);
419             }
420             throw new SXException(e1);
421         }
422     }
423     
424     
425     
426     /**
427      * Updates the file in the container. Xmlman and container must be opened
428      * and closed in calling method. Can be part of a Transaktion created in the
429      * calling method.
430      */
431     private void updateContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) throws Exception {
432         XmlUpdateContext context = null;
433         try {
434             context = xmlMan.createUpdateContext();
435             container.updateDocument(txn, xmldoc, context);
436             context.delete();
437         } catch (XmlException e) {
438             if (context != null){
439                 context.delete();
440             }
441             log.error("Exception during container update: " + e.getMessage());
442             throw e;
443         }
444     }
445     
446     /**
447      * Stores the file in the container. 
448      * Xmlman and container must be opened and closed in calling method. 
449      * Can be part of a Transaktio created in the calling method.
450      */
451     private void addToContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) 
452         throws Exception {
453         XmlUpdateContext context = null;
454         try {
455             context = xmlMan.createUpdateContext();
456             container.putDocument(txn, xmldoc, context);
457             context.delete();
458         } catch (XmlException e) {
459             if (context != null){
460                 context.delete();
461             }
462             log.error("Exception during container update: " + e.getMessage());
463             throw e;
464         }
465     }
466     
467     
468     
469     /**
470      * Delete a document in a container
471      * @param docType
472      * @param id
473      * @throws SXException
474      */
475     public void deleteDocument (SXDocumentType docType, String id) throws SXException {
476         XmlUpdateContext ctxt = null;
477         XmlTransaction txn = null;
478         try {
479             txn = xmlMan.createTransaction();
480             ctxt = xmlMan.createUpdateContext();
481             // get the right container
482             XmlContainer container = (XmlContainer) containers.get(docType.getName());
483             container.deleteDocument(txn, docType.getName() + "-" + id, ctxt);
484             txn.commit();
485             txn.delete();
486             syncContainers();
487         } catch (XmlException e){
488             try {
489                 txn.abort();
490                 txn.delete();
491             } catch (XmlException ex) {
492                 throw new SXException(ex);
493             }
494             SXException sxe = new SXException("Document with id " + id + 
495                     " could not be deleted: " + e.getMessage());
496             throw sxe;
497         } finally {
498             if (ctxt != null){
499                 ctxt.delete();
500             }
501         }
502     }
503     
504         
505     /**
506      * return a single document identified by its name and revision. 
507      * The name is the unique id of the document in the db..
508      * 
509      * @param the document type (collection)
510      * @param id the document id in that collection
511      * @param the revision as String (0 means latest revision)
512      * @return - a SX document which is basically a XmlDocument object
513      * @throws Exception - Whrn document is not found or anoth error happens
514      */
515     public SXDocument getDocument(SXDocumentType docType, String id, long rev)
516             throws SXException {
517         SXDocument latestDoc, targetDoc;
518         String name = docType.createDocumentName(id);
519         XmlContainer container = null;
520         XmlContainer histcontainer = null;
521         try {
522             // get the right container
523             container = (XmlContainer) containers.get(docType.getName());
524
525             log.debug("Reading document " + name);
526             // do not use XMLDocumentConfig
527             XmlDocument doc = container.getDocument(name);
528             latestDoc = new SXDocument(doc.getContentAsString());
529             doc.delete();
530             // check if document is requested from history or production
531             // container:
532             long latestrev = latestDoc.getRevision();
533             // rev == 0 means latest document
534             if (rev != 0 && rev < latestrev) {
535                 log.debug("Old revision requested, switching to history container");
536                 histcontainer = (XmlContainer) containers.get(docType.getName() + "-history");
537                 if (histcontainer != null) {
538                     String histName = id + "_" + rev;
539                     log.debug("Reading document " + histName);
540                     XmlDocument histDoc = histcontainer.getDocument(histName);
541                     targetDoc = new SXDocument(histDoc.getContentAsString());
542                     histDoc.delete();
543                     // replace id and revision in history-doc, so that it looks
544                     // like one from the target container:
545                     Document jdomDoc = targetDoc.getJDomDocument();
546                     setIDInDoc(jdomDoc, id);
547                     setRevInDoc(jdomDoc, rev);
548                     targetDoc.setContent(new XMLOutputter()
549                             .outputString(jdomDoc));
550                 } else {
551                     throw new SXException("History Container Object is null!");
552                 }
553             } else if (rev > latestrev) {
554                 throw new SXException("Requested revision could not be found!");
555             } else {
556                 targetDoc = latestDoc;
557             }
558         } catch (XmlException e){
559             SXException sxe = new SXException("Document with id " + id + 
560                     " could not be found in container: " + docType.getName());
561             sxe.setErrCode(11);
562             throw sxe;
563         } catch (Exception e){
564             // rethrow Exception
565             throw new SXException(e);
566         }
567         return targetDoc;
568     }
569
570     
571     
572     /**
573      * Returning all stored documents. 
574      * @param docType
575      * @return
576      * @throws Exception
577      */
578     public  ArrayList getAllDocuments(SXDocumentType docType) throws Exception {
579         ArrayList res = new ArrayList();
580         XmlContainer container = null;
581         try {
582             container = (XmlContainer) containers.get(docType.getName());
583             XmlQueryContext qc = xmlMan.createQueryContext();
584             qc.setEvaluationType(XmlQueryContext.Lazy);
585             //qc.setReturnType(XmlQueryContext.LiveValues);
586             
587             XmlResults results = container.getAllDocuments(new XmlDocumentConfig());
588             XmlValue val = results.next();
589             while (val != null) {
590                 SXDocument sxdoc = new SXDocument(val.asString());
591                 res.add(sxdoc);
592                 val.delete();
593                 val = results.next();
594             }
595             results.delete();
596             qc.delete();
597         } catch (Exception e) {
598             log.error("Failed to query all documents: " + e.getMessage());
599             throw new SXException(e);
600         }
601         return res;
602     }
603
604     
605     /**
606      * The next methods do XQuery on the database
607      */
608     private StringBuffer getContainerForQuery(SXDocumentType collection) throws SXException {
609             StringBuffer cStr = new StringBuffer("collection('").
610                 append(collection.getContainerFileName()).append("')");
611         return cStr;
612     }
613
614     /**
615      * Perform a XQuery-query on the database
616      * @param query - a xquery string
617      * @return a ArrayList of SXDocuments that represent the query.
618      */
619     public ArrayList queryDocuments(SXDocumentType collection, String query) throws SXException {
620         ArrayList res = new ArrayList();
621
622         StringBuffer col = new StringBuffer();
623         long time = System.currentTimeMillis();
624         query = query.trim();
625
626         // only allow queries for /<containername>[] atm:
627         /*
628          * if (!(query.startsWith("/" + collection.getName() + "[") &&
629          * query.endsWith("]"))){ SXException x = new SXException("Invalid
630          * querystring sent"); x.setErrCode(6); x.setHTTPResponseCode(400);
631          * throw x; }
632          */
633
634         try {
635             if (query.toLowerCase().startsWith("for ")) {
636                 // it is a flowr expression. That means that the container
637                 // string needs to be inserted.
638                 int insertPos = 0, pos = 0; 
639                 
640                 pos = query.toLowerCase().indexOf("in");
641                 if (pos > -1){
642                     insertPos = pos + 2;
643                 }
644                 
645                 // FIXME: workaround for functions:
646                 pos = query.toLowerCase().indexOf("in distinct-values(");
647                 if (pos > -1){
648                     insertPos = pos + 19;
649                 }
650                 
651                 if (insertPos > 0) {
652                     col.append(query.substring(0, insertPos));
653                     col.append(" ");
654                     col.append(getContainerForQuery(collection));
655                     col.append(query.substring(insertPos ));
656                 } else {
657                     throw new SXException("malformed flowr statemenet: Can not find keyword in");
658                 }
659             } else {
660                 // it is a normal xquery.
661                 col.append(getContainerForQuery(collection));
662                 col.append(query);
663             }
664             log.info("Executing query: " + col.toString());
665
666             if (query.length() > 0) {
667                 XmlQueryContext cntxt = xmlMan.createQueryContext();
668                 cntxt.setEvaluationType(XmlQueryContext.Lazy);
669                 cntxt.setNamespace("k", DataManager.kNameUrl);
670                 // Prepare (compile) the query
671                 XmlQueryExpression qe = xmlMan.prepare(col.toString(), cntxt);
672
673                 XmlResults resultSet = qe.execute(cntxt);
674                 log.debug("executing query took: " + (System.currentTimeMillis() - time) + "ms");
675
676                 XmlValue val = resultSet.next();
677                 log.debug("first value after: " + (System.currentTimeMillis() - time) + "ms");
678                 while (val != null) {
679                     res.add(new SXDocument(val.asString()));
680                     val.delete();
681                     val = resultSet.next();
682                 }
683                 log.debug("resultlist ready after : " + (System.currentTimeMillis() - time) + "ms");
684                 resultSet.delete();
685                 qe.delete();
686                 cntxt.delete();
687             }
688         } catch (XmlException e) {
689             e.printStackTrace();
690             throw new SXException(e);
691         }
692         time = System.currentTimeMillis() - time;
693         log.debug("executing query + building documents took: " + time + "ms");
694         if (time > 6000) {
695             log.warn("Slow query detected, please optimize !");
696         }
697         return res;
698     }
699    
700     
701     
702     /**
703      * The XMLManager needs to open the dbxml files with the very same
704      * environment options as the environment the container was created with!
705      * 
706      * @param readonly -
707      *            the transactional subsystem.
708      */
709     private XmlManager createXMLManager() throws SXException {
710         Environment dbenv = null;
711         XmlManager xmlman = null;
712         EnvironmentConfig envConf = new EnvironmentConfig();
713         envConf.setAllowCreate(true);
714         envConf.setInitializeCache(true); // Turn on the shared memory region.
715         int maxCache = new Integer(PropertyManager.getInstance().
716                 getValue("db_cache_size", "30000000")).intValue();
717         envConf.setCacheSize(maxCache); 
718         envConf.setInitializeLocking(true); // Turn on the locking subsystem.
719         envConf.setInitializeLogging(true); // Turn on the logging subsystem.
720         envConf.setLogAutoRemove(true);
721
722         envConf.setTransactional(true); // Turn on the transactional subsystem.
723         envConf.setTxnMaxActive(20); // set to see if we are losing transactions
724         // envConf.setVerboseDeadlock(true);
725         // envConf.setVerboseRecovery(true);
726         // envConf.setVerboseReplication(true);
727         // envConf.setVerboseWaitsFor(true);
728
729         // we need enough locks
730         envConf.setMaxLockers(15000);
731         envConf.setMaxLockObjects(15000);
732         envConf.setMaxLocks(15000);
733
734         //envConf.setRunRecovery(true);
735         // if private=false we can attach with scripts to the db and do
736         // backups while keeper is running
737         envConf.setThreaded(true); // already default
738         envConf.setPrivate(false);
739         File containerDir = new File(containerPath);
740
741         try {
742             dbenv = new Environment(containerDir, envConf);
743             xmlman = new XmlManager(dbenv, null);
744             // set internal storage type of container
745             xmlman.setDefaultContainerType(XmlContainer.NodeContainer);
746         } catch (FileNotFoundException e) {
747             log.error("getXMLManager: Cannot open file: " + e.getMessage());
748             e.printStackTrace();
749             throw new SXException(e);
750         } catch (Exception e) {
751             log.error("getXMLManager: " + e.getMessage());
752             e.printStackTrace();
753             throw new SXException(e);
754         }
755         return xmlman;
756     }   
757     
758     
759     
760     /**
761      * Close all containers and the xmlmanager. This is needed to clearly shut
762      * down the db.
763      */
764     public void close(){
765         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
766             XmlContainer container = (XmlContainer) it.next();
767             try {
768                 log.debug("Closing container: " + container.getName());
769                 container.close();
770             } catch (Exception e) {
771                 e.printStackTrace();
772             }
773         }
774         try {
775             log.debug("Closing xmlmanager...");
776             xmlMan.close();
777         } catch (XmlException e) {
778             e.printStackTrace();
779         }
780     }
781     
782     
783     public void syncContainers(){
784         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
785             XmlContainer container = (XmlContainer) it.next();
786             try {
787                 //log.debug("syncing container: " + container.getName());
788                 container.sync();
789             } catch (Exception e) {
790                 e.printStackTrace();
791             }
792         }
793         /*try {
794             LockStats theLockStats = xmlMan.getEnvironment().
795                 getLockStats(StatsConfig.DEFAULT);
796             log.debug("num lockers:" + theLockStats.getNumLockers());
797             log.debug("num locks:" + theLockStats.getNumLocks());
798         } catch (Exception e) {
799             log.error("Unable to get log stats!");
800         }*/
801     }
802     
803
804     private XmlContainerConfig getXMLContainerConfig() throws SXException {
805         XmlContainerConfig config = new XmlContainerConfig();
806         config.setReadOnly(false);
807         config.setAllowValidation(false);
808         config.setIndexNodes(false);
809         config.setTransactional(true);
810         return config; 
811     }
812
813
814     public HashMap getContainers() {
815         return containers;
816     }
817
818     public Set getContainerNames() {
819         return containers.keySet();
820     }
821     
822
823     public XmlManager getXmlMan() {
824         return xmlMan;
825     }
826
827     public String getType() {
828         return "dbxml";
829     }
830
831     
832 }