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