Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

continuing to work on #10

Signed-off-by: gburgett <gordon.burgett@gmail.com>
  • Loading branch information...
commit d4b697917bd1e5050926bcea34991e1c5bd39f4c 1 parent eb4fe8d
@gburgett authored
View
20 java/XFlat/src/org/gburgett/xflat/convert/PojoConverter.java
@@ -4,7 +4,7 @@
*/
package org.gburgett.xflat.convert;
-import org.jdom2.Content;
+import org.jdom2.xpath.XPathExpression;
/**
* The interface for a PojoConverter. The implementation is loaded dynamically
@@ -22,4 +22,22 @@
* @return The new conversion service that the database should use.
*/
public ConversionService extend(ConversionService service);
+
+ /**
+ * Gets an XPath expression which will select the ID property
+ * of the converted data inside a row. Can be null if the class has no ID property.
+ * <p/>
+ * The context of the expression is the root of the row. For example, if your
+ * class "Foo" has a property "FooInt" which is your ID, and the class is converted
+ * to the following XML:<br/>
+ * {@code
+ * <foo>
+ * <fooInt>17</FooInt>
+ * </foo>
+ * }
+ * then the expression should be "foo/fooInt".
+ * @param clazz
+ * @return
+ */
+ public XPathExpression<?> idSelector(Class<?> clazz);
}
View
70 java/XFlat/src/org/gburgett/xflat/convert/converters/JAXBPojoConverter.java
@@ -4,27 +4,34 @@
*/
package org.gburgett.xflat.convert.converters;
-import java.io.IOException;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamReader;
import org.gburgett.xflat.convert.ConversionException;
import org.gburgett.xflat.convert.ConversionNotSupportedException;
import org.gburgett.xflat.convert.ConversionService;
import org.gburgett.xflat.convert.Converter;
import org.gburgett.xflat.convert.PojoConverter;
+import org.gburgett.xflat.db.IdAccessor;
import org.gburgett.xflat.util.JDOMStreamReader;
import org.gburgett.xflat.util.JDOMStreamWriter;
import org.jdom2.Document;
import org.jdom2.Element;
+import org.jdom2.Namespace;
+import org.jdom2.filter.Filters;
import org.jdom2.output.XMLOutputter;
+import org.jdom2.xpath.XPathExpression;
+import org.jdom2.xpath.XPathFactory;
/**
* A PojoConverter that extends a ConversionService to convert all unknown
@@ -49,6 +56,65 @@ public ConversionService extend(ConversionService service) {
return new JAXBConversionService(service);
}
+
+ private Map<Class<?>, XPathExpression<?>> idSelectorCache = new ConcurrentHashMap<>();
+
+ @Override
+ public XPathExpression<?> idSelector(Class<?> clazz) {
+ XPathExpression<?> ret = idSelectorCache.get(clazz);
+ if(ret == null){
+ ret = makeIdSelector(clazz);
+ idSelectorCache.put(clazz, ret);
+ }
+ return ret;
+ }
+
+ private XPathExpression<?> makeIdSelector(Class<?> clazz){
+ IdAccessor accessor = IdAccessor.forClass(clazz);
+
+ if(!accessor.hasId()){
+ return null;
+ }
+
+ Namespace ns = null;
+ StringBuilder ret = new StringBuilder(clazz.getSimpleName());
+
+ XmlAttribute attribute = (XmlAttribute) accessor.getIdPropertyAnnotation(XmlAttribute.class);
+ if(attribute != null){
+ ret.append("/@");
+ if(attribute.namespace() != null){
+ ns = Namespace.getNamespace("id", attribute.namespace());
+ ret.append(ns.getPrefix()).append(":");
+ }
+ if(attribute.name() != null){
+ ret.append(attribute.name());
+ }
+ else{
+ ret.append(accessor.getIdPropertyName());
+ }
+ }
+ else{
+ ret.append("/");
+ XmlElement element = (XmlElement) accessor.getIdPropertyAnnotation(XmlElement.class);
+ if(element != null){
+ if(element.namespace() != null){
+ ns = Namespace.getNamespace("id", attribute.namespace());
+ ret.append(ns.getPrefix()).append(":");
+ }
+ if(element.name() != null){
+ ret.append(element.name());
+ }
+ else{
+ ret.append(accessor.getIdPropertyName());
+ }
+ }
+ }
+
+ if(ns == null){
+ return XPathFactory.instance().compile(ret.toString());
+ }
+ return XPathFactory.instance().compile(ret.toString(), Filters.fpassthrough(), null, ns);
+ }
private static class JAXBConversionService implements ConversionService{
View
15 java/XFlat/src/org/gburgett/xflat/db/EngineAction.java
@@ -0,0 +1,15 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.gburgett.xflat.db;
+
+/**
+ * Represents an action that a table can perform with an engine.
+ * @param <T>
+ */
+public interface EngineAction<T> {
+
+ public T act(Engine engine);
+
+}
View
26 java/XFlat/src/org/gburgett/xflat/db/IdAccessor.java
@@ -7,6 +7,7 @@
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -17,6 +18,7 @@
import java.util.concurrent.ConcurrentHashMap;
import org.gburgett.xflat.Id;
import org.gburgett.xflat.XflatException;
+import org.jdom2.Attribute;
/**
* A helper class that accesses the IDs of an object.
@@ -235,4 +237,28 @@ public void setIdValue(T pojo, Object id)
throw new UnsupportedOperationException("Cannot get ID value when object has no ID");
}
+
+ public String getIdPropertyName(){
+ if(this.idProperty != null){
+ return this.idProperty.getName();
+ }
+
+ if(this.idField != null){
+ return this.idField.getName();
+ }
+
+ throw new UnsupportedOperationException("Cannot get field name when object has no ID");
+ }
+
+ public <U extends Annotation> U getIdPropertyAnnotation(Class<U> annotationClass){
+ if(this.idProperty != null){
+ return this.idProperty.getReadMethod().getAnnotation(annotationClass);
+ }
+
+ if(this.idField != null){
+ return this.idField.getAnnotation(annotationClass);
+ }
+
+ throw new UnsupportedOperationException("Cannot get annotation when object has no ID");
+ }
}
View
53 java/XFlat/src/org/gburgett/xflat/db/ShardedEngineBase.java
@@ -9,14 +9,16 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
+import org.gburgett.xflat.Cursor;
+import org.gburgett.xflat.EngineStateException;
import org.gburgett.xflat.Range;
import org.gburgett.xflat.ShardsetConfig;
import org.gburgett.xflat.XflatException;
import org.gburgett.xflat.convert.ConversionException;
-import org.gburgett.xflat.engine.IdShardedEngine;
import org.jdom2.Element;
/**
@@ -29,6 +31,8 @@
//the engines that are spinning down while this engine spins down
private Map<Range<T>, EngineBase> spinningDownEngines = new HashMap<>();
+ private WeakHashMap<Cursor<Range<T>>, String> openTableCursors = new WeakHashMap<>();
+
private final Object spinDownSyncRoot = new Object();
protected File directory;
@@ -77,48 +81,55 @@ public ShardedEngineBase(File file, String tableName, ShardsetConfig<T> config){
return ret;
}
- protected EngineBase getEngine(Range<T> range){
-
- EngineState state = getState();
- if(state == EngineState.Uninitialized || state == EngineState.SpunDown){
- throw new XflatException("Attempt to read or write to an engine in an uninitialized state");
- }
-
-
- TableMetadata ret = openShards.get(range);
-
- if(ret == null){
+ private EngineBase getEngine(Range<T> range){
+ TableMetadata metadata = openShards.get(range);
+ if(metadata == null){
//definitely ensure we aren't spinning down before we start up a new engine
synchronized(spinDownSyncRoot){
- state = getState();
+ EngineState state = getState();
if(state == EngineState.SpunDown){
throw new XflatException("Engine has already spun down");
}
//build the new metadata element so we can use it to provide engines
String name = range.getName();
- ret = this.getMetadataFactory().makeTableMetadata(name, new File(directory, name + ".xml"));
- TableMetadata weWereLate = openShards.putIfAbsent(range, ret);
+ metadata = this.getMetadataFactory().makeTableMetadata(name, new File(directory, name + ".xml"));
+ TableMetadata weWereLate = openShards.putIfAbsent(range, metadata);
if(weWereLate != null){
//another thread put the new metadata already
- ret = weWereLate;
+ metadata = weWereLate;
}
if(state == EngineState.SpinningDown){
EngineBase eng = spinningDownEngines.get(range);
if(eng == null){
- //we're requesting a new engine for some kind of read, get it and immediately begin spinning it down.
- eng = ret.provideEngine();
+ //we're requesting a new engine for some kind of read, get it and let the task spin it down.
+ eng = metadata.provideEngine();
spinningDownEngines.put(range, eng);
- ret.spinDown();
+ return eng;
}
- return eng;
}
}
}
- return ret.provideEngine();
+ return metadata.provideEngine();
+ }
+
+ protected <U> U doWithEngine(Range<T> range, EngineAction<U> action){
+
+ EngineState state = getState();
+ if(state == EngineState.Uninitialized || state == EngineState.SpunDown){
+ throw new XflatException("Attempt to read or write to an engine in an uninitialized state");
+ }
+
+ try{
+ return action.act(getEngine(range));
+ }
+ catch(EngineStateException ex){
+ //try one more time with a potentially new engine, if we still fail then let it go
+ return action.act(getEngine(range));
+ }
}
View
8 java/XFlat/src/org/gburgett/xflat/db/TableBase.java
@@ -70,12 +70,4 @@ void setEngineProvider(EngineProvider engine){
return action.act(this.engineProvider.provideEngine());
}
}
-
- /**
- * Represents an action that a table can perform with an engine.
- * @param <T>
- */
- protected interface EngineAction<T>{
- public T act(Engine engine);
- }
}
View
72 java/XFlat/src/org/gburgett/xflat/engine/IdShardedEngine.java
@@ -5,25 +5,14 @@
package org.gburgett.xflat.engine;
import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
import org.gburgett.xflat.Cursor;
import org.gburgett.xflat.DuplicateKeyException;
import org.gburgett.xflat.KeyNotFoundException;
-import org.gburgett.xflat.Range;
import org.gburgett.xflat.ShardsetConfig;
import org.gburgett.xflat.XflatException;
-import org.gburgett.xflat.convert.ConversionException;
import org.gburgett.xflat.db.Engine;
-import org.gburgett.xflat.db.EngineBase;
-import org.gburgett.xflat.db.EngineState;
+import org.gburgett.xflat.db.EngineAction;
import org.gburgett.xflat.db.ShardedEngineBase;
-import org.gburgett.xflat.db.TableMetadata;
import org.gburgett.xflat.db.XFlatDatabase;
import org.gburgett.xflat.query.XpathQuery;
import org.gburgett.xflat.query.XpathUpdate;
@@ -46,16 +35,25 @@ public IdShardedEngine(File file, String tableName, ShardsetConfig<T> config){
}
@Override
- public void insertRow(String id, Element data) throws DuplicateKeyException {
+ public void insertRow(final String id, final Element data) throws DuplicateKeyException {
- Engine e = this.getEngine(getRange(id));
- e.insertRow(id, data);
+ doWithEngine(getRange(id), new EngineAction(){
+ @Override
+ public Object act(Engine engine) {
+ engine.insertRow(id, data);
+ return null;
+ }
+ });
}
@Override
- public Element readRow(String id) {
- Engine e = this.getEngine(getRange(id));
- return e.readRow(id);
+ public Element readRow(final String id) {
+ return doWithEngine(getRange(id), new EngineAction<Element>(){
+ @Override
+ public Element act(Engine engine) {
+ return engine.readRow(id);
+ }
+ });
}
@Override
@@ -64,13 +62,24 @@ public Element readRow(String id) {
}
@Override
- public void replaceRow(String id, Element data) throws KeyNotFoundException {
- throw new UnsupportedOperationException("Not supported yet.");
+ public void replaceRow(final String id, final Element data) throws KeyNotFoundException {
+ doWithEngine(getRange(id), new EngineAction(){
+ @Override
+ public Object act(Engine engine) {
+ engine.replaceRow(id, data);
+ return null;
+ }
+ });
}
@Override
- public boolean update(String id, XpathUpdate update) throws KeyNotFoundException {
- throw new UnsupportedOperationException("Not supported yet.");
+ public boolean update(final String id, final XpathUpdate update) throws KeyNotFoundException {
+ return doWithEngine(getRange(id), new EngineAction<Boolean>(){
+ @Override
+ public Boolean act(Engine engine) {
+ return engine.update(id, update);
+ }
+ });
}
@Override
@@ -79,13 +88,24 @@ public int update(XpathQuery query, XpathUpdate update) {
}
@Override
- public boolean upsertRow(String id, Element data) {
- throw new UnsupportedOperationException("Not supported yet.");
+ public boolean upsertRow(final String id, final Element data) {
+ return doWithEngine(getRange(id), new EngineAction<Boolean>(){
+ @Override
+ public Boolean act(Engine engine) {
+ return engine.upsertRow(id, data);
+ }
+ });
}
@Override
- public void deleteRow(String id) throws KeyNotFoundException {
- throw new UnsupportedOperationException("Not supported yet.");
+ public void deleteRow(final String id) throws KeyNotFoundException {
+ doWithEngine(getRange(id), new EngineAction(){
+ @Override
+ public Object act(Engine engine) {
+ engine.deleteRow(id);
+ return null;
+ }
+ });
}
@Override
View
114 java/XFlat/src/org/gburgett/xflat/query/XpathQuery.java
@@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.Description;
@@ -20,6 +21,7 @@
import org.hamcrest.StringDescription;
import org.jdom2.Attribute;
import org.jdom2.Element;
+import org.jdom2.Namespace;
import org.jdom2.filter.AttributeFilter;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
@@ -94,9 +96,121 @@ private XpathQuery(QueryType type, Matcher<Element> rowMatcher, XpathQuery... qu
this.queryChain.addAll(Arrays.asList(queries));
}
+ private XpathQuery(){
+ }
+
+ private static class XPathExpressionEqualsMatcher<U> extends TypeSafeMatcher<XPathExpression<U>>{
+ private final XPathExpression<U> toMatch;
+
+ public XPathExpressionEqualsMatcher(XPathExpression<U> toMatch){
+ this.toMatch = toMatch;
+ }
+
+ @Override
+ protected boolean matchesSafely(XPathExpression<U> item) {
+ if(toMatch == null){
+ return item == null;
+ }
+
+ if(toMatch.getNamespaces().length != item.getNamespaces().length){
+ return false;
+ }
+
+ String itemExp = item.getExpression();
+
+ //match namespaces by prefix
+ for(Namespace n : toMatch.getNamespaces()){
+ boolean didFind = false;
+ for(Namespace i : item.getNamespaces()){
+ if(n.getURI().equals(i.getURI())){
+ didFind = true;
+ if(!n.getPrefix().equals(i.getPrefix())){
+ //allow for different namespace prefixes
+ itemExp = itemExp.replace(i.getPrefix() + ":", n.getPrefix() + ":");
+ }
+ }
+ }
+ if(!didFind){
+ return false;
+ }
+ }
+
+ if(!toMatch.getExpression().equals(itemExp)){
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ if(toMatch == null){
+ description.appendText("null XPath expression");
+ return;
+ }
+ description.appendText("XPath expression equal to ").appendText(toMatch.getExpression());
+ }
+
+
+ }
+
+ /**
+ * Returns a new query in which the given selector expression is replaced whenever it is found
+ * with the new selector expression.
+ * This is used primarily to replace expressions selecting the Id property of an object
+ * with the {@link #Id} expression, so that engines can take advantage of ID indexes.
+ * @param <U>
+ * @param selector The selector to replace.
+ * @param newSelector The new selector to use.
+ * @return A new XpathQuery with the replaced values.
+ */
+ public <U> XpathQuery replaceExpression(XPathExpression<U> selector, XPathExpression<U> newSelector){
+ return replaceExpression(new XPathExpressionEqualsMatcher<>(selector), newSelector);
+ }
+
+ /**
+ * Returns a new query in which the given selector expression is replaced whenever it is found
+ * with the new selector expression.
+ * This is used primarily to replace expressions selecting the Id property of an object
+ * with the {@link #Id} expression, so that engines can take advantage of ID indexes.
+ * @param <U>
+ * @param selector A matcher matching the selector expressions to replace.
+ * @param newSelector The new selector to use.
+ * @return A new XpathQuery with the replaced values.
+ */
+ public <U> XpathQuery replaceExpression(Matcher<XPathExpression<U>> selector, XPathExpression<U> newSelector){
+ XpathQuery ret = new XpathQuery();
+ ret.conversionService = this.conversionService;
+ ret.queryType = this.queryType;
+ ret.value = this.value;
+ ret.valueType = this.valueType;
+ ret.rowMatcher = this.rowMatcher;
+
+ //match expression
+ if(selector.matches(this.selector)){
+ ret.selector = newSelector;
+ }
+ else{
+ ret.selector = this.selector;
+ }
+
+ //check sub-expressions
+ if(this.queryChain != null){
+ ret.queryChain = new ArrayList<>(this.queryChain.size());
+ for(XpathQuery q : this.queryChain){
+ ret.queryChain.add(q.replaceExpression(selector, newSelector));
+ }
+ }
+
+ return ret;
+ }
+
/**
* An XPath expression selecting the database ID of a row.
* This can be used to build queries matching the row's ID.
+ * <p/>
+ * Currently this is the expression "@db:id".
+ *
*/
public static final XPathExpression<Attribute> Id = XPathFactory.instance().compile("@db:id", new AttributeFilter(), null, XFlatDatabase.xFlatNs);
View
6 java/XFlat/test/org/gburgett/xflat/engine/IdShardedEngineTest.java
@@ -27,6 +27,7 @@
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
+import org.jdom2.xpath.XPathExpression;
import static org.junit.Assert.*;
/**
@@ -48,6 +49,11 @@ protected EngineBase createInstance(TestContext ctx) {
public ConversionService extend(ConversionService service) {
return conversionService;
}
+
+ @Override
+ public XPathExpression<?> idSelector(Class<?> clazz) {
+ return null;
+ }
});
db.setEngineFactory(new EngineFactory(){
@Override
View
4 java/XFlat/test/org/gburgett/xflat/query/XpathQueryTest.java
@@ -11,13 +11,13 @@
import org.gburgett.xflat.db.XFlatDatabase;
import org.gburgett.xflat.query.XpathQuery.QueryType;
import org.jdom2.Element;
-import org.jdom2.filter.AttributeFilter;
+import org.jdom2.Namespace;
+import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Matchers;
import test.Foo;
/**
Please sign in to comment.
Something went wrong with that request. Please try again.