first working version of bulkdownload
[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             sxe.setErrCode(11);
553             throw sxe;
554         } catch (Exception e){
555             // rethrow Exception
556             throw new SXException(e);
557         }
558         return targetDoc;
559     }
560
561     
562     public SXResultList getDocumentsById(SXDocumentType docType, List IdList) throws Exception {
563         
564         StringBuffer query = new StringBuffer(); 
565         query.append("/").append(docType.getName()).append("[");
566         
567         for (Iterator it = IdList.iterator(); it.hasNext(); ){
568             String id = (String) it.next(); 
569             if (it.hasNext()){
570                 query.append("@k:id=\"").append(id).append("\" or ");
571             } else {
572                 query.append("@k:id=\"").append(id).append("\"]");
573             }
574         }
575         
576         return queryDocuments(docType, query.toString());
577     }
578     
579     
580     /**
581      * Returning all stored documents. 
582      * @param docType
583      * @return
584      * @throws Exception
585      */
586     public SXResultList getAllDocuments(SXDocumentType docType) throws Exception {
587         DbxmlResultList  res;
588         XmlContainer container = null;
589         try {
590             container = (XmlContainer) containers.get(docType.getName());
591             XmlQueryContext qc = xmlMan.createQueryContext();
592             qc.setEvaluationType(XmlQueryContext.Lazy);            
593             XmlDocumentConfig conf = new XmlDocumentConfig();
594             XmlResults results = container.getAllDocuments(conf);
595             res = new DbxmlResultList(docType.getName(), results);
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 SXResultList queryDocuments(SXDocumentType collection, String query) throws SXException {
620         SXResultList res = null;
621
622         StringBuffer col = new StringBuffer();
623         query = query.trim();
624
625         // only allow queries for /<containername>[] atm:
626         /*
627          * if (!(query.startsWith("/" + collection.getName() + "[") &&
628          * query.endsWith("]"))){ SXException x = new SXException("Invalid
629          * querystring sent"); x.setErrCode(6); x.setHTTPResponseCode(400);
630          * throw x; }
631          */
632
633         try {
634             if (query.toLowerCase().startsWith("for ")) {
635                 // it is a flowr expression. That means that the container
636                 // string needs to be inserted.
637                 int insertPos = 0, pos = 0; 
638                 
639                 pos = query.toLowerCase().indexOf("in");
640                 if (pos > -1){
641                     insertPos = pos + 2;
642                 }
643                 
644                 // FIXME: workaround for functions:
645                 pos = query.toLowerCase().indexOf("in distinct-values(");
646                 if (pos > -1){
647                     insertPos = pos + 19;
648                 }
649                 
650                 if (insertPos > 0) {
651                     col.append(query.substring(0, insertPos));
652                     col.append(" ");
653                     col.append(getContainerForQuery(collection));
654                     col.append(query.substring(insertPos ));
655                 } else {
656                     throw new SXException("malformed flowr statemenet: Can not find keyword in");
657                 }
658             } else {
659                 // it is a normal xquery.
660                 col.append(getContainerForQuery(collection));
661                 col.append(query);
662             }
663             log.info("Executing query: " + col.toString());
664
665             if (query.length() > 0) {
666                 XmlQueryContext cntxt = xmlMan.createQueryContext();
667                 cntxt.setEvaluationType(XmlQueryContext.Lazy);
668                 cntxt.setNamespace("k", DataManager.kNameUrl);
669                 // Prepare (compile) the query
670                 XmlQueryExpression qe = xmlMan.prepare(col.toString(), cntxt);
671                 XmlResults resultSet = qe.execute(cntxt);
672                 res = new DbxmlResultList(collection.getName(), resultSet);
673                 qe.delete();
674                 cntxt.delete();
675             }
676         } catch (Exception e) {
677             e.printStackTrace();
678             throw new SXException(e);
679         }
680         return res;
681     }
682    
683     
684     
685     /**
686      * The XMLManager needs to open the dbxml files with the very same
687      * environment options as the environment the container was created with!
688      * 
689      * @param readonly -
690      *            the transactional subsystem.
691      */
692     private XmlManager createXMLManager() throws SXException {
693         Environment dbenv = null;
694         XmlManager xmlman = null;
695         EnvironmentConfig envConf = new EnvironmentConfig();
696         envConf.setAllowCreate(true);
697         envConf.setInitializeCache(true); // Turn on the shared memory region.
698         int maxCache = new Integer(PropertyManager.getInstance().
699                 getValue("db_cache_size", "30000000")).intValue();
700         envConf.setCacheSize(maxCache); 
701         envConf.setInitializeLocking(true); // Turn on the locking subsystem.
702         envConf.setInitializeLogging(true); // Turn on the logging subsystem.
703         envConf.setLogAutoRemove(true);
704
705         envConf.setTransactional(true); // Turn on the transactional subsystem.
706         envConf.setTxnMaxActive(20); // set to see if we are losing transactions
707         // envConf.setVerboseDeadlock(true);
708         // envConf.setVerboseRecovery(true);
709         // envConf.setVerboseReplication(true);
710         // envConf.setVerboseWaitsFor(true);
711
712         envConf.setLockDetectMode(LockDetectMode.DEFAULT);
713         envConf.setTxnTimeout(2*1000000);
714         envConf.setLockTimeout(2*1000000);
715         
716         
717         // we need enough locks
718         envConf.setMaxLockers(15000);
719         envConf.setMaxLockObjects(15000);
720         envConf.setMaxLocks(15000);
721
722         //envConf.setRunRecovery(true);
723         // if private=false we can attach with scripts to the db and do
724         // backups while keeper is running
725         envConf.setThreaded(true); // already default
726         envConf.setPrivate(false);
727         File containerDir = new File(containerPath);
728
729         try {
730             dbenv = new Environment(containerDir, envConf);
731             xmlman = new XmlManager(dbenv, null);
732             // set internal storage type of container
733             xmlman.setDefaultContainerType(XmlContainer.NodeContainer);
734         } catch (FileNotFoundException e) {
735             log.error("getXMLManager: Cannot open file: " + e.getMessage());
736             e.printStackTrace();
737             throw new SXException(e);
738         } catch (Exception e) {
739             log.error("getXMLManager: " + e.getMessage());
740             e.printStackTrace();
741             throw new SXException(e);
742         }
743         return xmlman;
744     }   
745     
746     
747     
748     /**
749      * Close all containers and the xmlmanager. This is needed to clearly shut
750      * down the db.
751      */
752     public void close() {
753         try {
754             syncContainers();
755             getXmlMan().getEnvironment().checkpoint(new CheckpointConfig());
756         } catch (Exception e1) {
757             e1.printStackTrace();
758             log.error("Checkpointing failed: " + e1.getMessage());
759         }
760         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
761             XmlContainer container = (XmlContainer) it.next();
762             try {
763                 log.debug("Closing container: " + container.getName());
764                 container.close();
765             } catch (Exception e) {
766                 e.printStackTrace();
767             }
768         }
769         try {
770             log.debug("Closing xmlmanager...");
771             xmlMan.close();
772         } catch (XmlException e) {
773             e.printStackTrace();
774         }
775     }
776     
777     
778     public void syncContainers() throws Exception {
779         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
780             XmlContainer container = (XmlContainer) it.next();
781             try {
782                 container.sync();
783             } catch (Exception e) {
784                 e.printStackTrace();
785             }
786         }
787         // checkpointing only if 500kb in buffer
788         CheckpointConfig cpc = new CheckpointConfig();
789         cpc.setKBytes(500);
790         getXmlMan().getEnvironment().checkpoint(cpc);
791         getXmlMan().getEnvironment().removeOldLogFiles();
792     }
793     
794
795     private XmlContainerConfig getXMLContainerConfig() throws SXException {
796         XmlContainerConfig config = new XmlContainerConfig();
797         config.setReadOnly(false);
798         config.setAllowValidation(false);
799         config.setIndexNodes(false);
800         config.setTransactional(true);
801         return config; 
802     }
803
804
805     public HashMap getContainers() {
806         return containers;
807     }
808
809     public Set getContainerNames() {
810         return containers.keySet();
811     }
812     
813
814     public XmlManager getXmlMan() {
815         return xmlMan;
816     }
817
818     public String getType() {
819         return "dbxml";
820     }
821     
822     /**
823      * Add index on an element. 
824      * @param dataType - string, integer
825      * @param indexType - value, substring, presence
826      */
827     public void addIndexElement(String containerName, String elementName, String nameSpace, String dataType,
828             String indexType) throws Exception {
829         
830         if (dataType == null) dataType = "string";
831         if (indexType.equals("presence")) {
832             indexType = "node-element-presence-none";
833         } else if (indexType.equals("substring")) {
834             indexType = "node-element-substring-string";
835         } else if (indexType.equals("value")) {
836             if (dataType.equals("string")) {
837                 indexType = "node-element-equality-string";
838             } else if (dataType.equals("integer")) {
839                 indexType = "node-element-equality-decimal";
840             }
841         }
842         addDbxmlIndex(containerName, nameSpace, elementName, indexType);
843     }
844
845     
846     /**
847      * Add index on an element. 
848      * @param dataType - string, integer
849      */
850     public void addIndexAttribute(String containerName, String attributeName, String nameSpace, String dataType) throws Exception {
851         if (dataType == null) dataType = "string";
852         String indexType = null;
853         if (dataType.equals("string")) {
854                 indexType = "node-attribute-equality-string";
855         } else if (dataType.equals("integer")) {
856                 indexType = "node-attribute-equality-decimal";
857         }
858         addDbxmlIndex(containerName, nameSpace, attributeName, indexType);    
859         
860     }
861     
862     
863     private void addDbxmlIndex(String containerName, String nameSpace, String elementName, String indexType) throws Exception {
864         log.debug("Adding dbxml index: " + indexType + " on " + elementName);
865         if (!containers.containsKey(containerName)) {
866             throw new Exception("Container: " + containerName + " not found.");
867         }
868         XmlContainer container = (XmlContainer) containers.get(containerName);
869         XmlTransaction txn = null;
870         if (nameSpace == null) nameSpace = "";
871         try {
872             txn = xmlMan.createTransaction();
873             // Retrieve the index specification from the container
874             XmlIndexSpecification idxSpec = container.getIndexSpecification(txn);
875
876             // Add the index to the specification.
877             // If it already exists, then this does nothing.
878             idxSpec.addIndex(nameSpace, elementName, indexType);
879
880             // need update context for modifications
881             XmlUpdateContext uc = xmlMan.createUpdateContext();
882             // Set the specification back to the container
883             container.setIndexSpecification(txn, idxSpec, uc);
884
885             // Commit the index adds
886             txn.commit();
887             txn.delete();
888             uc.delete();
889         } catch (Exception e) {
890             log.error("Error adding index: " + indexType + " on " + elementName);
891             log.error("Message: " + e.getMessage());
892             if (txn != null) {
893                 txn.abort();
894                 txn.delete();
895             }
896             throw e;
897         }
898     }
899     
900     
901 }