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