Skip to content
This repository
Browse code

Added a new view, "KeyValueTable" onto XFlat tables

Signed-off-by: gburgett <gordon.burgett@gmail.com>
  • Loading branch information...
commit ec352e7ea647f037f28f73818ad95bb07ed764c6 1 parent c03f4c1
Gordon authored March 30, 2013

Showing 30 changed files with 1,363 additions and 100 deletions. Show diff stats Hide diff stats

  1. 19  java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Writer.java
  2. 8  java/XFlat/src/org/xflatdb/xflat/Database.java
  3. 128  java/XFlat/src/org/xflatdb/xflat/KeyValueTable.java
  4. 1  java/XFlat/src/org/xflatdb/xflat/Table.java
  5. 19  java/XFlat/src/org/xflatdb/xflat/convert/converters/DateConverters.java
  6. 19  java/XFlat/src/org/xflatdb/xflat/convert/converters/JavaBeansPojoConverter.java
  7. 19  java/XFlat/src/org/xflatdb/xflat/convert/converters/XStreamPojoConverter.java
  8. 339  java/XFlat/src/org/xflatdb/xflat/db/ConvertingKeyValueTable.java
  9. 17  java/XFlat/src/org/xflatdb/xflat/db/ConvertingTable.java
  10. 4  java/XFlat/src/org/xflatdb/xflat/db/ElementTable.java
  11. 19  java/XFlat/src/org/xflatdb/xflat/db/EngineState.java
  12. 14  java/XFlat/src/org/xflatdb/xflat/db/TableBase.java
  13. 27  java/XFlat/src/org/xflatdb/xflat/db/XFlatDatabase.java
  14. 4  java/XFlat/src/org/xflatdb/xflat/engine/CachedDocumentEngine.java
  15. 19  java/XFlat/src/org/xflatdb/xflat/transaction/Propagation.java
  16. 46  java/XFlat/src/org/xflatdb/xflat/transaction/ThreadContextTransactionManager.java
  17. 33  java/XFlat/src/org/xflatdb/xflat/transaction/TransactionOptions.java
  18. 19  java/XFlat/src/org/xflatdb/xflat/transaction/TransactionPropagationException.java
  19. 19  java/XFlat/src/org/xflatdb/xflat/transaction/TransactionScope.java
  20. 29  java/XFlat/src/org/xflatdb/xflat/util/Action1.java
  21. 19  java/XFlat/src/org/xflatdb/xflat/util/XPathExpressionEqualityMatcher.java
  22. 19  java/XFlat/test/org/xflatdb/xflat/convert/converters/JavaBeansPojoMapperTest.java
  23. 19  java/XFlat/test/org/xflatdb/xflat/convert/converters/XStreamPojoConverterTest.java
  24. 19  java/XFlat/test/org/xflatdb/xflat/db/BigIntIdGeneratorTest.java
  25. 391  java/XFlat/test/org/xflatdb/xflat/db/ConvertingKeyValueTableTest.java
  26. 118  java/XFlat/test/org/xflatdb/xflat/db/DatabaseIntegrationTest.java
  27. 19  java/XFlat/test/org/xflatdb/xflat/db/IdGeneratorTestsBase.java
  28. 19  java/XFlat/test/org/xflatdb/xflat/db/MultithreadedDbIntegrationTests.java
  29. 19  java/XFlat/test/org/xflatdb/xflat/db/TimestampIdGeneratorTest.java
  30. 19  java/XFlat/test/org/xflatdb/xflat/db/UuidIdGeneratorTest.java
19  java/XFlat/src/com/thoughtworks/xstream/io/xml/JDom2Writer.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package com.thoughtworks.xstream.io.xml;
6 17
 
7 18
 import com.thoughtworks.xstream.io.naming.NameCoder;
8  java/XFlat/src/org/xflatdb/xflat/Database.java
@@ -51,6 +51,14 @@
51 51
     public <T> Table<T> getTable(Class<T> persistentClass, String name);
52 52
     
53 53
     /**
  54
+     * Gets the named table as a KeyValueTable, which can be used to store data
  55
+     * as key-value pairs.
  56
+     * @param name The name of the table.
  57
+     * @return A table for manipulating rows of key-value data.
  58
+     */
  59
+    public KeyValueTable getKeyValueTable(String name);
  60
+    
  61
+    /**
54 62
      * Gets the database's {@link TransactionManager}.  The TransactionManager
55 63
      * allows opening transactions in the database.
56 64
      * @return The database's TransactionManager.
128  java/XFlat/src/org/xflatdb/xflat/KeyValueTable.java
... ...
@@ -0,0 +1,128 @@
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
  16
+package org.xflatdb.xflat;
  17
+
  18
+import java.util.List;
  19
+import org.xflatdb.xflat.query.XPathQuery;
  20
+import org.xflatdb.xflat.query.XPathUpdate;
  21
+
  22
+/**
  23
+ * This interface represents a "KeyValue" view to an XFlat table.  The KeyValue
  24
+ * view allows storing arbitrary convertible objects by key in the database.
  25
+ * @author Gordon
  26
+ */
  27
+public interface KeyValueTable {
  28
+    
  29
+    //CREATE
  30
+    /**
  31
+     * Inserts a key value pair as a row in the table.
  32
+     * @param key The key to which the value is associated.
  33
+     * @param row The value to insert as XML.  Cannot be null.
  34
+     * @throws DuplicateKeyException if a row with the given key already exists.
  35
+     */
  36
+    public <T> void add(String key, T row)
  37
+            throws DuplicateKeyException;
  38
+    
  39
+    /**
  40
+     * Puts a value with the given key in the database.  If the value already
  41
+     * exists, it is overwritten.
  42
+     * @param <T>
  43
+     * @param key The key to which the value is associated.
  44
+     * @param row The new value for the row.  Cannot be null.
  45
+     */
  46
+    public <T> void set(String key, T row);
  47
+    
  48
+    /**
  49
+     * Puts a value with the given key in the database.  If the value already
  50
+     * exists, it is overwritten and the old value is returned.
  51
+     * @param <T>
  52
+     * @param key The key to which the value is associated.
  53
+     * @param row The new value for the row.  Cannot be null.
  54
+     * @return The old value for the row, or null if it did not previously exist.
  55
+     */
  56
+    public <T> T put(String key, T row);
  57
+    
  58
+    //READ
  59
+    /**
  60
+     * Finds one value by key
  61
+     * @param key The key to which the value is associated.
  62
+     * @param clazz The type as which the value should be deserialized.
  63
+     * @return The row value, or null if the row does not exist.
  64
+     */
  65
+    public <T> T get(String key, Class<T> clazz);
  66
+    
  67
+    /**
  68
+     * Finds the first value matching the Xpath query.
  69
+     * @param query The query to match.
  70
+     * @param clazz The type as which the value should be deserialized. 
  71
+     * @return the value of the matched row, or null if no row was matched.
  72
+     */
  73
+    public <T> T findOne(XPathQuery query, Class<T> clazz);
  74
+    
  75
+    /**
  76
+     * Gets a cursor over all the values matching the Xpath query.
  77
+     * @param query The query to match.
  78
+     * @param clazz The type as which the value should be deserialized.
  79
+     * @return A cursor over each matching row.
  80
+     */
  81
+    public <T> Cursor<T> find(XPathQuery query, Class<T> clazz);
  82
+    
  83
+    /**
  84
+     * Gets a list of all the values matching the Xpath query.
  85
+     * This is the same as {@link #find(org.xflatdb.xflat.query.XPathQuery) }
  86
+     * but without the hassle of a cursor.
  87
+     * @param query The query to match.
  88
+     * @param clazz The type as which the value should be deserialized.
  89
+     * @return A list of all the matching values.
  90
+     */
  91
+    public <T> List<T> findAll(XPathQuery query, Class<T> clazz);
  92
+    
  93
+    //UPDATE
  94
+    /**
  95
+     * Replaces a value with the new value by ID.  This is the same as "Save"
  96
+     * in some other document databases.
  97
+     * @param key The key to which the value is associated.
  98
+     * @param newValue The new value to replace the old value.
  99
+     */
  100
+    public <T> void replace(String key, T newValue)
  101
+            throws KeyNotFoundException;
  102
+ 
  103
+    /**
  104
+     * Applies an update to the data in a given row.
  105
+     * @param key The key to which the value is associated.
  106
+     * @param update The update to apply.
  107
+     * @return true if the update actually applied, false if the row was found
  108
+     * but the update did not select an existing document element.
  109
+     * @throws KeyNotFoundException if the row does not exist.
  110
+     */
  111
+    public boolean update(String key, XPathUpdate update)
  112
+            throws KeyNotFoundException;
  113
+    
  114
+    //DELETE
  115
+    /**
  116
+     * Deletes the row associated to the given key.
  117
+     * @param key The key to which the value is associated.
  118
+     */
  119
+    public void delete(String key)
  120
+            throws KeyNotFoundException;
  121
+    
  122
+    /**
  123
+     * Deletes all rows matching the given query.
  124
+     * @param query The query selecting elements to delete.
  125
+     * @return the number of rows that were deleted.
  126
+     */
  127
+    public int deleteAll(XPathQuery query);
  128
+}
1  java/XFlat/src/org/xflatdb/xflat/Table.java
@@ -18,7 +18,6 @@
18 18
 import java.util.List;
19 19
 import org.xflatdb.xflat.query.XPathQuery;
20 20
 import org.xflatdb.xflat.query.XPathUpdate;
21  
-import org.jdom2.Element;
22 21
 
23 22
 /**
24 23
  * Represents a table in the database.  A Table provides CRUD access to the underlying
19  java/XFlat/src/org/xflatdb/xflat/convert/converters/DateConverters.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.convert.converters;
6 17
 
7 18
 import java.util.Date;
19  java/XFlat/src/org/xflatdb/xflat/convert/converters/JavaBeansPojoConverter.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.convert.converters;
6 17
 
7 18
 import java.beans.XMLDecoder;
19  java/XFlat/src/org/xflatdb/xflat/convert/converters/XStreamPojoConverter.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.convert.converters;
6 17
 
7 18
 import com.thoughtworks.xstream.XStream;
339  java/XFlat/src/org/xflatdb/xflat/db/ConvertingKeyValueTable.java
... ...
@@ -0,0 +1,339 @@
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
  16
+package org.xflatdb.xflat.db;
  17
+
  18
+import java.util.ArrayList;
  19
+import java.util.Iterator;
  20
+import java.util.List;
  21
+import org.jdom2.Element;
  22
+import org.xflatdb.xflat.Cursor;
  23
+import org.xflatdb.xflat.DuplicateKeyException;
  24
+import org.xflatdb.xflat.KeyNotFoundException;
  25
+import org.xflatdb.xflat.KeyValueTable;
  26
+import org.xflatdb.xflat.XFlatException;
  27
+import org.xflatdb.xflat.convert.ConversionException;
  28
+import org.xflatdb.xflat.convert.ConversionNotSupportedException;
  29
+import org.xflatdb.xflat.convert.ConversionService;
  30
+import org.xflatdb.xflat.query.XPathQuery;
  31
+import org.xflatdb.xflat.query.XPathUpdate;
  32
+import org.xflatdb.xflat.transaction.TransactionManager;
  33
+import org.xflatdb.xflat.transaction.TransactionOptions;
  34
+import org.xflatdb.xflat.transaction.TransactionScope;
  35
+import org.xflatdb.xflat.util.Action1;
  36
+
  37
+/**
  38
+ * A KeyValueTable implementation using the XFlat conversion service
  39
+ * to convert values to XML.
  40
+ * @author Gordon
  41
+ */
  42
+public class ConvertingKeyValueTable extends TableBase implements KeyValueTable {
  43
+    
  44
+    private ConversionService conversionService;
  45
+    public void setConversionService(ConversionService conversionService) {
  46
+        this.conversionService = conversionService;
  47
+    }
  48
+    
  49
+    private TransactionManager transactionService;
  50
+    /**
  51
+     * Injects the transactionService service.
  52
+     */
  53
+    public void setTransactionService(TransactionManager transactionService){
  54
+        this.transactionService = transactionService;
  55
+    }
  56
+    
  57
+    private Action1 loadPojoMapperAction = new Action1<ConvertingKeyValueTable>(){
  58
+        @Override
  59
+        public void apply(ConvertingKeyValueTable val) {            
  60
+        }
  61
+    };
  62
+    
  63
+    void setLoadPojoMapperAction(Action1 action) {
  64
+        this.loadPojoMapperAction = action;
  65
+    }
  66
+    
  67
+    public ConvertingKeyValueTable(String tableName){
  68
+        super(tableName);
  69
+    }
  70
+    
  71
+
  72
+    private <T> Element convert(T data, String key){
  73
+        Element ret;
  74
+        try {
  75
+            try{
  76
+                ret = this.conversionService.convert(data, Element.class);
  77
+            } catch(ConversionNotSupportedException ex){
  78
+                //load pojo mapper and try one more time...
  79
+                this.loadPojoMapperAction.apply(this);
  80
+
  81
+                ret = this.conversionService.convert(data, Element.class);
  82
+            }
  83
+
  84
+        } catch (ConversionException ex) {
  85
+            throw new XFlatException("Cannot convert data with key " + key, ex);
  86
+        }        
  87
+        
  88
+        return ret;
  89
+    }
  90
+    
  91
+    private <T> T convert(Element rowData, String key, Class<T> clazz){
  92
+                
  93
+        T ret;
  94
+        try {
  95
+            try{
  96
+                ret = this.conversionService.convert(rowData, clazz);
  97
+            } catch(ConversionNotSupportedException ex){
  98
+                //load pojo mapper and try one more time...
  99
+                this.loadPojoMapperAction.apply(this);
  100
+
  101
+                ret = this.conversionService.convert(rowData, clazz);
  102
+            }
  103
+        } catch (ConversionException ex) {
  104
+            throw new XFlatException("Cannot convert data with key " + key, ex);
  105
+        }        
  106
+        
  107
+        return ret;
  108
+    }
  109
+    
  110
+    @Override
  111
+    public <T> void add(final String key, T row) throws DuplicateKeyException {
  112
+        final Element e = convert(row, key);
  113
+        
  114
+        this.doWithEngine(new EngineAction(){
  115
+            @Override
  116
+            public Object act(Engine engine) {
  117
+                engine.insertRow(key, e);
  118
+                return null;
  119
+            }
  120
+        });
  121
+    }
  122
+
  123
+    @Override
  124
+    public <T> void set(final String key, T row){
  125
+        final Element data = convert(row, key);
  126
+        
  127
+        this.doWithEngine(new EngineAction(){
  128
+            @Override
  129
+            public Object act(Engine engine) {
  130
+                engine.upsertRow(key, data);
  131
+                return null;
  132
+            }
  133
+        });
  134
+    }
  135
+    
  136
+    @Override
  137
+    public <T> T put(final String key, T row) {
  138
+        Class<? extends T> clazz = (Class<? extends T>)row.getClass();
  139
+        final Element data = convert(row, key);
  140
+        
  141
+        Element inRow;
  142
+        
  143
+        //since this is a two step process, must execute in transaction.
  144
+        //This transaction can be non-durable, since non-transactional table operations
  145
+        //do not guarantee durability.
  146
+        try(TransactionScope tx = this.transactionService.openTransaction(TransactionOptions.DEFAULT.withDurability(false))){
  147
+            
  148
+            inRow = this.doWithEngine(new EngineAction<Element>(){
  149
+                @Override
  150
+                public Element act(Engine engine) {
  151
+                    Element inRow = engine.readRow(key);       
  152
+                    
  153
+                    if(inRow == null)
  154
+                        engine.insertRow(key, data);
  155
+                    else
  156
+                        engine.replaceRow(key, data);
  157
+                    
  158
+                    return inRow;
  159
+                }
  160
+            });
  161
+            
  162
+            tx.commit(); 
  163
+        }
  164
+        
  165
+        if(inRow == null)
  166
+            return null;
  167
+
  168
+        return convert(inRow, key, clazz);
  169
+    }
  170
+
  171
+    @Override
  172
+    public <T> T get(final String key, Class<T> clazz) {
  173
+        Element data = this.doWithEngine(new EngineAction<Element>(){
  174
+            @Override
  175
+            public Element act(Engine engine) {
  176
+                return engine.readRow(key);
  177
+            }
  178
+        });
  179
+        
  180
+        if(data == null){
  181
+            return null;
  182
+        }
  183
+        
  184
+        T ret = convert(data, key, clazz);
  185
+        return ret;
  186
+    }
  187
+
  188
+    @Override
  189
+    public <T> T findOne(XPathQuery query, Class<T> clazz) {
  190
+        Element e = findOneElement(query);
  191
+        
  192
+        if(e == null){
  193
+            return null;
  194
+        }
  195
+        
  196
+        return convert(e, "unknown", clazz);
  197
+    }
  198
+
  199
+    private Element findOneElement(XPathQuery query){
  200
+        try(Cursor<Element> elements = this.queryTable(query)){
  201
+            Iterator<Element> it = elements.iterator();
  202
+            if(!it.hasNext()){
  203
+                return null;
  204
+            }
  205
+            
  206
+            return it.next();
  207
+        }
  208
+        catch(Exception ex){
  209
+            throw new XFlatException("Unable to close cursor", ex);
  210
+        }
  211
+    }
  212
+    
  213
+    private Cursor<Element> queryTable(final XPathQuery query){
  214
+        return this.doWithEngine(new EngineAction<Cursor<Element>>(){
  215
+            @Override
  216
+            public Cursor<Element> act(Engine engine) {
  217
+                return engine.queryTable(query);
  218
+            }
  219
+        });
  220
+    }
  221
+    
  222
+    @Override
  223
+    public <T> Cursor<T> find(final XPathQuery query, final Class<T> clazz) {
  224
+                
  225
+        return this.doWithEngine(new EngineAction<Cursor<T>>(){
  226
+            @Override
  227
+            public Cursor<T> act(Engine engine) {
  228
+                return new ConvertingCursor(engine.queryTable(query), clazz);
  229
+            }
  230
+        });
  231
+    }
  232
+
  233
+    @Override
  234
+    public <T> List<T> findAll(XPathQuery query, Class<T> clazz) {
  235
+        List<T> ret = new ArrayList<>();
  236
+        
  237
+        try(Cursor<Element> data = this.queryTable(query)){
  238
+            for(Element e : data){
  239
+                ret.add(convert(e, "unknown", clazz));
  240
+            }
  241
+        }
  242
+        
  243
+        return ret;
  244
+    }
  245
+
  246
+    @Override
  247
+    public <T> void replace(final String key, T newValue) throws KeyNotFoundException {
  248
+        final Element data = convert(newValue, key);
  249
+        
  250
+        this.doWithEngine(new EngineAction(){
  251
+            @Override
  252
+            public Object act(Engine engine) {
  253
+                engine.replaceRow(key, data);
  254
+                return null;
  255
+            }
  256
+        });
  257
+    }
  258
+
  259
+    @Override
  260
+    public boolean update(final String key, final XPathUpdate update) throws KeyNotFoundException {
  261
+        return this.doWithEngine(new EngineAction<Boolean>(){
  262
+            @Override
  263
+            public Boolean act(Engine engine) {
  264
+                return engine.update(key, update);
  265
+            }
  266
+        });
  267
+    }
  268
+
  269
+    @Override
  270
+    public void delete(final String key) throws KeyNotFoundException {
  271
+        this.doWithEngine(new EngineAction(){
  272
+            @Override
  273
+            public Object act(Engine engine) {
  274
+                engine.deleteRow(key);
  275
+                return null;
  276
+            }
  277
+        });
  278
+    }
  279
+
  280
+    @Override
  281
+    public int deleteAll(final XPathQuery query) {
  282
+        return this.doWithEngine(new EngineAction<Integer>(){
  283
+            @Override
  284
+            public Integer act(Engine engine) {
  285
+                return engine.deleteAll(query);
  286
+            }
  287
+        });
  288
+    }
  289
+
  290
+    
  291
+    
  292
+    private class ConvertingCursor<T> implements Cursor<T>{
  293
+        Cursor<Element> rowCursor;
  294
+        
  295
+        Class<T> clazz;
  296
+        
  297
+        public ConvertingCursor(Cursor<Element> rowCursor, Class<T> clazz){
  298
+            this.rowCursor = rowCursor;
  299
+            this.clazz = clazz;
  300
+        }
  301
+
  302
+        @Override
  303
+        public Iterator<T> iterator() {
  304
+            return new ConvertingCursorIterator(this.rowCursor.iterator(), clazz);
  305
+        }
  306
+
  307
+        @Override
  308
+        public void close() throws XFlatException {
  309
+            this.rowCursor.close();
  310
+        }
  311
+    }
  312
+    
  313
+    private class ConvertingCursorIterator<T> implements Iterator<T>{
  314
+        Iterator<Element> rowIterator;
  315
+        
  316
+        Class<T> clazz;
  317
+        
  318
+        public ConvertingCursorIterator(Iterator<Element> rowIterator, Class<T> clazz){
  319
+            this.rowIterator = rowIterator;
  320
+            this.clazz = clazz;
  321
+        }
  322
+
  323
+        @Override
  324
+        public boolean hasNext() {
  325
+            return rowIterator.hasNext();
  326
+        }
  327
+
  328
+        @Override
  329
+        public T next() {
  330
+            return convert(rowIterator.next(), "unknown", clazz);
  331
+        }
  332
+
  333
+        @Override
  334
+        public void remove() {
  335
+            throw new UnsupportedOperationException("Remove not supported on cursors.");
  336
+        }
  337
+    }
  338
+    
  339
+}
17  java/XFlat/src/org/xflatdb/xflat/db/ConvertingTable.java
@@ -22,8 +22,6 @@
22 22
 import java.util.List;
23 23
 import java.util.Map;
24 24
 import java.util.WeakHashMap;
25  
-import java.util.logging.Level;
26  
-import java.util.logging.Logger;
27 25
 import org.apache.commons.logging.LogFactory;
28 26
 import org.xflatdb.xflat.Cursor;
29 27
 import org.xflatdb.xflat.DuplicateKeyException;
@@ -42,8 +40,17 @@
42 40
  * using the database's conversion service.
43 41
  * @author gordon
44 42
  */
45  
-public class ConvertingTable<T> extends TableBase<T> implements Table<T> {
  43
+public class ConvertingTable<T> extends TableBase implements Table<T> {
46 44
 
  45
+    private Class<T> tableType;
  46
+    /**
  47
+     * Gets the class of the items in the table.
  48
+     * @return The class object
  49
+     */
  50
+    protected Class<T> getTableType(){
  51
+        return this.tableType;
  52
+    }
  53
+    
47 54
     private ConversionService conversionService;
48 55
     public void setConversionService(ConversionService conversionService) {
49 56
         this.conversionService = conversionService;
@@ -59,7 +66,9 @@ void setAlternateIdExpression(XPathExpression<Object> expression){
59 66
     }
60 67
     
61 68
     ConvertingTable(Class<T> type, String name){
62  
-        super(type, name);
  69
+        super(name);
  70
+        
  71
+        this.tableType = type;
63 72
         
64 73
         this.accessor = IdAccessor.forClass(type);
65 74
         if(!this.accessor.hasId()){
4  java/XFlat/src/org/xflatdb/xflat/db/ElementTable.java
@@ -33,11 +33,11 @@
33 33
  * This is the simple Table implementation for tables of JDOM Elements.
34 34
  * @author gordon
35 35
  */
36  
-public class ElementTable extends TableBase<Element> implements Table<Element> {
  36
+public class ElementTable extends TableBase implements Table<Element> {
37 37
 
38 38
     
39 39
     ElementTable(String tableName){
40  
-        super(Element.class, tableName);
  40
+        super(tableName);
41 41
     }
42 42
     
43 43
     @Override
19  java/XFlat/src/org/xflatdb/xflat/db/EngineState.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.db;
6 17
 
7 18
 /**
14  java/XFlat/src/org/xflatdb/xflat/db/TableBase.java
@@ -22,17 +22,8 @@
22 22
  * This provides dependencies to table implementations that are injected by the database.
23 23
  * @author gordon
24 24
  */
25  
-public abstract class TableBase<T> {
  25
+public abstract class TableBase {
26 26
         
27  
-    private Class<T> tableType;
28  
-    /**
29  
-     * Gets the class of the items in the table.
30  
-     * @return The class object
31  
-     */
32  
-    protected Class<T> getTableType(){
33  
-        return this.tableType;
34  
-    }
35  
-    
36 27
     private String tableName;
37 28
     /**
38 29
      * Gets the table's name.
@@ -66,8 +57,7 @@ protected IdGenerator getIdGenerator(){
66 57
      * @param tableType The type of the objects in this table.
67 58
      * @param tableName The name of the table.
68 59
      */
69  
-    protected TableBase(Class<T> tableType, String tableName){
70  
-        this.tableType = tableType;
  60
+    protected TableBase(String tableName){
71 61
         this.tableName = tableName;
72 62
     }
73 63
     
27  java/XFlat/src/org/xflatdb/xflat/db/XFlatDatabase.java
@@ -29,6 +29,8 @@
29 29
 import java.util.concurrent.TimeoutException;
30 30
 import java.util.concurrent.atomic.AtomicBoolean;
31 31
 import java.util.concurrent.atomic.AtomicReference;
  32
+import java.util.logging.Level;
  33
+import java.util.logging.Logger;
32 34
 import org.apache.commons.logging.Log;
33 35
 import org.apache.commons.logging.LogFactory;
34 36
 import org.jdom2.Document;
@@ -36,6 +38,7 @@
36 38
 import org.jdom2.Namespace;
37 39
 import org.xflatdb.xflat.Database;
38 40
 import org.xflatdb.xflat.DatabaseConfig;
  41
+import org.xflatdb.xflat.KeyValueTable;
39 42
 import org.xflatdb.xflat.Table;
40 43
 import org.xflatdb.xflat.TableConfig;
41 44
 import org.xflatdb.xflat.XFlatException;
@@ -49,6 +52,7 @@
49 52
 import org.xflatdb.xflat.engine.DefaultEngineFactory;
50 53
 import org.xflatdb.xflat.transaction.ThreadContextTransactionManager;
51 54
 import org.xflatdb.xflat.transaction.TransactionManager;
  55
+import org.xflatdb.xflat.util.Action1;
52 56
 import org.xflatdb.xflat.util.DocumentFileWrapper;
53 57
 
54 58
 /**
@@ -461,6 +465,29 @@ private void update(){
461 465
         return (Table<T>)ret;
462 466
     }
463 467
     
  468
+    @Override
  469
+    public KeyValueTable getKeyValueTable(String name){
  470
+        TableMetadata table = getMetadata(null, name);
  471
+        
  472
+        ConvertingKeyValueTable ret = new ConvertingKeyValueTable(name);
  473
+        ret.setConversionService(conversionService);
  474
+        ret.setTransactionService(transactionManager);
  475
+        ret.setEngineProvider(table);
  476
+        ret.setLoadPojoMapperAction(new Action1<ConvertingKeyValueTable>(){
  477
+            @Override
  478
+            public void apply(ConvertingKeyValueTable val){
  479
+                try {
  480
+                    loadPojoConverter();
  481
+                    val.setConversionService(conversionService);
  482
+                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
  483
+                    throw new UnsupportedOperationException("No conversion available between your type and Element", ex);
  484
+                }
  485
+            }
  486
+        });
  487
+        
  488
+        return ret;        
  489
+    }
  490
+    
464 491
     /**
465 492
      * Gets the internal EngineBase that has been spun up to manage the given table.
466 493
      * This internal engine is the low-level manager of the database on disk.
4  java/XFlat/src/org/xflatdb/xflat/engine/CachedDocumentEngine.java
@@ -590,9 +590,9 @@ public void commit(Transaction tx, TransactionOptions options){
590 590
             }
591 591
             
592 592
             //we must immediately dump the cache, we cannot say we are committed
593  
-            //until the data is on disk.
  593
+            //until the data is on disk.  That is, if the transaction is durable.
594 594
             lastModified.set(System.currentTimeMillis());
595  
-            dumpCacheNow(true);
  595
+            dumpCacheNow(options.isDurable());
596 596
             
597 597
             currentlyCommitting.compareAndSet(tx.getTransactionId(), -1);
598 598
         }
19  java/XFlat/src/org/xflatdb/xflat/transaction/Propagation.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.transaction;
6 17
 
7 18
 /**
46  java/XFlat/src/org/xflatdb/xflat/transaction/ThreadContextTransactionManager.java
@@ -270,16 +270,19 @@ private synchronized void commit(AmbientThreadedTransactionScope tx){
270 270
             entry.tableNames.add(e.getTableName());
271 271
         }
272 272
         
273  
-        Element entryElement;
274  
-        try {
275  
-            if(transactionJournal == null){
276  
-                loadJournal();
  273
+        Element entryElement = null;
  274
+        if(tx.options.isDurable()){
  275
+            //use the transaction journal to ensure durability
  276
+            try {
  277
+                if(transactionJournal == null){
  278
+                    loadJournal();
  279
+                }
  280
+                entryElement = toElement.convert(entry);
  281
+                transactionJournal.getRootElement().addContent(entryElement);
  282
+                journalWrapper.writeFile(transactionJournal);
  283
+            } catch (ConversionException | IOException | JDOMException ex) {
  284
+                throw new TransactionException("Unable to commit, could not access journal file " + journalWrapper, ex);
277 285
             }
278  
-            entryElement = toElement.convert(entry);
279  
-            transactionJournal.getRootElement().addContent(entryElement);
280  
-            journalWrapper.writeFile(transactionJournal);
281  
-        } catch (ConversionException | IOException | JDOMException ex) {
282  
-            throw new TransactionException("Unable to commit, could not access journal file " + journalWrapper, ex);
283 286
         }
284 287
         
285 288
         //commit all, and if any fail revert all.
@@ -295,9 +298,20 @@ private synchronized void commit(AmbientThreadedTransactionScope tx){
295 298
                 tx.commitId = -1;
296 299
                 tx.revert();
297 300
             }catch(TransactionException ex2){
298  
-                throw new TransactionException("Unable to commit, " + ex2.getMessage(), ex);
  301
+                throw new TransactionException("Unable to commit, and another error occured during revert: " + ex2.getMessage(), ex);
299 302
             }
300 303
             
  304
+            //we were able to revert all, no need to keep the transaction in the journal.
  305
+            if(tx.options.isDurable()){
  306
+                transactionJournal.getRootElement().removeContent(entryElement);
  307
+                try {
  308
+                    journalWrapper.writeFile(transactionJournal);
  309
+                } catch (IOException ioEx) {
  310
+                    //this is not the most important exception
  311
+                }
  312
+            }
  313
+            
  314
+            
301 315
             if(ex instanceof TransactionException)
302 316
                 throw ex;
303 317
             
@@ -305,11 +319,13 @@ private synchronized void commit(AmbientThreadedTransactionScope tx){
305 319
         }
306 320
         
307 321
         //remove it from the transaction journal
308  
-        transactionJournal.getRootElement().removeContent(entryElement);
309  
-        try {
310  
-            journalWrapper.writeFile(transactionJournal);
311  
-        } catch (IOException ex) {
312  
-            throw new TransactionException("Unable to commit, could not access journal file " + journalWrapper, ex);
  322
+        if(tx.options.isDurable()){
  323
+            transactionJournal.getRootElement().removeContent(entryElement);
  324
+            try {
  325
+                journalWrapper.writeFile(transactionJournal);
  326
+            } catch (IOException ex) {
  327
+                throw new TransactionException("Unable to commit, could not access journal file " + journalWrapper, ex);
  328
+            }
313 329
         }
314 330
         
315 331
         //we're all committed, so we can finally say so.
33  java/XFlat/src/org/xflatdb/xflat/transaction/TransactionOptions.java
@@ -98,6 +98,33 @@ public TransactionOptions withPropagation(Propagation propagation) {
98 98
         return ret;
99 99
     }
100 100
     
  101
+    private boolean durable;
  102
+    /**
  103
+     * Gets whether the transaction is durable.
  104
+     * Defaults to true.
  105
+     */
  106
+    public boolean isDurable(){
  107
+        return this.durable;
  108
+    }
  109
+    /**
  110
+     * Sets the durability to apply to this transaction.  A Durable transaction
  111
+     * is guaranteed to not return until the data is written to disk, and any
  112
+     * errors (even power failures) during the commit operation will not result
  113
+     * in partially-committed data.  This is of course subject to the durability
  114
+     * of the operating system.<br/>
  115
+     * A non-durable transaction makes none of these guarantees, but the commit
  116
+     * operation may be substantially faster.
  117
+     * <p/>
  118
+     * The default value for this property is "true".
  119
+     * @param durable true to ensure the transaction is durable, false to use a non-durable transaction.
  120
+     * @return a new TransactionOptions object with Durable == the given value.
  121
+     */
  122
+    public TransactionOptions withDurability(boolean durable){
  123
+        TransactionOptions ret = new TransactionOptions(this);
  124
+        ret.durable = durable;
  125
+        return ret;
  126
+    }
  127
+    
101 128
     /**
102 129
      * Creates a new TransactionOptions object with the default options.
103 130
      */
@@ -105,12 +132,14 @@ public TransactionOptions(){
105 132
         this.readOnly = false;
106 133
         this.isolation = Isolation.SNAPSHOT;
107 134
         this.propagation = Propagation.REQUIRED;
  135
+        this.durable = true;
108 136
     }
109 137
     
110 138
     private TransactionOptions(TransactionOptions other){
111 139
         this.readOnly = other.readOnly;
112 140
         this.isolation = other.isolation;
113 141
         this.propagation = other.propagation;
  142
+        this.durable = other.durable;
114 143
     }
115 144
     
116 145
     /**
@@ -123,6 +152,7 @@ private TransactionOptions(TransactionOptions other){
123 152
     public int hashCode() {
124 153
         int hash = 5;
125 154
         hash = 97 * hash + (this.readOnly ? 1 : 0);
  155
+        hash = 97 * hash + (this.durable ? 1 : 0);
126 156
         hash = 97 * hash + (this.isolation != null ? this.isolation.hashCode() : 0);
127 157
         hash = 97 * hash + (this.propagation != null ? this.propagation.hashCode() : 0);
128 158
         return hash;
@@ -146,6 +176,9 @@ public boolean equals(Object obj) {
146 176
         if (this.propagation != other.propagation) {
147 177
             return false;
148 178
         }
  179
+        if(this.durable != other.durable){
  180
+            return false;
  181
+        }
149 182
         return true;
150 183
     }
151 184
 }
19  java/XFlat/src/org/xflatdb/xflat/transaction/TransactionPropagationException.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.transaction;
6 17
 
7 18
 /**
19  java/XFlat/src/org/xflatdb/xflat/transaction/TransactionScope.java
... ...
@@ -1,7 +1,18 @@
1  
-/*
2  
- * To change this template, choose Tools | Templates
3  
- * and open the template in the editor.
4  
- */
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
5 16
 package org.xflatdb.xflat.transaction;
6 17
 
7 18
 /**
29  java/XFlat/src/org/xflatdb/xflat/util/Action1.java
... ...
@@ -0,0 +1,29 @@
  1
+/* 
  2
+*	Copyright 2013 Gordon Burgett and individual contributors
  3
+*
  4
+*	Licensed under the Apache License, Version 2.0 (the "License");
  5
+*	you may not use this file except in compliance with the License.
  6
+*	You may obtain a copy of the License at
  7
+*
  8
+*      http://www.apache.org/licenses/LICENSE-2.0
  9
+*
  10
+*	Unless required by applicable law or agreed to in writing, software
  11
+*	distributed under the License is distributed on an "AS IS" BASIS,
  12
+*	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+*	See the License for the specific language governing permissions and
  14
+*	limitations under the License.
  15
+*/
  16
+package org.xflatdb.xflat.util;
  17
+
  18
+/**
  19
+ * Represents a first-class function that takes one value as a parameter.
  20
+ * An action is invoked by calling the apply method.
  21
+ * @author Gordon
  22
+ */
  23
+public interface Action1<T> {
  24
+    /**
  25
+     * Invokes the action with the given parameters.
  26
+     * @param value The only parameter of this action.
  27
+     */
  28
+    public void apply(T value);
  29
+}
19  java/XFlat/src/org/xflatdb/xflat/util/XPathExpressionEqualityMatcher.java