new timeout
[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                 throw ex;
369             }
370             // Ok, original document revision and the new revision are the same.
371             // That means we can update and have to return a new revision here.
372
373             XmlDocument xmldoc = xmlMan.createDocument();
374             XMLOutputter out = new XMLOutputter();
375             out.setFormat(Format.getRawFormat());
376             // write the new Revision to the document
377             setRevInDoc(xdoc, origRevLong + 1);
378             setIDInDoc(xdoc, id);
379             setEnterDateInDoc(xdoc, origDoc.getEnterDate());
380             setEnteredByInDoc(xdoc, origDoc.getEnterPerson());
381             setLastModifiedInDoc(xdoc);
382             setLastChangedByInDoc(xdoc, user.getUserId());
383             xmldoc.setContent(out.outputString(xdoc));
384             xmldoc.setName(docName);
385
386             log.debug("Writing with ID and new Rev: " + id + " " + (origRevLong + 1));
387             updateContainer(txn, container, xmldoc);
388             xmldoc.delete();
389
390             // now store the old doc in history:
391             String histName = collection.getName() + "-history";
392             log.debug("History container is: " + histName);
393             if (!containers.containsKey(histName)) {
394                 // container does not exist yet, -> create
395                 log.debug("Creating new history container for " + collection.getName());
396                 createContainer(histName);
397             }
398             log.debug("Storing doc to history");
399             importDocument(txn, new SXDocumentType(histName), id + "_" + origRevLong, 0, (Document) histDoc
400                     .clone());
401             txn.commit();
402             syncContainers();
403             TriggerManager.getInstance().runPostWriteTriggers(collection, xdoc, histDoc, user);
404         } catch (SXException e1) {
405             try {
406                 txn.abort();
407                 txn.delete();
408             } catch (XmlException e) {
409                 throw new SXException(e);
410             }
411             throw e1;
412         } catch (Exception e1) {
413             try {
414                 txn.abort();
415                 txn.delete();
416             } catch (XmlException e) {
417                 throw new SXException(e);
418             }
419             throw new SXException(e1);
420         }
421     }
422     
423     
424     
425     /**
426      * Updates the file in the container. Can be part of a Transaction 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      * Can be part of a transaction created in the calling method.
447      */
448     private void addToContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) 
449         throws Exception {
450         XmlUpdateContext context = null;
451         try {
452             context = xmlMan.createUpdateContext();
453             container.putDocument(txn, xmldoc, context);
454             context.delete();
455         } catch (XmlException e) {
456             if (context != null){
457                 context.delete();
458             }
459             log.error("Exception during container update: " + e.getMessage());
460             throw e;
461         }
462     }
463     
464     
465     
466     /**
467      * Delete a document in a container
468      * @param docType
469      * @param id
470      * @throws SXException
471      */
472     @Override
473     public void deleteDocument (SXDocumentType docType, String id) throws SXException {
474         XmlUpdateContext ctxt = null;
475         XmlTransaction txn = null;
476         log.debug("Deleting document: " + docType.getName() + " " + id);
477         try {
478             txn = xmlMan.createTransaction();
479             ctxt = xmlMan.createUpdateContext();
480             // get the right container
481             XmlContainer container = (XmlContainer) containers.get(docType.getName());
482             container.deleteDocument(txn, id, ctxt);
483             txn.commit();
484             txn.delete();
485         } catch (Exception 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     @Override
514     public SXDocument getDocument(SXDocumentType docType, String id, long rev) throws SXException {
515         SXDocument latestDoc, targetDoc;
516         String name = docType.createDocumentName(id);
517         XmlContainer container = null;
518         XmlContainer histcontainer = null;
519         XmlValue xmlValue = null;
520         try {
521             container = (XmlContainer) containers.get(docType.getName());
522             // log.debug("Reading document " + docType.getName() + "/" + name);
523             xmlValue = new XmlValue(container.getDocument(name));
524             latestDoc = new DbxmlSXDocument(docType.getName(), xmlValue.getFirstChild().getNextSibling());
525             //doc.delete();
526             //xmlValue.delete();
527         } catch (XmlException e) {
528             throw new SXException("Document with id " + id + " could not be found in container: "
529                     + docType.getName());
530         } catch (Exception e) {
531             throw new SXException(e);
532         }
533         // check if document is requested from history or production
534         // container:
535         long latestrev = latestDoc.getRevision();
536         // rev == 0 means latest document
537         if (rev != 0 && rev < latestrev) {
538             log.debug("Old revision requested, switching to history container");
539             histcontainer = (XmlContainer) containers.get(docType.getName() + "-history");
540             if (histcontainer != null) {
541                 String histName = id + "_" + rev;
542                 log.debug("Reading document " + histName);
543                 try {
544                     latestDoc.delete();
545                     XmlDocument histDoc = histcontainer.getDocument(histName);
546                     targetDoc = new SXDocument(docType.getName(), histDoc.getContentAsString());
547                     histDoc.delete();
548                     // replace id and revision in history-doc, so that it looks
549                     // like one from the target container:
550                     Document jdomDoc = targetDoc.getJDomDocument();
551                     setIDInDoc(jdomDoc, id);
552                     setRevInDoc(jdomDoc, rev);
553                     targetDoc = new SXDocument(docType.getName(), new XMLOutputter().outputString(jdomDoc));
554                 } catch (XmlException e) {
555                     SXException sxe = new SXException("Document with id " + histName
556                             + " could not be found in container: " + docType.getName() + "-history");
557                     throw sxe;
558                 } catch (Exception e) {
559                     throw new SXException(e);
560                 }
561             } else {
562                 latestDoc.delete();
563                 throw new SXException("History for container " + docType.getName() + " not found.");
564             }
565         } else if (rev > latestrev) {
566             latestDoc.delete();
567             throw new SXException("Requested revision (" + rev + 
568                     ") could not be found, latest available revision is: " + latestrev);
569         } else {
570             targetDoc = latestDoc;
571         }
572         return targetDoc;
573     }
574
575     
576     @Override
577     public SXResultList getBulkResultlist(String containerName, List IdList) throws Exception {
578         return new DbxmlBulkResultList(containerName, IdList);
579     }
580     
581     
582     /**
583      * Returning all stored documents. 
584      * @param docType
585      * @return
586      * @throws Exception
587      */
588     @Override
589     public SXResultList getAllDocuments(SXDocumentType docType) throws Exception {
590         DbxmlResultList  res;
591         XmlContainer container = null;
592         try {
593             container = (XmlContainer) containers.get(docType.getName());
594             XmlQueryContext qc = xmlMan.createQueryContext();
595             qc.setEvaluationType(XmlQueryContext.Lazy);            
596             XmlDocumentConfig conf = new XmlDocumentConfig();
597             XmlResults results = container.getAllDocuments(conf);
598             res = new DbxmlResultList(docType.getName(), results);
599             qc.delete();
600         } catch (Exception e) {
601             log.error("Failed to query all documents: " + e.getMessage());
602             throw new SXException(e);
603         }
604         return res;
605     }
606
607     
608     /**
609      * The next methods do XQuery on the database
610      */
611     private StringBuffer getContainerForQuery(SXDocumentType collection) throws SXException {
612             StringBuffer cStr = new StringBuffer("collection('").
613                 append(collection.getContainerFileName()).append("')");
614         return cStr;
615     }
616
617     /**
618      * Perform a XQuery-query on the database
619      * @param query - a xquery string
620      * @return a ArrayList of SXDocuments that represent the query.
621      */
622     @Override
623     public SXResultList queryDocuments(final SXDocumentType collection, final String query) throws SXException {
624         SXResultList res = null;
625         try {
626             XmlQueryContext cntxt = xmlMan.createQueryContext();
627             // this breaks compatibility with dbxml 2.2.x
628             cntxt.setQueryTimeoutSeconds(timeOut);
629             cntxt.setEvaluationType(XmlQueryContext.Lazy);
630             cntxt.setNamespace("k", DataManager.kNameUrl);
631             // Prepare (compile) the query
632             XmlQueryExpression qe = xmlMan.prepare(prepareQueryString(query, collection), cntxt);
633             XmlResults resultSet = qe.execute(cntxt);
634             res = new DbxmlResultList(collection.getName(), resultSet);
635             qe.delete();
636             cntxt.delete();
637         } catch (Exception e) {
638             e.printStackTrace();
639             throw new SXException(e);
640         }
641         return res;
642     }
643    
644     
645     
646
647     private String prepareQueryString(String queryString, SXDocumentType collection) throws Exception {
648         StringBuffer col = new StringBuffer();
649         queryString = queryString.trim();
650
651         // only allow queries for /<containername>[] atm:
652         /*
653          * if (!(query.startsWith("/" + collection.getName() + "[") &&
654          * query.endsWith("]"))){ SXException x = new SXException("Invalid
655          * querystring sent"); x.setErrCode(6); x.setHTTPResponseCode(400);
656          * throw x; }
657          */
658         
659         if (queryString.toLowerCase().startsWith("for ")) {
660             // it is a flowr expression. That means that the container
661             // string needs to be inserted.
662             int insertPos = 0, pos = 0; 
663             pos = queryString.toLowerCase().indexOf("in");
664             if (pos > -1){
665                 insertPos = pos + 2;
666             }
667             // FIXME: workaround for functions:
668             pos = queryString.toLowerCase().indexOf("in distinct-values(");
669             if (pos > -1){
670                 insertPos = pos + 19;
671             }
672             if (insertPos > 0) {
673                 col.append(queryString.substring(0, insertPos));
674                 col.append(" ");
675                 col.append(getContainerForQuery(collection));
676                 col.append(queryString.substring(insertPos ));
677             } else {
678                 throw new SXException("malformed flowr statemenet: Can not find keyword in");
679             }
680         } else {
681             // it is a normal xquery.
682             col.append(getContainerForQuery(collection));
683             col.append(queryString);
684         }
685         log.info("Using query: " + col.toString());
686         return col.toString();
687     }
688     
689     
690     
691     
692     /**
693      * The XMLManager needs to open the dbxml files with the very same
694      * environment options as the environment the container was created with!
695      * 
696      * @param readonly -
697      *            the transactional subsystem.
698      */
699     private XmlManager createXMLManager() throws SXException {
700         Environment dbenv = null;
701         XmlManager xmlman = null;
702         EnvironmentConfig envConf = new EnvironmentConfig();
703         envConf.setAllowCreate(true);
704         envConf.setInitializeCache(true); // Turn on the shared memory region.
705         int maxCache = new Integer(PropertyManager.getInstance().
706                 getValue("db_cache_size", "30000000")).intValue();
707         envConf.setCacheSize(maxCache); 
708         envConf.setInitializeLocking(true); // Turn on the locking subsystem.
709         envConf.setInitializeLogging(true); // Turn on the logging subsystem.
710         envConf.setLogAutoRemove(true);
711
712         envConf.setTransactional(true); // Turn on the transactional subsystem.
713         envConf.setTxnMaxActive(20); // set to see if we are losing transactions
714         // envConf.setVerboseRecovery(true);
715         // envConf.setVerboseReplication(true);
716
717         envConf.setLockDetectMode(LockDetectMode.MINLOCKS);
718         envConf.setVerboseDeadlock(true);
719         envConf.setVerboseWaitsFor(true);
720         // values are microseconds (3 minutes)
721         envConf.setTxnTimeout(3*60*1000);
722         envConf.setLockTimeout(3*60*1000);
723         
724         // we need enough locks
725         envConf.setMaxLockers(50000);
726         envConf.setMaxLockObjects(50000);
727         envConf.setMaxLocks(50000);
728
729         //envConf.setRunRecovery(true);
730         // if private=false we can attach with scripts to the db and do
731         // backups while keeper is running
732         envConf.setThreaded(true); // already default
733         envConf.setPrivate(false);
734         File containerDir = new File(containerPath);
735
736         try {
737             dbenv = new Environment(containerDir, envConf);
738             xmlman = new XmlManager(dbenv, null);
739             // set internal storage type of container
740             xmlman.setDefaultContainerType(XmlContainer.NodeContainer);
741         } catch (FileNotFoundException e) {
742             log.error("getXMLManager: Cannot open file: " + e.getMessage());
743             e.printStackTrace();
744             throw new SXException(e);
745         } catch (Exception e) {
746             log.error("getXMLManager: " + e.getMessage());
747             e.printStackTrace();
748             throw new SXException(e);
749         }
750         return xmlman;
751     }   
752     
753     
754     
755     /**
756      * Close all containers and the xmlmanager. This is needed to clearly shut
757      * down the db.
758      */
759     @Override
760     public void close() {
761         try {
762             syncContainers();
763             getXmlMan().getEnvironment().checkpoint(new CheckpointConfig());
764         } catch (Exception e1) {
765             e1.printStackTrace();
766             log.error("Checkpointing failed: " + e1.getMessage());
767         }
768         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
769             XmlContainer container = (XmlContainer) it.next();
770             try {
771                 log.debug("Closing container: " + container.getName());
772                 container.close();
773             } catch (Exception e) {
774                 e.printStackTrace();
775             }
776         }
777         try {
778             log.debug("Closing xmlmanager...");
779             xmlMan.close();
780         } catch (XmlException e) {
781             e.printStackTrace();
782         }
783     }
784     
785     
786     public void syncContainers() throws Exception {
787         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
788             XmlContainer container = (XmlContainer) it.next();
789             try {
790                 container.sync();
791                 // checkpointing only if 500kb in buffer
792                 CheckpointConfig cpc = new CheckpointConfig();
793                 cpc.setKBytes(500);
794                 getXmlMan().getEnvironment().checkpoint(cpc);
795                 getXmlMan().getEnvironment().removeOldLogFiles();
796             } catch (Exception e) {
797                 log.error("Syncing + checkpointing container " + container.getName() + " failed: " + e.getMessage());
798                 e.printStackTrace();
799             }
800         }
801     }
802     
803
804     private XmlContainerConfig getXMLContainerConfig() throws SXException {
805         XmlContainerConfig config = new XmlContainerConfig();
806         config.setReadOnly(false);
807         config.setAllowValidation(false);
808         config.setIndexNodes(false);
809         config.setTransactional(true);
810         return config; 
811     }
812
813
814     public HashMap getContainers() {
815         return containers;
816     }
817
818     @Override
819     public Set getContainerNames() {
820         return containers.keySet();
821     }
822     
823
824     public XmlManager getXmlMan() {
825         return xmlMan;
826     }
827
828     @Override
829     public String getType() {
830         return "dbxml";
831     }
832     
833     /**
834      * Add index on an element. 
835      * @param dataType - string, integer
836      * @param indexType - value, substring, presence
837      */
838     @Override
839     public void addIndexElement(String containerName, String elementName, String nameSpace, String dataType,
840             String indexType) throws Exception {
841         
842         if (dataType == null) dataType = "string";
843         if (indexType.equals("presence")) {
844             indexType = "node-element-presence-none";
845         } else if (indexType.equals("substring")) {
846             indexType = "node-element-substring-string";
847         } else if (indexType.equals("value")) {
848             if (dataType.equals("string")) {
849                 indexType = "node-element-equality-string";
850             } else if (dataType.equals("integer")) {
851                 indexType = "node-element-equality-decimal";
852             }
853         }
854         addDbxmlIndex(containerName, nameSpace, elementName, indexType);
855     }
856
857     
858     /**
859      * Add index on an element. 
860      * @param dataType - string, integer
861      */
862     @Override
863     public void addIndexAttribute(String containerName, String attributeName, String nameSpace, String dataType) throws Exception {
864         if (dataType == null) dataType = "string";
865         String indexType = null;
866         if (dataType.equals("string")) {
867                 indexType = "node-attribute-equality-string";
868         } else if (dataType.equals("integer")) {
869                 indexType = "node-attribute-equality-decimal";
870         }
871         addDbxmlIndex(containerName, nameSpace, attributeName, indexType);    
872         
873     }
874     
875     
876     private void addDbxmlIndex(String containerName, String nameSpace, String elementName, String indexType) throws Exception {
877         log.debug("Adding dbxml index: " + indexType + " on " + elementName);
878         if (!containers.containsKey(containerName)) {
879             throw new Exception("Container: " + containerName + " not found.");
880         }
881         XmlContainer container = (XmlContainer) containers.get(containerName);
882         XmlTransaction txn = null;
883         if (nameSpace == null) nameSpace = "";
884         try {
885             txn = xmlMan.createTransaction();
886             // Retrieve the index specification from the container
887             XmlIndexSpecification idxSpec = container.getIndexSpecification(txn);
888
889             // Add the index to the specification.
890             // If it already exists, then this does nothing.
891             idxSpec.addIndex(nameSpace, elementName, indexType);
892
893             // need update context for modifications
894             XmlUpdateContext uc = xmlMan.createUpdateContext();
895             // Set the specification back to the container
896             container.setIndexSpecification(txn, idxSpec, uc);
897
898             // Commit the index adds
899             txn.commit();
900             txn.delete();
901             uc.delete();
902         } catch (Exception e) {
903             log.error("Error adding index: " + indexType + " on " + elementName);
904             log.error("Message: " + e.getMessage());
905             if (txn != null) {
906                 txn.abort();
907                 txn.delete();
908             }
909             throw e;
910         }
911     }
912     
913     
914 }