working on exchangeable database backend
[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.text.*;
14 import java.util.*;
15
16 import org.apache.log4j.*;
17 import org.jdom.*;
18 import org.jdom.output.*;
19
20 import com.sleepycat.db.*;
21 import com.sleepycat.dbxml.*;
22
23 import de.suse.notification.*;
24 import de.suse.security.*;
25 import de.suse.xml.*;
26
27 /**
28  * This is part of a base library of the SUSE XML server. The dataManager
29  * provides storage- and query capabilities to the SX database in a very generic
30  * way.
31  * 
32  * @author Klaas Freitag &lt;freitag@suse.de&gt;
33  * @author Thomas Schmidt &lt;tschmidt@suse.de&gt;
34  */
35 public class DbxmlBackend extends SxBackendIface {
36
37     private Logger log = Logger.getLogger("de.suse.backends.dbxml");
38     private String containerPath;
39     private PropertyManager propMan;
40     private XmlManager xmlMan = null;
41     // list of opened containers, key=containername, value=container
42     HashMap containers = new HashMap();
43     public static String kNameUrl = "http://inttools.suse.de/sxkeeper/schema/keeper";
44     public static Namespace kName = Namespace.getNamespace("k", kNameUrl);
45     public static String containerPathId = "sxkeeper_containerpath";
46     
47     /**
48      * The (private) constructor opens all containers that are available in 
49      * the configure containerpath. A container is identified by its .dbxml 
50      * ending. 
51      * A History file for each available container is automatically created 
52      * with the name <containername>-history.dbxml.
53      * Other classes use getInstance() to get the instance of the data manager.
54      */
55     public DbxmlBackend() throws Exception {
56         propMan = PropertyManager.getInstance();
57         
58         containerPath = propMan.getValue("containerPath");
59         containerPath = System.getProperty(containerPathId, containerPath);
60         if (containerPath != null && !containerPath.endsWith("/")) {
61                 StringBuffer buf = new StringBuffer(containerPath);
62                 containerPath = buf.append("/").toString();
63         }
64         if( containerPath == null ) {
65             log.error("No container path found in config!");
66             throw new Exception("No container path found in config!");
67         } else {
68             log.debug("containerpath set to: " + containerPath);
69         }
70         try {
71             // open shared manager
72             xmlMan = createXMLManager();
73             // iterate over the directory and open every found xmldb container.
74             File[] containerFiles = new File(containerPath).listFiles();
75             XmlContainerConfig containerConf = getXMLContainerConfig();
76             for (int i=0; containerFiles != null && i < containerFiles.length; i++) {
77                 if (!containerFiles[i].isDirectory() && 
78                         containerFiles[i].getName().endsWith(".dbxml")) {
79                     log.debug("Found containerfile: " + containerFiles[i].getName());
80                     // try to load config file for container: 
81                     String containerName = containerFiles[i].getName().
82                         substring(0, containerFiles[i].getName().length() - 6);
83                     propMan.loadConfig(containerName);
84                     try {
85                         containers.put(containerName, xmlMan.openContainer(containerFiles[i].getName(), containerConf));
86                     } catch (Exception e) {
87                         log.error("Opening container: " + containerFiles[i].getName() + " failed: " + e.getMessage());
88                         e.printStackTrace();
89                     }
90                 }
91             }
92         } catch (Exception e) {
93             log.error("Error setting up initial XMLManager: " + e.getMessage());
94             e.printStackTrace();
95             throw new Exception(e.getMessage());
96         }
97     }
98
99
100
101
102     
103     
104     /**
105      * Completely deletes the container. Use with caution ;-)
106      * @param containerName
107      * @throws SXException
108      */
109     public synchronized void deleteContainer(String containerName)
110             throws SXException {
111         log.debug("Attention: going to delete container: " + containerName);
112         if (containers.containsKey(containerName)) {
113             XmlContainer container = (XmlContainer) containers
114                     .get(containerName);
115             containers.remove(containerName);
116             XmlTransaction txn = null;
117             try {
118                 txn = xmlMan.createTransaction();
119                 container.delete();
120                 xmlMan.removeContainer(containerName + ".dbxml");
121                 txn.commit();
122                 txn.delete();
123             } catch (XmlException e) {
124                 try {
125                     txn.abort();
126                 } catch (XmlException e1) {
127                     e1.printStackTrace();
128                 }
129                 e.printStackTrace();
130                 throw new SXException(e);
131             }
132         } else {
133             log.error("cannot find container " + containerName
134                     + ", will try to delete the file anyway");
135         }
136         File dbContainer = new File(containerPath + containerName + ".dbxml");
137         if (dbContainer.exists()) {
138             log.debug("Deleting containerfile manually.");
139             dbContainer.delete();
140         }
141     }
142     
143     /**
144      * create a new container
145      * 
146      * @param containerName name of the container (e.g. "feature")
147      * @throws SXException
148      */
149     public synchronized void createContainer(String containerName) throws SXException {
150         log.debug("Creating new container " + containerName);
151         String containerFileName = containerName + ".dbxml";
152         XmlContainer container = null;
153         XmlTransaction txn = null;
154         try {
155             new File(containerPath + containerFileName);
156             txn = xmlMan.createTransaction();
157             container = xmlMan.createContainer(containerFileName, getXMLContainerConfig());
158             containers.put(containerName, container);
159             txn.commit();
160             txn.delete();
161         } catch (Exception e) {
162             log.error("Error in creating new container: " + e.getMessage());
163             if (txn != null){
164                 try {
165                     txn.abort();
166                 } catch (XmlException e1) {
167                     e1.printStackTrace();
168                     txn.delete();
169                     throw new SXException(e1);
170                 }
171                 txn.delete();
172             }
173             e.printStackTrace();
174             throw new SXException(e);
175         }
176         log.debug("created new container: " + containerName);
177     }
178     
179     
180     /**
181      * Stores a XML snippet to the database.
182      * 
183      * @param collection
184      *            the namespace of the document, ie. feature
185      * @param doc
186      *            the XML snippet as a string. Note that the doc must be a valid
187      *            XML document. It should maintain the namespace correctly to
188      *            distinguish between different sx storage types, ie. features,
189      *            contacts or package descriptions.
190      *            <p>
191      *            A good example document looks like this:
192      *            <p>
193      *            <code>
194      *        &lt;features xmlns:sxfeatures="http://sx.suse.de/features"&gt;<br>
195      *           &lt;description&gt;<br>
196      *             ...<br>
197      *           &lt;/description&gt;<br>
198      *        &lt;/features&gt;<br>
199      * </code>
200      *            <p>
201      *            It uses the xsfeatures namespace.
202      * 
203      * @return the unique name of the doc
204      */
205      public String storeDoc(SXDocumentType collection, Document xdoc, String id, 
206              FeatureUser user, boolean doNotify) throws SXException {
207          
208          if (user == null){
209                 SXException ex = new SXException("Please specify a valid user to update a document");
210                 ex.setHTTPResponseCode(400);
211                 throw ex;
212          }
213          
214         XmlContainer container = null;
215         XmlTransaction txn = null;
216          try {
217             txn = xmlMan.createTransaction();
218             container = (XmlContainer) containers.get(collection.getName());
219
220             // if we have no id, generate one
221             if (id == null || id.equals("")){
222                 id = collection.createDocumentName(null);
223             }
224             
225             log.debug("Storing new document with new id " + id); 
226             // always set the id in the document: 
227             setIDInDoc(xdoc, id);
228             // revision for new docs is always 1
229             long newRev = 1;
230             XmlDocument xmldoc = xmlMan.createDocument();
231             // write the new Revision to the document
232             setRevInDoc(xdoc, newRev);
233             setEnterDateInDoc(xdoc);
234             setEnteredByInDoc(xdoc, user.getUserId());
235             setLastModifiedInDoc(xdoc);
236             setLastChangedByInDoc(xdoc, user.getUserId());
237             XMLOutputter out = new XMLOutputter();
238             xmldoc.setContent(out.outputString(xdoc));
239             xmldoc.setName(id);
240             log.debug("Writing with ID and Ref: " + id + " " + newRev);
241             addToContainer(txn, container, xmldoc);
242             xmldoc.delete();
243             if (doNotify) {
244                 notifyOnInsert(collection, id, xdoc, user);
245             }
246             txn.commit();
247             txn.delete();
248             syncContainers();
249         } catch (Exception e1) {
250             if (txn != null){
251                 try {
252                     txn.abort();
253                 } catch (XmlException e2) {
254                     e1.printStackTrace();
255                     txn.delete();
256                     throw new SXException(e2);
257                 }
258                 txn.delete();
259             }
260             throw new SXException(e1);
261         }
262         return id;
263     }
264     
265     
266
267      /**
268      * Importing a document which means the same as storeDoc, but the document has 
269      * an id + revision set. 
270      * If revision is 0, it will be set to 1.
271      * Modified timestamp will not be set.
272      * We assume that imported docs have <versioningSummary> fields already set.   
273     */  
274     public String importDocument(SXDocumentType collection, String id, long rev, 
275                 Document xdoc) throws SXException {
276         XmlTransaction txn = null;
277         try {
278         txn = xmlMan.createTransaction();
279         id = importDocument(txn, collection, id, rev, xdoc);
280         txn.commit();
281         syncContainers();
282         txn.delete();
283         } catch (Exception e1) {
284             try {
285                 txn.abort();
286                 txn.delete();
287             } catch (XmlException e) {
288                 e.printStackTrace();
289             }
290             throw new SXException(e1);
291         }
292         return id;
293     }
294      
295     
296      /**
297      * importing a document inside a transaction
298      */
299     private String importDocument(XmlTransaction txn, SXDocumentType collection, 
300             String id, long rev, Document xdoc) throws SXException {
301         // contraints
302         if (id == null){
303             SXException e = new SXException("Illegal ID=null - for import mode!");
304             e.setHTTPResponseCode(400);
305             throw e;
306         }
307         log.debug("Importing doc with id: " + id + " rev: " + rev);
308         XmlContainer container = null;        
309         try {
310             container = (XmlContainer) containers.get(collection.getName());
311             String docName = collection.createDocumentName(id);
312             setIDInDoc(xdoc, id);
313             XmlDocument xmldoc = xmlMan.createDocument();
314             if (rev == 0){
315                 rev = 1;
316             }
317             setRevInDoc(xdoc, rev);
318             XMLOutputter out = new XMLOutputter();
319             xmldoc.setContent(out.outputString(xdoc));
320             xmldoc.setName( docName );    
321             log.debug("Writing with ID and Ref: " + id + " " + rev);
322             addToContainer(txn, container, xmldoc);
323             xmldoc.delete();
324         } catch (Exception e1) {
325             throw new SXException(e1);
326         }
327         return id;
328     }
329     
330     
331     
332     /**
333      * Updating a document that already exists inside the db. The revisions of
334      * the stored and the new document must match, otherwise we have a revision
335      * conflict.
336      */
337     public void updateDocument(SXDocumentType collection, String id, long rev, Document xdoc,
338             FeatureUser user, boolean doNotify) throws SXException {
339
340         // check contraints
341         if (rev == 0) {
342             SXException ex = new SXException("Please specify your document revision");
343             ex.setHTTPResponseCode(400);
344             throw ex;
345         } else if (!(id != null && rev > 0)) {
346             SXException ex = new SXException("Please specify your document revision and id");
347             ex.setHTTPResponseCode(400);
348             throw ex;
349         } else if (user == null) {
350             SXException ex = new SXException("Please specify a valid user to update a document");
351             ex.setHTTPResponseCode(400);
352             throw ex;
353         }
354
355         // check if container is read-only
356         Properties conf = propMan.getConfig(collection.getName());
357         if (conf != null && conf.containsKey("read-only") && 
358                 conf.getProperty("read-only").equalsIgnoreCase("true")) {
359             SXException ex = new SXException("This container (" + 
360                     collection.getName() + ") is not configured for writing.");
361             ex.setHTTPResponseCode(400);
362             throw ex;
363         }
364
365         log.debug("updating doc with id: " + id + " and old-rev: " + rev);
366         XmlContainer container = null;
367         XmlTransaction txn = null;
368
369         try {
370             txn = xmlMan.createTransaction();
371             String docName = collection.createDocumentName(id);
372             container = (XmlContainer) containers.get(collection.getName());
373
374             // get the original document
375             // TODO: theoretically we have to lock the db for this doc-id here
376             // to avoid concurrent updates
377             SXDocument origDoc = getDocument(collection, id, 0);
378             Document histDoc = origDoc.getJDomDocument();
379             long origRevLong = origDoc.getRevision();
380             if (origRevLong != rev) {
381                 SXException ex = new SXException("Revision conflict: Your revision: " + rev
382                         + ", Server revision " + origRevLong);
383                 ex.setHTTPResponseCode(409);
384                 ex.setErrCode(21);
385                 throw ex;
386             }
387             // Ok, original document revision and the new revision are the same.
388             // That means we can update and have to return a new revision here.
389
390             XmlDocument xmldoc = xmlMan.createDocument();
391             XMLOutputter out = new XMLOutputter();
392             // write the new Revision to the document
393             setRevInDoc(xdoc, origRevLong + 1);
394             setIDInDoc(xdoc, id);
395             setEnterDateInDoc(xdoc, origDoc.getEnterDate());
396             setEnteredByInDoc(xdoc, origDoc.getEnterPerson());
397             setLastModifiedInDoc(xdoc);
398             setLastChangedByInDoc(xdoc, user.getUserId());
399             xmldoc.setContent(out.outputString(xdoc));
400             xmldoc.setName(docName);
401
402             log.debug("Writing with ID and new Rev: " + id + " " + (origRevLong + 1));
403             updateContainer(txn, container, xmldoc);
404             xmldoc.delete();
405
406             // now store the old doc in history:
407             String histName = collection.getName() + "-history";
408             log.debug("History container is: " + histName);
409             if (!containers.containsKey(histName)) {
410                 // container does not exist yet, -> create
411                 log.debug("Creating new history container for " + collection.getName());
412                 createContainer(histName);
413             }
414             log.debug("Storing doc to history");
415             importDocument(txn, new SXDocumentType(histName), id + "_" + origRevLong, 0, (Document) histDoc
416                     .clone());
417             if (doNotify) {
418                 // start update notifications
419                 notifyOnUpdate(collection, id, histDoc, xdoc, user);
420             }
421             txn.commit();
422             syncContainers();
423         } catch (SXException e1) {
424             try {
425                 txn.abort();
426                 txn.delete();
427             } catch (XmlException e) {
428                 throw new SXException(e);
429             }
430             throw new SXException(e1);
431         } catch (Exception e1) {
432             try {
433                 txn.abort();
434                 txn.delete();
435             } catch (XmlException e) {
436                 throw new SXException(e);
437             }
438             throw new SXException(e1);
439         }
440     }
441     
442     
443     
444     /**
445      * Updates the file in the container. Xmlman and container must be opened
446      * and closed in calling method. Can be part of a Transaktion created in the
447      * calling method.
448      */
449     private void updateContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) throws Exception {
450         XmlUpdateContext context = null;
451         try {
452             context = xmlMan.createUpdateContext();
453             container.updateDocument(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      * Stores the file in the container. 
466      * Xmlman and container must be opened and closed in calling method. 
467      * Can be part of a Transaktio created in the calling method.
468      */
469     private void addToContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) 
470         throws Exception {
471         XmlUpdateContext context = null;
472         try {
473             context = xmlMan.createUpdateContext();
474             container.putDocument(txn, xmldoc, context);
475             context.delete();
476         } catch (XmlException e) {
477             if (context != null){
478                 context.delete();
479             }
480             log.error("Exception during container update: " + e.getMessage());
481             throw e;
482         }
483     }
484     
485     
486     /**
487      * Setting the id in the document.
488      * (Attribute "id" in the root element)
489      */
490     private void setIDInDoc(Document xdoc, String id) {
491         xdoc.getRootElement().setAttribute("id", id, kName);
492     }
493     
494     /**
495      * Setting the revision in the document.
496      * (Attribute "revision" in the root element)
497      */
498     private void setRevInDoc(Document xdoc, long rev) {
499         xdoc.getRootElement().setAttribute("revision", String.valueOf(rev), kName);
500     }
501
502     
503     /**
504      * Setting elements in <versioningSummary>
505      * if not present, they will be created.
506      */
507     private void setLastModifiedInDoc(Document xdoc) {
508         SimpleDateFormat format = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss");
509         checkVersioningSummary(xdoc, "lastmodifydate");
510         xdoc.getRootElement().getChild("versioningsummary", kName).
511             getChild("lastmodifydate", kName).setText( format.format(new Date()));
512     }
513     
514     private void setLastChangedByInDoc(Document xdoc, String userId) {
515         checkVersioningSummary(xdoc, "lastchangedby");
516         xdoc.getRootElement().getChild("versioningsummary", kName).
517             getChild("lastchangedby", kName).setText(userId);
518     }
519     
520     private void setEnteredByInDoc(Document xdoc, String userId) {
521         checkVersioningSummary(xdoc, "enteredby");
522         xdoc.getRootElement().getChild("versioningsummary", kName).
523             getChild("enteredby", kName).setText(userId);
524     }
525     
526     private void setEnterDateInDoc(Document xdoc) {
527         checkVersioningSummary(xdoc, "enterdate");
528         SimpleDateFormat format = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss");
529         xdoc.getRootElement().getChild("versioningsummary", kName).
530             getChild("enterdate", kName).setText(format.format(new Date()));
531     }
532     
533     private void setEnterDateInDoc(Document xdoc, Date date) {
534         checkVersioningSummary(xdoc, "enterdate");
535         SimpleDateFormat format = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss");
536         xdoc.getRootElement().getChild("versioningsummary", kName).
537             getChild("enterdate", kName).setText(format.format(date));
538     }
539     
540     private void checkVersioningSummary (Document xdoc, String childName){
541         if (xdoc.getRootElement().getChild("versioningsummary", kName) == null){
542             xdoc.getRootElement().addContent(0, new Element("versioningsummary").setNamespace(kName));
543         }
544         if (xdoc.getRootElement().getChild("versioningsummary", kName).
545                 getChild(childName, kName) == null){
546             xdoc.getRootElement().getChild("versioningsummary", kName).
547                 addContent(new Element(childName).setNamespace(kName));
548         }
549     }
550     
551     
552     /**
553      * Triggering notifications on document update. 
554      * Each container may have its own Class to manage how notifications are done. 
555      * This class must be defined in <containername>.conf and implement NotificationIface
556      */
557     private void notifyOnUpdate(SXDocumentType collection, String id, Document histDoc,
558             Document newDoc, FeatureUser user) {
559         Properties p = propMan.getConfig(collection.getName());
560         if (p != null){
561             String className = p.getProperty("notification.class");
562             if (className != null && !className.equals("")) {
563                 try {
564                     Class notificationClass = Class.forName(className);
565                     NotificationIface notificationManager = (NotificationIface) notificationClass
566                             .newInstance();
567                     notificationManager.notifyOnUpdate(id, histDoc, newDoc, user);
568                 } catch (Exception e) {
569                     log.error("Error loading configured notification class: " + e.getMessage());
570                     // failing Notifications do not break the write transaction atm.
571                     e.printStackTrace();
572                 }
573             } else {
574                 log.debug("No NotificationClass configured for updating container "
575                         + collection.getName());
576             }
577         }
578     }
579     
580     
581     /**
582      * Triggering notifications on document insert. 
583      * Each container may have its own Class to manage how notifications are done. 
584      * This class must be defined in <containername>.conf and implement NotificationIface
585      */
586     private void notifyOnInsert(SXDocumentType collection, String id, Document newDoc, 
587             FeatureUser user) {
588         Properties p = propMan.getConfig(collection.getName());
589         if (p != null){
590             String className = p.getProperty("notification.class");
591             if (className != null && !className.equals("")) {
592                 try {
593                     Class notificationClass = Class.forName(className);
594                     NotificationIface notificationManager = (NotificationIface) notificationClass
595                             .newInstance();
596                     notificationManager.notifyOnInsert(id, newDoc, user);
597                 } catch (Exception e) {
598                     log.error("Error loading configured notification class: " + e.getMessage());
599                     e.printStackTrace();
600                 }
601             } else {
602                 log.debug("No NotificationClass configured for updating container "
603                         + collection.getName());
604             }
605         }
606     }
607     
608     
609     /**
610      * Delete a document in a container
611      * @param docType
612      * @param id
613      * @throws SXException
614      */
615     public void deleteDocument (SXDocumentType docType, String id) throws SXException {
616         XmlUpdateContext ctxt = null;
617         XmlTransaction txn = null;
618         try {
619             txn = xmlMan.createTransaction();
620             ctxt = xmlMan.createUpdateContext();
621             // get the right container
622             XmlContainer container = (XmlContainer) containers.get(docType.getName());
623             container.deleteDocument(txn, docType.getName() + "-" + id, ctxt);
624             txn.commit();
625             txn.delete();
626             syncContainers();
627         } catch (XmlException e){
628             try {
629                 txn.abort();
630                 txn.delete();
631             } catch (XmlException ex) {
632                 throw new SXException(ex);
633             }
634             SXException sxe = new SXException("Document with id " + id + 
635                     " could not be deleted: " + e.getMessage());
636             throw sxe;
637         } finally {
638             if (ctxt != null){
639                 ctxt.delete();
640             }
641         }
642     }
643     
644         
645     /**
646      * return a single document identified by its name and revision. 
647      * The name is the unique id of the document in the db..
648      * 
649      * @param the document type (collection)
650      * @param id the document id in that collection
651      * @param the revision as String (0 means latest revision)
652      * @return - a SX document which is basically a XmlDocument object
653      * @throws Exception - Whrn document is not found or anoth error happens
654      */
655     public SXDocument getDocument(SXDocumentType docType, String id, long rev)
656             throws SXException {
657         SXDocument latestDoc, targetDoc;
658         String name = docType.createDocumentName(id);
659         XmlContainer container = null;
660         XmlContainer histcontainer = null;
661         try {
662             // get the right container
663             container = (XmlContainer) containers.get(docType.getName());
664
665             log.debug("Reading document " + name);
666             // do not use XMLDocumentConfig
667             XmlDocument doc = container.getDocument(name);
668             latestDoc = new SXDocument(doc.getContentAsString());
669             doc.delete();
670             // check if document is requested from history or production
671             // container:
672             long latestrev = latestDoc.getRevision();
673             // rev == 0 means latest document
674             if (rev != 0 && rev < latestrev) {
675                 log.debug("Old revision requested, switching to history container");
676                 histcontainer = (XmlContainer) containers.get(docType.getName() + "-history");
677                 if (histcontainer != null) {
678                     String histName = id + "_" + rev;
679                     log.debug("Reading document " + histName);
680                     XmlDocument histDoc = histcontainer.getDocument(histName);
681                     targetDoc = new SXDocument(histDoc.getContentAsString());
682                     histDoc.delete();
683                     // replace id and revision in history-doc, so that it looks
684                     // like one from the target container:
685                     Document jdomDoc = targetDoc.getJDomDocument();
686                     setIDInDoc(jdomDoc, id);
687                     setRevInDoc(jdomDoc, rev);
688                     targetDoc.setContent(new XMLOutputter()
689                             .outputString(jdomDoc));
690                 } else {
691                     throw new SXException("History Container Object is null!");
692                 }
693             } else if (rev > latestrev) {
694                 throw new SXException("Requested revision could not be found!");
695             } else {
696                 targetDoc = latestDoc;
697             }
698         } catch (XmlException e){
699             SXException sxe = new SXException("Document with id " + id + 
700                     " could not be found in container: " + docType.getName());
701             sxe.setErrCode(11);
702             throw sxe;
703         } catch (Exception e){
704             // rethrow Exception
705             throw new SXException(e);
706         }
707         return targetDoc;
708     }
709
710     
711     
712     /**
713      * Returning all stored documents. 
714      * @param docType
715      * @return
716      * @throws Exception
717      */
718     public  ArrayList getAllDocuments(SXDocumentType docType) throws Exception {
719         ArrayList res = new ArrayList();
720         XmlContainer container = null;
721         try {
722             container = (XmlContainer) containers.get(docType.getName());
723             XmlQueryContext qc = xmlMan.createQueryContext();
724             qc.setEvaluationType(XmlQueryContext.Lazy);
725             //qc.setReturnType(XmlQueryContext.LiveValues);
726             
727             XmlResults results = container.getAllDocuments(new XmlDocumentConfig());
728             XmlValue val = results.next();
729             while (val != null) {
730                 SXDocument sxdoc = new SXDocument(val.asString());
731                 res.add(sxdoc);
732                 val.delete();
733                 val = results.next();
734             }
735             results.delete();
736             qc.delete();
737         } catch (Exception e) {
738             log.error("Failed to query all documents: " + e.getMessage());
739             throw new SXException(e);
740         }
741         return res;
742     }
743
744     /**
745      * Gets the meta revision for @containerName
746      */
747     public long getMetaRevisionForContainer(String containerName) throws Exception {
748         // check with xquery, because id load would throw exception on missing meta file
749         ArrayList docs = queryDocuments(new SXDocumentType("meta"), "/meta[@k:id=\"" + containerName + "\"]");    
750         long revision = 0;
751         if (docs != null && docs.size() == 1) {
752             revision = ((SXDocument) docs.get(0)).getRevision();
753         }
754         return revision;
755     }
756     
757     
758     /**
759      * The next methods do XQuery on the database
760      */
761     private StringBuffer getContainerForQuery(SXDocumentType collection) throws SXException {
762             StringBuffer cStr = new StringBuffer("collection('").
763                 append(collection.getContainerFileName()).append("')");
764         return cStr;
765     }
766
767     /**
768      * Perform a XQuery-query on the database
769      * @param query - a xquery string
770      * @return a ArrayList of SXDocuments that represent the query.
771      */
772     public ArrayList queryDocuments(SXDocumentType collection, String query) throws SXException {
773         ArrayList res = new ArrayList();
774
775         StringBuffer col = new StringBuffer();
776         long time = System.currentTimeMillis();
777         query = query.trim();
778
779         // only allow queries for /<containername>[] atm:
780         /*
781          * if (!(query.startsWith("/" + collection.getName() + "[") &&
782          * query.endsWith("]"))){ SXException x = new SXException("Invalid
783          * querystring sent"); x.setErrCode(6); x.setHTTPResponseCode(400);
784          * throw x; }
785          */
786
787         try {
788             if (query.toLowerCase().startsWith("for ")) {
789                 // it is a flowr expression. That means that the container
790                 // string needs to be inserted.
791                 int insertPos = 0, pos = 0; 
792                 
793                 pos = query.toLowerCase().indexOf("in");
794                 if (pos > -1){
795                     insertPos = pos + 2;
796                 }
797                 
798                 // FIXME: workaround for functions:
799                 pos = query.toLowerCase().indexOf("in distinct-values(");
800                 if (pos > -1){
801                     insertPos = pos + 19;
802                 }
803                 
804                 if (insertPos > 0) {
805                     col.append(query.substring(0, insertPos));
806                     col.append(" ");
807                     col.append(getContainerForQuery(collection));
808                     col.append(query.substring(insertPos ));
809                 } else {
810                     throw new SXException("malformed flowr statemenet: Can not find keyword in");
811                 }
812             } else {
813                 // it is a normal xquery.
814                 col.append(getContainerForQuery(collection));
815                 col.append(query);
816             }
817             log.info("Executing query: " + col.toString());
818
819             if (query.length() > 0) {
820                 XmlQueryContext cntxt = xmlMan.createQueryContext();
821                 cntxt.setEvaluationType(XmlQueryContext.Lazy);
822                 cntxt.setNamespace("k", kNameUrl);
823                 // Prepare (compile) the query
824                 XmlQueryExpression qe = xmlMan.prepare(col.toString(), cntxt);
825
826                 XmlResults resultSet = qe.execute(cntxt);
827                 log.debug("executing query took: " + (System.currentTimeMillis() - time) + "ms");
828
829                 XmlValue val = resultSet.next();
830                 log.debug("first value after: " + (System.currentTimeMillis() - time) + "ms");
831                 while (val != null) {
832                     res.add(new SXDocument(val.asString()));
833                     val.delete();
834                     val = resultSet.next();
835                 }
836                 log.debug("resultlist ready after : " + (System.currentTimeMillis() - time) + "ms");
837                 resultSet.delete();
838                 qe.delete();
839                 cntxt.delete();
840             }
841         } catch (XmlException e) {
842             e.printStackTrace();
843             throw new SXException(e);
844         }
845         time = System.currentTimeMillis() - time;
846         log.debug("executing query + building documents took: " + time + "ms");
847         if (time > 6000) {
848             log.warn("Slow query detected, please optimize !");
849         }
850         return res;
851     }
852    
853     
854     
855     /**
856      * The XMLManager needs to open the dbxml files with the very same
857      * environment options as the environment the container was created with!
858      * 
859      * @param readonly -
860      *            the transactional subsystem.
861      */
862     private XmlManager createXMLManager() throws SXException {
863         Environment dbenv = null;
864         XmlManager xmlman = null;
865         EnvironmentConfig envConf = new EnvironmentConfig();
866         envConf.setAllowCreate(true);
867         envConf.setInitializeCache(true); // Turn on the shared memory region.
868         envConf.setCacheSize(60 * 1024 * 1024); // 60 mb cache
869         envConf.setInitializeLocking(true); // Turn on the locking subsystem.
870         envConf.setInitializeLogging(true); // Turn on the logging subsystem.
871         envConf.setLogAutoRemove(true);
872
873         envConf.setTransactional(true); // Turn on the transactional subsystem.
874         envConf.setTxnMaxActive(20); // set to see if we are losing transactions
875         // envConf.setVerboseDeadlock(true);
876         // envConf.setVerboseRecovery(true);
877         // envConf.setVerboseReplication(true);
878         // envConf.setVerboseWaitsFor(true);
879
880         // we need enough locks
881         envConf.setMaxLockers(15000);
882         envConf.setMaxLockObjects(15000);
883         envConf.setMaxLocks(15000);
884
885         //envConf.setRunRecovery(true);
886         // if private=false we can attach with scripts to the db and do
887         // backups while keeper is running
888         envConf.setThreaded(true); // already default
889         envConf.setPrivate(false);
890         File containerDir = new File(containerPath);
891
892         try {
893             dbenv = new Environment(containerDir, envConf);
894             xmlman = new XmlManager(dbenv, null);
895             // set internal storage type of container
896             xmlman.setDefaultContainerType(XmlContainer.NodeContainer);
897         } catch (FileNotFoundException e) {
898             log.error("getXMLManager: Cannot open file: " + e.getMessage());
899             e.printStackTrace();
900             throw new SXException(e);
901         } catch (Exception e) {
902             log.error("getXMLManager: " + e.getMessage());
903             e.printStackTrace();
904             throw new SXException(e);
905         }
906         return xmlman;
907     }   
908     
909     
910     
911     /**
912      * Close all containers and the xmlmanager. This is needed to clearly shut
913      * down the db.
914      */
915     public void close(){
916         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
917             XmlContainer container = (XmlContainer) it.next();
918             try {
919                 log.debug("Closing container: " + container.getName());
920                 container.close();
921             } catch (Exception e) {
922                 e.printStackTrace();
923             }
924         }
925         try {
926             log.debug("Closing xmlmanager...");
927             xmlMan.close();
928         } catch (XmlException e) {
929             e.printStackTrace();
930         }
931     }
932     
933     
934     public void syncContainers(){
935         for (Iterator it = containers.values().iterator(); it.hasNext(); ){
936             XmlContainer container = (XmlContainer) it.next();
937             try {
938                 //log.debug("syncing container: " + container.getName());
939                 container.sync();
940             } catch (Exception e) {
941                 e.printStackTrace();
942             }
943         }
944         /*try {
945             LockStats theLockStats = xmlMan.getEnvironment().
946                 getLockStats(StatsConfig.DEFAULT);
947             log.debug("num lockers:" + theLockStats.getNumLockers());
948             log.debug("num locks:" + theLockStats.getNumLocks());
949         } catch (Exception e) {
950             log.error("Unable to get log stats!");
951         }*/
952     }
953     
954
955     private XmlContainerConfig getXMLContainerConfig() throws SXException {
956         XmlContainerConfig config = new XmlContainerConfig();
957         config.setReadOnly(false);
958         config.setAllowValidation(false);
959         config.setIndexNodes(false);
960         config.setTransactional(true);
961         return config; 
962     }
963
964
965     public HashMap getContainers() {
966         return containers;
967     }
968
969     public Set getContainerNames() {
970         return containers.keySet();
971     }
972     
973
974     public XmlManager getXmlMan() {
975         return xmlMan;
976     }
977
978
979
980     
981 }