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