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