added community notifications, removed authentication
[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.output.*;
17
18 import com.sleepycat.db.*;
19 import com.sleepycat.dbxml.*;
20
21 import de.suse.backends.dbxml.*;
22 import de.suse.sx.notification.*;
23 import de.suse.sx.result.*;
24 import de.suse.trigger.*;
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 final Logger log = Logger.getLogger("de.suse.backends");
38     private XmlManager xmlMan = null;
39     public static String dbSuffix = ".dbxml";
40     // list of opened containers, key=containername, value=container object
41     protected HashMap<String, XmlContainer> containers = new HashMap<String, XmlContainer>();
42     // how long are the query results valid
43     private final int timeOut = 60;
44
45     /**
46      * The (private) constructor opens all containers that are available in
47      * the configure containerpath. A container is identified by its .dbxml
48      * ending.
49      * A History file for each available container is automatically created
50      * with the name <containername>-history.dbxml.
51      * Other classes use getInstance() to get the instance of the data manager.
52      */
53     public DbxmlBackend() throws Exception {
54         super();
55         try {
56             // open shared manager
57             xmlMan = createXMLManager();
58             // iterate over the directory and open every found xmldb container.
59             File[] containerFiles = new File(containerPath).listFiles();
60             for (int i=0; containerFiles != null && i < containerFiles.length; i++) {
61                 if (!containerFiles[i].isDirectory() &&
62                         containerFiles[i].getName().endsWith(dbSuffix)) {
63                     log.debug("Found containerfile: " + containerFiles[i].getName());
64                     // try to load config file for container:
65                     String containerName = containerFiles[i].getName().
66                         substring(0, containerFiles[i].getName().length() - dbSuffix.length());
67                     propMan.loadConfig(containerName);
68                     try {
69                         XmlContainerConfig containerConf = getXMLContainerConfig();
70                         if (propMan.getConfig(containerName).containsKey("readonly") && 
71                                 propMan.getConfig(containerName).getProperty("readonly").equals("true")) 
72                             containerConf.setReadOnly(true);
73                         containers.put(containerName, xmlMan.openContainer(containerFiles[i].getName(), containerConf));
74                     } catch (Exception e) {
75                         log.error("Opening container: " + containerFiles[i].getName() + " failed: " + e.getMessage());
76                         e.printStackTrace();
77                     }
78                 }
79             }
80         } catch (Exception e) {
81             log.error("Error setting up initial XMLManager: " + e.getMessage());
82             e.printStackTrace();
83             throw new Exception(e.getMessage());
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 = containers.get(containerName);
99             containers.remove(containerName);
100             try {
101                 container.close();
102                 xmlMan.removeContainer(containerName + dbSuffix);
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      * If the document has no id set, it will get generated, else 
165      * the included id will be used.
166      *
167      * @param collection - the namespace of the document, ie. feature
168      * @param doc - document that will be stored
169      * @return the unique name of the doc
170      */
171      @Override
172     public String storeDoc(SXDocumentType collection, SXDocument xdoc, Properties reqProps) throws SXException {
173         XmlContainer container = null;
174         XmlTransaction txn = null;
175         String id = null;
176          try {
177             txn = xmlMan.createTransaction();
178             TriggerManager.getInstance().runPreWriteTriggers(collection, xdoc, reqProps);
179             container = containers.get(collection.getName());
180             
181             if (xdoc.getXPathContent("/" + collection.getName() + "/@k:id") != null) {
182                 id = xdoc.getXPathContent("/" + collection.getName() + "/@k:id");
183             } else {
184                 id = collection.createDocumentName();
185                 xdoc.setId(id);
186             }
187
188             log.debug("Storing new document with new id " + id);
189             // revision for new docs is always 1
190             long newRev = 1;
191             XmlDocument xmldoc = xmlMan.createDocument();
192             // write the new Revision to the document
193             xdoc.setRevision(newRev);
194             xdoc.setEnterDate();
195             xdoc.setEnteredBy(reqProps.getProperty("userId"));
196             xdoc.setLastModified();
197             xdoc.setLastChangedBy(reqProps.getProperty("userId"));
198             xmldoc.setContent(xdoc.getContent());
199             xmldoc.setName(id);
200             log.debug("Writing with ID and Ref: " + id + " " + newRev);
201             addToContainer(txn, container, xmldoc);
202             txn.commit();
203             txn.delete();
204             syncContainers();
205             TriggerManager.getInstance().runPostWriteTriggers(collection, xdoc, null, reqProps);
206         } catch (Exception e1) {
207             if (txn != null){
208                 try {
209                     txn.abort();
210                 } catch (XmlException e2) {
211                     e1.printStackTrace();
212                     txn.delete();
213                     throw new SXException(e2);
214                 }
215                 txn.delete();
216             }
217             throw new SXException(e1);
218         }
219         return id;
220     }
221
222
223
224      /**
225      * Importing a document which means the same as storeDoc, but the document has
226      * an id + revision set and no triggers will be called.
227      * If revision is 0, it will be set to 1.
228      * Modified timestamp will not be set.
229      * We assume that imported docs have <versioningSummary> fields already set.
230     */
231     @Override
232     public String importDocument(SXDocumentType collection, SXDocument xdoc, Properties reqProps) throws SXException {
233         XmlTransaction txn = null;
234         String id;
235         try {
236         txn = xmlMan.createTransaction();
237         id = importDocument(txn, collection, xdoc);
238         txn.commit();
239         //syncContainers(); // this has a performance impact when importing a large amount
240         txn.delete();
241         } catch (Exception e1) {
242             try {
243                 txn.abort();
244                 txn.delete();
245             } catch (XmlException e) {
246                 e.printStackTrace();
247             }
248             throw new SXException(e1);
249         }
250         return id;
251     }
252
253
254      /**
255      * importing a document inside a transaction
256      */
257     private String importDocument(XmlTransaction txn, SXDocumentType collection, 
258             SXDocument xdoc) throws SXException {
259         String id = xdoc.getId();
260         long rev = xdoc.getRevision();
261         try {
262             if (rev == 0){ 
263                 rev = 1;
264                 xdoc.setRevision(rev);
265             }
266             log.debug("Importing doc with id: " + id + " rev: " + rev);
267             XmlContainer container = null;
268             container = containers.get(collection.getName());
269             XmlDocument xmldoc = xmlMan.createDocument();
270             xmldoc.setContent(xdoc.getFormattedContent(Format.getPrettyFormat()));
271             xmldoc.setName(id);
272             addToContainer(txn, container, xmldoc);
273         } catch (Exception e1) {
274             throw new SXException(e1);
275         }
276         return id;
277     }
278
279
280
281     /**
282      * Updating a document that already exists inside the db. The revisions of
283      * the stored and the new document must match, otherwise we have a revision
284      * conflict.
285      */
286     @Override
287     public long updateDocument(SXDocumentType collection, SXDocument xdoc,  Properties reqProps) throws SXException {
288         XmlContainer container = null;
289         XmlTransaction txn = null;
290         long docRev;
291         try {
292             txn = xmlMan.createTransaction();
293             TriggerManager.getInstance().runPreWriteTriggers(collection, xdoc, reqProps);
294
295             String docName = xdoc.getId();
296             docRev = xdoc.getRevision();
297             container = containers.get(collection.getName());
298             log.debug("updating doc with id: " + docName);
299
300             // get the original document
301             // TODO: theoretically we have to lock the db for this doc-id here
302             // to avoid concurrent updates
303             SXDocument origDoc = getDocument(collection, docName, 0);
304             long origRev = origDoc.getRevision();
305             if (origRev != docRev) {
306                 SXException ex = new SXException("Revision conflict: Your revision: " + docRev
307                         + ", Server revision " + origRev, 409);
308                 throw ex;
309             }
310             // Ok, original document revision and the new revision are the same.
311             // That means we can update and have to return a new revision here.
312
313             // now store the old doc in history:
314             String histName = collection.getName() + "-history";
315             log.debug("History container is: " + histName);
316             if (!containers.containsKey(histName)) {
317                 createContainer(histName);
318             }
319             SXDocument histDoc = new SXDocument(histName, origDoc.getContent());
320             histDoc.setId(docName + "_" + origRev);
321             histDoc.setRevision(1);
322             importDocument(txn, new SXDocumentType(histName), histDoc);
323             
324             XmlDocument xmldoc = xmlMan.createDocument();
325             // write the new Revision to the document
326             xdoc.setRevision(++docRev);
327             xdoc.setLastModified();
328             xdoc.setLastChangedBy(reqProps.getProperty("userId"));
329             xmldoc.setContent(xdoc.getContent());
330             xmldoc.setName(docName);
331             log.debug("Writing with ID and new revision: " + docName + " " + docRev);
332             updateContainer(txn, container, xmldoc);
333
334             txn.commit();
335             syncContainers();
336             histDoc.setId(docName);
337             histDoc.setRevision(origRev);
338             TriggerManager.getInstance().runPostWriteTriggers(collection, xdoc, histDoc, reqProps);
339         } catch (SXException e1) {
340             throw e1;
341         } catch (Exception e1) {
342             try {
343                 txn.abort();
344             } catch (XmlException e) {
345                 throw new SXException(e);
346             }
347             e1.printStackTrace();
348             throw new SXException(e1);
349         } finally {
350             txn.delete();
351         }
352         return docRev;
353     }
354
355
356
357     /**
358      * Updates the file in the container. Can be part of a Transaction created in the
359      * calling method.
360      */
361     private void updateContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc) throws Exception {
362         XmlUpdateContext context = null;
363         try {
364             context = xmlMan.createUpdateContext();
365             container.updateDocument(txn, xmldoc, context);
366         } catch (XmlException e) {
367             log.error("Exception during container update: " + e.getMessage());
368             throw e;
369         }
370     }
371
372     /**
373      * Stores the file in the container.
374      * Can be part of a transaction created in the calling method.
375      */
376     private void addToContainer(XmlTransaction txn, XmlContainer container, XmlDocument xmldoc)
377         throws Exception {
378         XmlUpdateContext context = null;
379         try {
380             context = xmlMan.createUpdateContext();
381             container.putDocument(txn, xmldoc, context);
382         } catch (XmlException e) {
383             log.error("Exception during container update: " + e.getMessage());
384             throw e;
385         }
386     }
387
388
389
390     /**
391      * Delete a document in a container
392      * @param docType
393      * @param id
394      * @throws SXException
395      */
396     @Override
397     public void deleteDocument (SXDocumentType docType, String id, Properties reqProps) throws SXException {
398         XmlUpdateContext ctxt = null;
399         XmlTransaction txn = null;
400         log.debug("Deleting document: " + docType.getName() + " " + id);
401         try {
402             txn = xmlMan.createTransaction();
403             ctxt = xmlMan.createUpdateContext();
404             // get the right container
405             XmlContainer container = containers.get(docType.getName());
406             container.deleteDocument(txn, id, ctxt);
407             txn.commit();
408             txn.delete();
409         } catch (Exception e){
410             try {
411                 txn.abort();
412                 txn.delete();
413             } catch (XmlException ex) {
414                 throw new SXException(ex);
415             }
416             SXException sxe = new SXException("Document with id " + id +
417                     " in container: " + docType.getName() + " could not be deleted: " + e.getMessage());
418             throw sxe;
419         }
420     }
421
422
423     /**
424      * return a single document identified by its name and revision.
425      * The name is the unique id of the document in the db..
426      *
427      * @param the document type (collection)
428      * @param id the document id in that collection
429      * @param the revision as String (0 means latest revision)
430      * @return - a SX document which is basically a XmlDocument object
431      * @throws Exception - Whrn document is not found or anoth error happens
432      */
433     @Override
434     public SXDocument getDocument(SXDocumentType docType, String id, long rev) throws SXException {
435         SXDocument latestDoc, targetDoc;
436         XmlContainer container = null;
437         XmlContainer histcontainer = null;
438
439         try {
440             container = containers.get(docType.getName());
441             XmlValue xmlValue = new XmlValue(container.getDocument(id));
442             // dbxml returns XmlValues of a parent document element when querying all or single docs
443             xmlValue = xmlValue.getFirstChild().getNextSibling();
444                         latestDoc = new DbxmlSXDocument(docType.getName(), xmlValue, id);
445         } catch (XmlException e) {
446             if (e.getErrorCode() == XmlException.DOCUMENT_NOT_FOUND)
447                 throw new SXException("Container: " + docType.getName() + " " + e.getMessage(), 404);
448             else 
449                 e.printStackTrace();
450                 throw new SXException("Container: " + docType.getName() + " Error: " + e.getMessage());
451         } catch (Exception e) {
452             e.printStackTrace();
453             throw new SXException(e);
454         }
455         // check if document is requested from history or production container:
456         long latestrev = latestDoc.getRevision();
457         // rev == 0 means latest document
458         if (rev != 0 && rev < latestrev) {
459             log.debug("Old revision requested, switching to history container");
460             histcontainer = containers.get(docType.getName() + "-history");
461             if (histcontainer != null) {
462                 String histName = id + "_" + rev;
463                 log.debug("Reading document " + histName);
464                 try {
465                     XmlDocument histDoc = histcontainer.getDocument(histName);
466                     targetDoc = new SXDocument(docType.getName(), histDoc.getContentAsString());
467                     // replace id and revision in history-doc, so that it looks
468                     // like one from the target container:
469                     targetDoc.setId(id);
470                     targetDoc.setRevision(rev);
471                 } catch (XmlException e) {
472                     SXException sxe = new SXException("Document with id " + histName
473                             + " could not be found in container: " + docType.getName() + "-history");
474                     throw sxe;
475                 } catch (Exception e) {
476                     throw new SXException(e);
477                 }
478             } else {
479                 throw new SXException("History for container " + docType.getName() + " not found.");
480             }
481         } else if (rev > latestrev) {
482             throw new SXException("Requested revision (" + rev +
483                     ") could not be found, latest available revision is: " + latestrev, 404);
484         } else {
485             targetDoc = latestDoc;
486         }
487         return targetDoc;
488     }
489
490
491     @Override
492     public SXResultList getBulkResultlist(String containerName, List<String> IdList) throws Exception {
493         return new DbxmlBulkResultList(containerName, IdList);
494     }
495
496
497     /**
498      * Returning all stored documents.
499      * @param docType
500      * @return
501      * @throws Exception
502      */
503     @Override
504     public SXResultList getAllDocuments(SXDocumentType docType) throws Exception {
505         DbxmlResultList  res;
506         XmlContainer container = null;
507         try {
508             container = containers.get(docType.getName());
509             XmlQueryContext qc = xmlMan.createQueryContext();
510             qc.setEvaluationType(XmlQueryContext.Lazy);
511             qc.setQueryTimeoutSeconds(timeOut);
512             XmlDocumentConfig conf = new XmlDocumentConfig();
513             conf.setLazyDocs(true);
514             XmlResults results = container.getAllDocuments(conf);
515             res = new DbxmlResultList(docType.getName(), results);
516             qc.delete();
517         } catch (Exception e) {
518             log.error("Failed to query all documents: " + e.getMessage());
519             throw new SXException(e);
520         }
521         return res;
522     }
523
524
525     /**
526      * The next methods do XQuery on the database
527      */
528     private StringBuffer getContainerForQuery(SXDocumentType collection) throws SXException {
529             StringBuffer cStr = new StringBuffer("collection('").
530                 append(collection.getContainerFileName()).append("')");
531         return cStr;
532     }
533
534     /**
535      * Perform a XQuery-query on the database
536      * @param query - a xquery string
537      * @return a ArrayList of SXDocuments that represent the query.
538      */
539     @Override
540     public SXResultList queryDocuments(final SXDocumentType collection, final String query) throws SXException {
541         SXResultList res = null;
542         try {
543             XmlQueryContext cntxt = xmlMan.createQueryContext();
544             cntxt.setQueryTimeoutSeconds(timeOut);
545             cntxt.setEvaluationType(XmlQueryContext.Lazy);
546             cntxt.setNamespace("k", DataManager.kNameUrl);
547             // Prepare (compile) the query
548             XmlQueryExpression qe = xmlMan.prepare(prepareQueryString(query, collection), cntxt);
549             if (qe.isUpdateExpression()) {
550                                 throw new SXException("Updating xqueries are not supported.");
551                         }
552             XmlResults resultSet = qe.execute(cntxt);
553             res = new DbxmlResultList(collection.getName(), resultSet);
554             cntxt.delete();
555             qe.delete();
556         } catch (XmlException e) {
557             SXException ex = new SXException(e);
558             if (e.getErrorCode() != XmlException.QUERY_PARSER_ERROR) {
559                                 e.printStackTrace();
560                         } else {
561                             ex.setHTTPResponseCode(453);
562                         }
563             throw ex;
564         } catch (Exception e) {
565             e.printStackTrace();
566             throw new SXException(e);
567         }
568         return res;
569     }
570
571
572
573     private String prepareQueryString(String queryString, SXDocumentType collection) throws Exception {
574         queryString = queryString.trim();
575         // replace all /<containername> with collection('<filename>')/<containername>
576         queryString = queryString.replaceAll("/" + collection.getName(), getContainerForQuery(collection) + "/" + collection.getName());
577         log.info("Using query: " + queryString);
578         return queryString;
579     }
580
581
582
583     /**
584      * The XMLManager needs to open the dbxml files with the very same
585      * environment options as the environment the container was created with!
586      *
587      * @param readonly -
588      *            the transactional subsystem.
589      */
590     private XmlManager createXMLManager() throws SXException {
591         Environment dbenv = null;
592         XmlManager xmlman = null;
593         EnvironmentConfig envConf = new EnvironmentConfig();
594         envConf.setAllowCreate(true);
595         envConf.setInitializeCache(true); // Turn on the shared memory region.
596         int maxCache = new Integer(PropertyManager.getInstance().
597                 getValue("db_cache_size", "30000000")).intValue();
598         envConf.setCacheSize(maxCache);
599         envConf.setInitializeLocking(true); // Turn on the locking subsystem.
600         envConf.setInitializeLogging(true); // Turn on the logging subsystem.
601         envConf.setLogAutoRemove(true);
602
603         envConf.setTransactional(true); // Turn on the transactional subsystem.
604         envConf.setTxnMaxActive(20); // set to see if we are losing transactions
605         // envConf.setVerboseRecovery(true);
606         // envConf.setVerboseReplication(true);
607
608         envConf.setLockDetectMode(LockDetectMode.MINLOCKS);
609         envConf.setVerbose(VerboseConfig.DEADLOCK, true);
610         envConf.setVerbose(VerboseConfig.WAITSFOR, true);
611         // values are microseconds (2 minutes)
612         envConf.setTxnTimeout(2*60*1000);
613         envConf.setLockTimeout(2*60*1000);
614
615         // we need enough locks
616         envConf.setMaxLockers(10000);
617         envConf.setMaxLockObjects(10000);
618         envConf.setMaxLocks(10000);
619
620         //envConf.setRunRecovery(true);
621         // if private=false we can attach with scripts to the db and do
622         // backups while keeper is running
623         envConf.setThreaded(true); // already default
624         envConf.setPrivate(false);
625         File containerDir = new File(containerPath);
626
627         try {
628             dbenv = new Environment(containerDir, envConf);
629             xmlman = new XmlManager(dbenv, null);
630             // set internal storage type of container
631             xmlman.setDefaultContainerType(XmlContainer.NodeContainer);
632         } catch (FileNotFoundException e) {
633             log.error("getXMLManager: Cannot open file: " + e.getMessage());
634             e.printStackTrace();
635             throw new SXException(e);
636         } catch (Exception e) {
637             log.error("getXMLManager: " + e.getMessage());
638             e.printStackTrace();
639             throw new SXException(e);
640         }
641         return xmlman;
642     }
643
644
645
646     /**
647      * Close all containers and the xmlmanager. This is needed to clearly shut
648      * down the db.
649      */
650     @Override
651     public void close() {
652         try {
653             syncContainers();
654             getXmlMan().getEnvironment().checkpoint(new CheckpointConfig());
655         } catch (Exception e1) {
656             e1.printStackTrace();
657             log.error("Checkpointing failed: " + e1.getMessage());
658         }
659         for (Iterator<XmlContainer> it = containers.values().iterator(); it.hasNext(); ){
660             XmlContainer container = it.next();
661             try {
662                 String containerName = container.getName();
663                 log.debug("Closing container: " + containerName);
664                 container.delete();
665                 //xmlMan.compactContainer(containerName, xmlMan.createUpdateContext());
666             } catch (Exception e) {
667                 e.printStackTrace();
668             }
669         }
670         log.debug("Closing xmlmanager...");
671         xmlMan.delete();
672         log.debug("Shut down notification thread...");
673         NotificationManager.stopThread();
674     }
675
676
677     public void syncContainers() throws Exception {
678         for (Iterator<XmlContainer> it = containers.values().iterator(); it.hasNext(); ){
679             XmlContainer container = it.next();
680             try {
681                 container.sync();
682                 // checkpointing only if 500kb in buffer
683                 CheckpointConfig cpc = new CheckpointConfig();
684                 cpc.setKBytes(500);
685                 getXmlMan().getEnvironment().checkpoint(cpc);
686                 getXmlMan().getEnvironment().removeOldLogFiles();
687             } catch (Exception e) {
688                 log.error("Syncing + checkpointing container " + container.getName() + " failed: " + e.getMessage());
689                 e.printStackTrace();
690             }
691         }
692     }
693
694
695     private XmlContainerConfig getXMLContainerConfig() throws SXException {
696         XmlContainerConfig config = new XmlContainerConfig();
697         config.setReadOnly(false);
698         config.setAllowValidation(false);
699         config.setTransactional(true);
700         //config.setStatisticsEnabled(false);
701         return config;
702     }
703
704
705     public HashMap<String, XmlContainer> getContainers() {
706         return containers;
707     }
708
709     @Override
710     public Set<String> getContainerNames() {
711         return containers.keySet();
712     }
713
714
715     public XmlManager getXmlMan() {
716         return xmlMan;
717     }
718
719     @Override
720     public String getType() {
721         return "dbxml";
722     }
723
724     /**
725      * Add index on an element.
726      * @param dataType - string, integer
727      * @param indexType - value, substring, presence
728      */
729     @Override
730     public void addIndexElement(String containerName, String elementName, String nameSpace, String dataType,
731             String indexType) throws Exception {
732
733         if (dataType == null) {
734                         dataType = "string";
735                 }
736         if (indexType.equals("presence")) {
737             indexType = "node-element-presence-none";
738         } else if (indexType.equals("substring")) {
739             indexType = "node-element-substring-string";
740         } else if (indexType.equals("value")) {
741             if (dataType.equals("string")) {
742                 indexType = "node-element-equality-string";
743             } else if (dataType.equals("integer")) {
744                 indexType = "node-element-equality-decimal";
745             }
746         }
747         addDbxmlIndex(containerName, nameSpace, elementName, indexType);
748     }
749
750
751     /**
752      * Add index on an element.
753      * @param dataType - string, integer
754      */
755     @Override
756     public void addIndexAttribute(String containerName, String attributeName, String nameSpace, String dataType) throws Exception {
757         if (dataType == null) {
758                         dataType = "string";
759                 }
760         String indexType = null;
761         if (dataType.equals("string")) {
762                 indexType = "node-attribute-equality-string";
763         } else if (dataType.equals("integer")) {
764                 indexType = "node-attribute-equality-decimal";
765         }
766         addDbxmlIndex(containerName, nameSpace, attributeName, indexType);
767
768     }
769
770
771     private void addDbxmlIndex(String containerName, String nameSpace, String elementName, String indexType) throws Exception {
772         log.debug("Adding dbxml index: " + indexType + " on " + elementName);
773         if (!containers.containsKey(containerName)) {
774             throw new Exception("Container: " + containerName + " not found.");
775         }
776         XmlContainer container = containers.get(containerName);
777         XmlTransaction txn = null;
778         if (nameSpace == null) {
779                         nameSpace = "";
780                 }
781         try {
782             txn = xmlMan.createTransaction();
783             // Retrieve the index specification from the container
784             XmlIndexSpecification idxSpec = container.getIndexSpecification(txn);
785
786             // Add the index to the specification.
787             // If it already exists, then this does nothing.
788             idxSpec.addIndex(nameSpace, elementName, indexType);
789
790             // need update context for modifications
791             XmlUpdateContext uc = xmlMan.createUpdateContext();
792             // Set the specification back to the container
793             container.setIndexSpecification(txn, idxSpec, uc);
794
795             // Commit the index adds
796             txn.commit();
797             txn.delete();
798         } catch (Exception e) {
799             log.error("Error adding index: " + indexType + " on " + elementName);
800             log.error("Message: " + e.getMessage());
801             if (txn != null) {
802                 txn.abort();
803                 txn.delete();
804             }
805             throw e;
806         }
807     }
808
809
810 }