More caching fixes.

This commit is contained in:
Greg Burd 2017-10-27 14:00:24 -04:00
parent b04e033bf4
commit 7b14eda9b3
15 changed files with 555 additions and 284 deletions

View file

@ -1,9 +1,7 @@
package net.helenus.core;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.io.Serializable;
import java.util.*;
import com.google.common.primitives.Primitives;
@ -11,6 +9,7 @@ import net.helenus.core.reflect.DefaultPrimitiveTypes;
import net.helenus.core.reflect.Drafted;
import net.helenus.core.reflect.MapExportable;
import net.helenus.mapping.MappingUtil;
import org.apache.commons.lang3.SerializationUtils;
public abstract class AbstractEntityDraft<E> implements Drafted<E> {
@ -30,38 +29,49 @@ public abstract class AbstractEntityDraft<E> implements Drafted<E> {
}
@SuppressWarnings("unchecked")
protected <T> T get(Getter<T> getter, Class<?> returnType) {
public <T> T get(Getter<T> getter, Class<?> returnType) {
return (T) get(this.<T>methodNameFor(getter), returnType);
}
@SuppressWarnings("unchecked")
protected <T> T get(String key, Class<?> returnType) {
public <T> T get(String key, Class<?> returnType) {
T value = (T) backingMap.get(key);
if (value == null) {
value = (T) entityMap.get(key);
if (value == null) {
value = (T) entityMap.get(key);
if (value == null) {
if (Primitives.allPrimitiveTypes().contains(returnType)) {
if (Primitives.allPrimitiveTypes().contains(returnType)) {
DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType);
if (type == null) {
throw new RuntimeException("unknown primitive type " + returnType);
}
DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType);
if (type == null) {
throw new RuntimeException("unknown primitive type " + returnType);
}
return (T) type.getDefaultValue();
}
}
}
return (T) type.getDefaultValue();
}
} else {
// Collections fetched from the entityMap
if (value instanceof Collection) {
try {
value = MappingUtil.<T>clone(value);
}
catch (CloneNotSupportedException e) {
//TODO(gburd): deep?shallow? copy of List, Map, Set to a mutable collection.
value = (T)SerializationUtils.<Serializable>clone((Serializable)value);
}
}
}
}
return value;
}
protected <T> Object set(Getter<T> getter, Object value) {
public <T> Object set(Getter<T> getter, Object value) {
return set(this.<T>methodNameFor(getter), value);
}
protected Object set(String key, Object value) {
public Object set(String key, Object value) {
if (key == null || value == null) {
return null;
}
@ -71,11 +81,11 @@ public abstract class AbstractEntityDraft<E> implements Drafted<E> {
}
@SuppressWarnings("unchecked")
protected <T> T mutate(Getter<T> getter, T value) {
public <T> T mutate(Getter<T> getter, T value) {
return (T) mutate(this.<T>methodNameFor(getter), value);
}
protected Object mutate(String key, Object value) {
public Object mutate(String key, Object value) {
Objects.requireNonNull(key);
if (value == null) {

View file

@ -240,49 +240,48 @@ public final class HelenusSession extends AbstractSessionOperations implements C
@Override
public void mergeCache(Table<String, String, Either<Object, List<Facet>>> uowCache) {
List<Either<Object, List<Facet>>> items = uowCache.values().stream().distinct().collect(Collectors.toList());
for (Either<Object, List<Facet>> item : items) {
if (item.isRight()) {
List<Facet> facets = item.getRight();
String tableName = CacheUtil.schemaName(facets);
List<String[]> combinations = CacheUtil.flattenFacets(facets);
for (String[] combination : combinations) {
String cacheKey = tableName + "." + Arrays.toString(combination);
sessionCache.invalidate(cacheKey);
}
} else {
Object pojo = item.getLeft();
HelenusEntity entity = Helenus.resolve(MappingUtil.getMappingInterface(pojo));
Map<String, Object> valueMap = pojo instanceof MapExportable ? ((MapExportable) pojo).toMap() : null;
if (entity.isCacheable()) {
List<Facet> boundFacets = new ArrayList<>();
for (Facet facet : entity.getFacets()) {
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
unboundFacet.getProperties().forEach(prop -> {
if (valueMap == null) {
Object value = BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop,
false);
binder.setValueForProperty(prop, value.toString());
} else {
binder.setValueForProperty(prop, valueMap.get(prop.getPropertyName()).toString());
}
});
if (binder.isBound()) {
boundFacets.add(binder.bind());
}
} else {
boundFacets.add(facet);
}
}
// NOTE: should equal `String tableName = CacheUtil.schemaName(facets);`
List<String[]> facetCombinations = CacheUtil.flattenFacets(boundFacets);
String tableName = CacheUtil.schemaName(boundFacets);
mergeAndUpdateCacheValues(pojo, tableName, facetCombinations);
}
}
}
List<Object> items = uowCache.values().stream().filter(Either::isLeft).map(Either::getLeft).distinct().collect(Collectors.toList());
for (Object pojo : items) {
HelenusEntity entity = Helenus.resolve(MappingUtil.getMappingInterface(pojo));
Map<String, Object> valueMap = pojo instanceof MapExportable ? ((MapExportable) pojo).toMap() : null;
if (entity.isCacheable()) {
List<Facet> boundFacets = new ArrayList<>();
for (Facet facet : entity.getFacets()) {
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
unboundFacet.getProperties().forEach(prop -> {
if (valueMap == null) {
Object value = BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop);
binder.setValueForProperty(prop, value.toString());
} else {
binder.setValueForProperty(prop, valueMap.get(prop.getPropertyName()).toString());
}
});
if (binder.isBound()) {
boundFacets.add(binder.bind());
}
} else {
boundFacets.add(facet);
}
}
// NOTE: should equal `String tableName = CacheUtil.schemaName(facets);`
List<String[]> facetCombinations = CacheUtil.flattenFacets(boundFacets);
String tableName = CacheUtil.schemaName(boundFacets);
mergeAndUpdateCacheValues(pojo, tableName, facetCombinations);
}
}
List<List<Facet>> deletedFacetSets = uowCache.values().stream().filter(Either::isRight).map(Either::getRight).collect(
Collectors.toList());
for (List<Facet> facets : deletedFacetSets) {
String tableName = CacheUtil.schemaName(facets);
List<String[]> combinations = CacheUtil.flattenFacets(facets);
for (String[] combination : combinations) {
String cacheKey = tableName + "." + Arrays.toString(combination);
sessionCache.invalidate(cacheKey);
}
}
}
private void mergeAndUpdateCacheValues(Object pojo, String tableName, List<String[]> facetCombinations) {
@ -503,13 +502,21 @@ public final class HelenusSession extends AbstractSessionOperations implements C
return new UpdateOperation<ResultSet>(this);
}
public <E> UpdateOperation<E> update(Object pojo) {
if (pojo instanceof MapExportable == false) {
throw new HelenusMappingException(
"update of objects that don't implement MapExportable is not yet supported");
}
return new UpdateOperation<E>(this, pojo);
}
public <E> UpdateOperation<E> update(Drafted<E> drafted) {
if (drafted instanceof AbstractEntityDraft == false) {
throw new HelenusMappingException(
"update of draft objects that don't inherit from AbstractEntityDraft is not yet supported");
}
if (drafted instanceof AbstractEntityDraft == false) {
throw new HelenusMappingException(
"update of draft objects that don't inherit from AbstractEntityDraft is not yet supported");
}
AbstractEntityDraft<E> draft = (AbstractEntityDraft<E>) drafted;
UpdateOperation update = new UpdateOperation<E>(this, draft);
UpdateOperation update = new UpdateOperation<E>(this, draft);
Map<String, Object> map = draft.toMap();
Set<String> mutatedProperties = draft.mutated();
HelenusEntity entity = Helenus.entity(draft.getEntityClass());

View file

@ -15,6 +15,7 @@
*/
package net.helenus.core.cache;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@ -23,7 +24,13 @@ import net.helenus.mapping.HelenusProperty;
public class BoundFacet extends Facet<String> {
private final Map<HelenusProperty, Object> properties;
BoundFacet(String name, Map<HelenusProperty, Object> properties) {
public BoundFacet(HelenusProperty property, Object value) {
super(property.getPropertyName(), value == null ? null : value.toString());
this.properties = new HashMap<HelenusProperty, Object>(1);
this.properties.put(property, value);
}
public BoundFacet(String name, Map<HelenusProperty, Object> properties) {
super(name,
(properties.keySet().size() > 1)
? "[" + String.join(", ",

View file

@ -1,7 +1,14 @@
package net.helenus.core.cache;
import net.helenus.core.Helenus;
import net.helenus.core.reflect.MapExportable;
import net.helenus.mapping.HelenusEntity;
import net.helenus.mapping.MappingUtil;
import net.helenus.mapping.value.BeanColumnValueProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CacheUtil {
@ -38,7 +45,22 @@ public class CacheUtil {
}
public static Object merge(Object to, Object from) {
return to; // TODO(gburd): yeah...
if (to == from) {
return to;
}
//TODO(gburd): take ttl and writeTime into account when merging.
Map<String, Object> toValueMap = to instanceof MapExportable ? ((MapExportable) to).toMap() : null;
Map<String, Object> fromValueMap = to instanceof MapExportable ? ((MapExportable) from).toMap() : null;
if (toValueMap != null && fromValueMap != null) {
for (String key : fromValueMap.keySet()) {
if (toValueMap.containsKey(key) && toValueMap.get(key) != fromValueMap.get(key)) {
toValueMap.put(key, fromValueMap.get(key));
}
}
}
return to;
}
public static String schemaName(List<Facet> facets) {

View file

@ -15,10 +15,13 @@
*/
package net.helenus.core.operation;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
import net.helenus.core.*;
import net.helenus.core.cache.Facet;
import net.helenus.core.cache.UnboundFacet;
import net.helenus.mapping.HelenusEntity;
import net.helenus.mapping.HelenusProperty;
public abstract class AbstractFilterOperation<E, O extends AbstractFilterOperation<E, O>>
extends
@ -108,4 +111,38 @@ public abstract class AbstractFilterOperation<E, O extends AbstractFilterOperati
ifFilters.add(filter);
}
protected List<Facet> bindFacetValues(List<Facet> facets) {
if (facets == null) {
return new ArrayList<Facet>();
}
List<Facet> boundFacets = new ArrayList<>();
Map<HelenusProperty, Filter> filterMap = new HashMap<>(filters.size());
filters.forEach(f -> filterMap.put(f.getNode().getProperty(), f));
for (Facet facet : facets) {
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
if (filters != null) {
for (HelenusProperty prop : unboundFacet.getProperties()) {
Filter filter = filterMap.get(prop);
if (filter != null) {
Object[] postulates = filter.postulateValues();
for (Object p : postulates) {
binder.setValueForProperty(prop, p.toString());
}
}
}
}
if (binder.isBound()) {
boundFacets.add(binder.bind());
}
} else {
boundFacets.add(facet);
}
}
return boundFacets;
}
}

View file

@ -66,9 +66,9 @@ public abstract class AbstractOptionalOperation<E, O extends AbstractOptionalOpe
try {
Optional<E> result = Optional.empty();
E cacheResult = null;
boolean updateCache = isSessionCacheable();
boolean updateCache = isSessionCacheable() && checkCache;
if (enableCache && isSessionCacheable()) {
if (checkCache && isSessionCacheable()) {
List<Facet> facets = bindFacetValues();
String tableName = CacheUtil.schemaName(facets);
cacheResult = (E) sessionOps.checkCache(tableName, facets);
@ -115,7 +115,7 @@ public abstract class AbstractOptionalOperation<E, O extends AbstractOptionalOpe
E cachedResult = null;
final boolean updateCache;
if (enableCache) {
if (checkCache) {
Stopwatch timer = Stopwatch.createStarted();
try {
List<Facet> facets = bindFacetValues();

View file

@ -45,7 +45,7 @@ import net.helenus.support.HelenusException;
public abstract class AbstractStatementOperation<E, O extends AbstractStatementOperation<E, O>> extends Operation<E> {
protected boolean enableCache = true;
protected boolean checkCache = true;
protected boolean showValues = true;
protected TraceContext traceContext;
long queryExecutionTimeout = 10;
@ -66,13 +66,13 @@ public abstract class AbstractStatementOperation<E, O extends AbstractStatementO
public abstract Statement buildStatement(boolean cached);
public O ignoreCache(boolean enabled) {
enableCache = enabled;
public O uncached(boolean enabled) {
checkCache = enabled;
return (O) this;
}
public O ignoreCache() {
enableCache = true;
public O uncached() {
checkCache = false;
return (O) this;
}
@ -333,10 +333,10 @@ public abstract class AbstractStatementOperation<E, O extends AbstractStatementO
List<Facet> facets = new ArrayList<>();
Map<String, Object> valueMap = pojo instanceof MapExportable ? ((MapExportable) pojo).toMap() : null;
for (Facet facet : identifyingFacets) {
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
for (Facet facet : identifyingFacets) {
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
for (HelenusProperty prop : unboundFacet.getProperties()) {
Object value;
if (valueMap == null) {

View file

@ -69,7 +69,7 @@ public abstract class AbstractStreamOperation<E, O extends AbstractStreamOperati
E cacheResult = null;
boolean updateCache = isSessionCacheable();
if (enableCache && isSessionCacheable()) {
if (checkCache && isSessionCacheable()) {
List<Facet> facets = bindFacetValues();
String tableName = CacheUtil.schemaName(facets);
cacheResult = (E) sessionOps.checkCache(tableName, facets);
@ -121,7 +121,7 @@ public abstract class AbstractStreamOperation<E, O extends AbstractStreamOperati
E cachedResult = null;
final boolean updateCache;
if (enableCache) {
if (checkCache) {
Stopwatch timer = Stopwatch.createStarted();
try {
List<Facet> facets = bindFacetValues();

View file

@ -133,40 +133,9 @@ public final class DeleteOperation extends AbstractFilterOperation<ResultSet, De
}
}
public List<Facet> bindFacetValues(List<Facet> facets) {
if (facets == null) {
return new ArrayList<Facet>();
}
List<Facet> boundFacets = new ArrayList<>();
Map<HelenusProperty, Filter> filterMap = new HashMap<>(filters.size());
filters.forEach(f -> filterMap.put(f.getNode().getProperty(), f));
for (Facet facet : facets) {
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
if (filters != null) {
for (HelenusProperty prop : unboundFacet.getProperties()) {
Filter filter = filterMap.get(prop);
if (filter != null) {
Object[] postulates = filter.postulateValues();
for (Object p : postulates) {
binder.setValueForProperty(prop, p.toString());
}
}
}
}
if (binder.isBound()) {
boundFacets.add(binder.bind());
}
} else {
boundFacets.add(facet);
}
}
return boundFacets;
}
public List<Facet> bindFacetValues() {
return bindFacetValues(getFacets());
}
@Override
public ResultSet sync() throws TimeoutException {
@ -177,20 +146,19 @@ public final class DeleteOperation extends AbstractFilterOperation<ResultSet, De
return result;
}
@Override
public List<Facet> getFacets() {
return entity.getFacets();
}
@Override
public ResultSet sync(UnitOfWork uow) throws TimeoutException {
if (uow == null) {
return sync();
}
ResultSet result = super.sync(uow);
List<Facet> facets = getFacets();
uow.cacheEvict(bindFacetValues(facets));
uow.cacheEvict(bindFacetValues());
return result;
}
@Override
public List<Facet> getFacets() {
return entity.getFacets();
}
}

View file

@ -44,7 +44,7 @@ public final class InsertOperation<T> extends AbstractOperation<T, InsertOperati
private final List<Fun.Tuple2<HelenusPropertyNode, Object>> values = new ArrayList<Fun.Tuple2<HelenusPropertyNode, Object>>();
private final T pojo;
private final Class<?> resultType;
private final Class<?> resultType;
private HelenusEntity entity;
private boolean ifNotExists;
@ -56,7 +56,7 @@ public final class InsertOperation<T> extends AbstractOperation<T, InsertOperati
this.ifNotExists = ifNotExists;
this.pojo = null;
this.resultType = ResultSet.class;
this.resultType = ResultSet.class;
}
public InsertOperation(AbstractSessionOperations sessionOperations, Class<?> resultType, boolean ifNotExists) {
@ -251,10 +251,14 @@ public final class InsertOperation<T> extends AbstractOperation<T, InsertOperati
return sync();
}
T result = super.sync(uow);
Class<?> iface = entity.getMappingInterface();
if (resultType == iface) {
Class<?> iface = entity.getMappingInterface();
if (resultType == iface) {
cacheUpdate(uow, result, entity.getFacets());
}
} else {
if (entity.isCacheable()) {
sessionOps.cacheEvict(bindFacetValues());
}
}
return result;
}

View file

@ -207,7 +207,7 @@ public final class SelectOperation<E> extends AbstractFilterStreamOperation<E, S
if (facet instanceof UnboundFacet) {
UnboundFacet unboundFacet = (UnboundFacet) facet;
UnboundFacet.Binder binder = unboundFacet.binder();
for (HelenusProperty prop : unboundFacet.getProperties()) {
for (HelenusProperty prop : unboundFacet.getProperties()) {
if (filters != null) {
Filter filter = filters.get(prop);
if (filter != null) {

View file

@ -18,6 +18,7 @@ package net.helenus.core.operation;
import java.util.*;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.querybuilder.Assignment;
@ -26,20 +27,25 @@ import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Update;
import net.helenus.core.*;
import net.helenus.core.cache.BoundFacet;
import net.helenus.core.cache.Facet;
import net.helenus.core.cache.UnboundFacet;
import net.helenus.core.reflect.HelenusPropertyNode;
import net.helenus.core.reflect.MapExportable;
import net.helenus.mapping.HelenusEntity;
import net.helenus.mapping.HelenusProperty;
import net.helenus.mapping.MappingUtil;
import net.helenus.mapping.value.BeanColumnValueProvider;
import net.helenus.support.HelenusMappingException;
import net.helenus.support.Immutables;
public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateOperation<E>> {
private final List<Assignment> assignments = new ArrayList<Assignment>();
private final Map<Assignment, BoundFacet> assignments = new HashMap<>();
private final AbstractEntityDraft<E> draft;
private final Map<String, Object> draftMap;
private HelenusEntity entity = null;
private Object pojo;
private int[] ttl;
private long[] timestamp;
@ -55,24 +61,47 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
this.draftMap = draft.toMap();
}
public UpdateOperation(AbstractSessionOperations sessionOperations, HelenusPropertyNode p, Object v) {
super(sessionOperations);
this.draft = null;
this.draftMap = null;
public UpdateOperation(AbstractSessionOperations sessionOperations, Object pojo) {
super(sessionOperations);
this.draft = null;
this.draftMap = null;
this.pojo = pojo;
}
Object value = sessionOps.getValuePreparer().prepareColumnValue(v, p.getProperty());
assignments.add(QueryBuilder.set(p.getColumnName(), value));
public UpdateOperation(AbstractSessionOperations sessionOperations, HelenusPropertyNode p, Object v) {
super(sessionOperations);
this.draft = null;
this.draftMap = null;
addPropertyNode(p);
}
Object value = sessionOps.getValuePreparer().prepareColumnValue(v, p.getProperty());
assignments.put(QueryBuilder.set(p.getColumnName(), value), new BoundFacet(p.getProperty(), v));
public <V> UpdateOperation<E> set(Getter<V> getter, V v) {
addPropertyNode(p);
}
public <V> UpdateOperation<E> set(Getter<V> getter, V v) {
Objects.requireNonNull(getter, "getter is empty");
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(getter);
HelenusProperty prop = p.getProperty();
Object value = sessionOps.getValuePreparer().prepareColumnValue(v, p.getProperty());
assignments.add(QueryBuilder.set(p.getColumnName(), value));
Object value = sessionOps.getValuePreparer().prepareColumnValue(v, prop);
assignments.put(QueryBuilder.set(p.getColumnName(), value), new BoundFacet(prop, value));
if (draft != null) {
String key = prop.getPropertyName();
if (draft.get(key, value.getClass()) != value) {
draft.set(key, value);
}
}
if (pojo != null && pojo instanceof MapExportable) {
String key = prop.getPropertyName();
Map<String, Object> map = ((MapExportable)pojo).toMap();
if (map.get(key) != value) {
map.put(key, value);
}
}
addPropertyNode(p);
@ -97,15 +126,20 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(counterGetter);
assignments.add(QueryBuilder.incr(p.getColumnName(), delta));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
Long value = (Long)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop);
facet = new BoundFacet(prop, value + delta);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
draftMap.put(key, (Long) draftMap.get(key) + delta);
}
assignments.put(QueryBuilder.incr(p.getColumnName(), delta), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
draftMap.put(key, (Long) draftMap.get(key) + delta);
}
return this;
}
@ -119,15 +153,20 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(counterGetter);
assignments.add(QueryBuilder.decr(p.getColumnName(), delta));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
Long value = (Long) BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop);
facet = new BoundFacet(prop, value - delta);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
draftMap.put(key, (Long) draftMap.get(key) - delta);
}
assignments.put(QueryBuilder.decr(p.getColumnName(), delta), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
draftMap.put(key, (Long) draftMap.get(key) - delta);
}
return this;
}
@ -146,16 +185,22 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
Object valueObj = prepareSingleListValue(p, value);
assignments.add(QueryBuilder.prepend(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
List<V> list = new ArrayList<V>((List<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
list.add(0, value);
facet = new BoundFacet(prop, list);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.add(0, value);
}
assignments.put(QueryBuilder.prepend(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.add(0, value);
}
return this;
}
@ -167,16 +212,22 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
List valueObj = prepareListValue(p, value);
assignments.add(QueryBuilder.prependAll(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
List<V> list = new ArrayList<V>((List<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
list.addAll(0, value);
facet = new BoundFacet(prop, list);
} else if (draft != null && value.size() > 0) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.addAll(0, value);
}
assignments.put(QueryBuilder.prependAll(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null && value.size() > 0) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.addAll(0, value);
}
return this;
}
@ -188,23 +239,31 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
Object valueObj = prepareSingleListValue(p, value);
assignments.add(QueryBuilder.setIdx(p.getColumnName(), idx, valueObj));
BoundFacet facet = null;
if (pojo != null || draft != null) {
List<V> list;
HelenusProperty prop = p.getProperty();
if (pojo != null) {
list = new ArrayList<V>((List<V>) BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
} else {
String key = p.getProperty().getPropertyName();
list = (List<V>) draftMap.get(key);
}
if (idx < 0) {
list.add(0, value);
} else if (idx > list.size()) {
list.add(list.size(), value);
} else {
list.add(idx, value);
}
list.add(0, value);
facet = new BoundFacet(prop, list);
}
assignments.put(QueryBuilder.setIdx(p.getColumnName(), idx, valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
if (idx < 0) {
list.add(0, value);
} else if (idx > list.size()) {
list.add(list.size(), value);
} else {
list.add(idx, value);
}
list.add(0, value);
}
return this;
}
@ -216,16 +275,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
Object valueObj = prepareSingleListValue(p, value);
assignments.add(QueryBuilder.append(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
List<V> list = new ArrayList<V>((List<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
list.add(value);
facet = new BoundFacet(prop, list);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.add(value);
}
assignments.put(QueryBuilder.append(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.add(value);
}
return this;
}
@ -237,16 +301,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
List valueObj = prepareListValue(p, value);
assignments.add(QueryBuilder.appendAll(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
List<V> list = new ArrayList<V>((List<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
list.addAll(value);
facet = new BoundFacet(prop, list);
} else if (draft != null && value.size() > 0) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.addAll(value);
}
assignments.put(QueryBuilder.appendAll(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null && value.size() > 0) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.addAll(value);
}
return this;
}
@ -258,16 +327,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
Object valueObj = prepareSingleListValue(p, value);
assignments.add(QueryBuilder.discard(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
List<V> list = new ArrayList<V>((List<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
list.remove(value);
facet = new BoundFacet(prop, list);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.remove(value);
}
assignments.put(QueryBuilder.discard(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.remove(value);
}
return this;
}
@ -279,16 +353,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(listGetter);
List valueObj = prepareListValue(p, value);
assignments.add(QueryBuilder.discardAll(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
List<V> list = new ArrayList<V>((List<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
list.removeAll(value);
facet = new BoundFacet(prop, list);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.removeAll(value);
}
assignments.put(QueryBuilder.discardAll(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
List<V> list = (List<V>) draftMap.get(key);
list.removeAll(value);
}
return this;
}
@ -336,16 +415,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(setGetter);
Object valueObj = prepareSingleSetValue(p, value);
assignments.add(QueryBuilder.add(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
Set<V> set = new HashSet<V>((Set<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
set.add(value);
facet = new BoundFacet(prop, set);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.add(value);
}
assignments.put(QueryBuilder.add(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.add(value);
}
return this;
}
@ -357,16 +441,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(setGetter);
Set valueObj = prepareSetValue(p, value);
assignments.add(QueryBuilder.addAll(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
Set<V> set = new HashSet<V>((Set<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
set.addAll(value);
facet = new BoundFacet(prop, set);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.addAll(value);
}
assignments.put(QueryBuilder.addAll(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.addAll(value);
}
return this;
}
@ -378,16 +467,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(setGetter);
Object valueObj = prepareSingleSetValue(p, value);
assignments.add(QueryBuilder.remove(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
Set<V> set = new HashSet<V>((Set<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
set.remove(value);
facet = new BoundFacet(prop, set);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.remove(value);
}
assignments.put(QueryBuilder.remove(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.remove(value);
}
return this;
}
@ -399,16 +493,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(setGetter);
Set valueObj = prepareSetValue(p, value);
assignments.add(QueryBuilder.removeAll(p.getColumnName(), valueObj));
BoundFacet facet = null;
if (pojo != null) {
HelenusProperty prop = p.getProperty();
Set<V> set = new HashSet<V>((Set<V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
set.removeAll(value);
facet = new BoundFacet(prop, set);
} else if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.removeAll(value);
}
assignments.put(QueryBuilder.removeAll(p.getColumnName(), valueObj), facet);
addPropertyNode(p);
if (draft != null) {
String key = p.getProperty().getPropertyName();
Set<V> set = (Set<V>) draftMap.get(key);
set.removeAll(value);
}
return this;
}
@ -455,23 +554,28 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(mapGetter);
HelenusProperty prop = p.getProperty();
BoundFacet facet = null;
if (pojo != null) {
Map<K, V> map = new HashMap<K, V>((Map<K, V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
map.put(key, value);
facet = new BoundFacet(prop, map);
} else if (draft != null) {
((Map<K, V>) draftMap.get(prop.getPropertyName())).put(key, value);
}
Optional<Function<Object, Object>> converter = prop.getWriteConverter(sessionOps.getSessionRepository());
if (converter.isPresent()) {
Map<Object, Object> convertedMap = (Map<Object, Object>) converter.get()
.apply(Immutables.mapOf(key, value));
for (Map.Entry<Object, Object> e : convertedMap.entrySet()) {
assignments.add(QueryBuilder.put(p.getColumnName(), e.getKey(), e.getValue()));
assignments.put(QueryBuilder.put(p.getColumnName(), e.getKey(), e.getValue()), facet);
}
} else {
assignments.add(QueryBuilder.put(p.getColumnName(), key, value));
assignments.put(QueryBuilder.put(p.getColumnName(), key, value), facet);
}
addPropertyNode(p);
if (draft != null) {
((Map<K, V>) draftMap.get(prop.getPropertyName())).put(key, value);
}
return this;
}
@ -483,20 +587,25 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
HelenusPropertyNode p = MappingUtil.resolveMappingProperty(mapGetter);
HelenusProperty prop = p.getProperty();
BoundFacet facet = null;
if (pojo != null) {
Map<K, V> newMap = new HashMap<K, V>((Map<K, V>)BeanColumnValueProvider.INSTANCE.getColumnValue(pojo, -1, prop));
newMap.putAll(map);
facet = new BoundFacet(prop, newMap);
} else if (draft != null) {
((Map<K, V>) draftMap.get(prop.getPropertyName())).putAll(map);
}
Optional<Function<Object, Object>> converter = prop.getWriteConverter(sessionOps.getSessionRepository());
if (converter.isPresent()) {
Map convertedMap = (Map) converter.get().apply(map);
assignments.add(QueryBuilder.putAll(p.getColumnName(), convertedMap));
assignments.put(QueryBuilder.putAll(p.getColumnName(), convertedMap), facet);
} else {
assignments.add(QueryBuilder.putAll(p.getColumnName(), map));
assignments.put(QueryBuilder.putAll(p.getColumnName(), map), facet);
}
addPropertyNode(p);
if (draft != null) {
((Map<K, V>) draftMap.get(prop.getPropertyName())).putAll(map);
}
return this;
}
@ -509,7 +618,7 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
Update update = QueryBuilder.update(entity.getName().toCql());
for (Assignment assignment : assignments) {
for (Assignment assignment : assignments.keySet()) {
update.with(assignment);
}
@ -571,8 +680,14 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
@Override
public E sync() throws TimeoutException {
E result = super.sync();
if (entity.isCacheable() && draft != null) {
sessionOps.updateCache(result, getFacets());
if (entity.isCacheable()) {
if (draft != null) {
sessionOps.updateCache(draft, bindFacetValues());
} else if (pojo != null) {
sessionOps.updateCache(pojo, bindFacetValues());
} else {
sessionOps.cacheEvict(bindFacetValues());
}
}
return result;
}
@ -584,12 +699,21 @@ public final class UpdateOperation<E> extends AbstractFilterOperation<E, UpdateO
}
E result = super.sync(uow);
if (draft != null) {
cacheUpdate(uow, result, getFacets());
}
cacheUpdate(uow, result, bindFacetValues());
} else if (pojo != null) {
cacheUpdate(uow, (E)pojo, bindFacetValues());
}
return result;
}
@Override
@Override
public List<Facet> bindFacetValues() {
List<Facet> facets = bindFacetValues(entity.getFacets());
facets.addAll(assignments.values().stream().distinct().filter(o -> o != null).collect(Collectors.toList()));
return facets;
}
@Override
public List<Facet> getFacets() {
if (entity != null) {
return entity.getFacets();

View file

@ -102,7 +102,8 @@ public class MapperInvocationHandler<E> implements InvocationHandler, Serializab
}
if (MapExportable.TO_MAP_METHOD.equals(methodName)) {
return Collections.unmodifiableMap(src);
//return Collections.unmodifiableMap(src);
return src;
}
Object value = src.get(methodName);

View file

@ -16,6 +16,7 @@
package net.helenus.mapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@ -282,4 +283,43 @@ public final class MappingUtil {
return e.getPropertyNode();
}
}
// https://stackoverflow.com/a/4882306/366692
public static <T> T clone(T object)
throws CloneNotSupportedException {
Object clone = null;
// Use reflection, because there is no other way
try {
Method method = object.getClass().getMethod("clone");
clone = method.invoke(object);
} catch (InvocationTargetException e) {
rethrow(e.getCause());
} catch (Exception cause) {
rethrow(cause);
}
if (object.getClass().isInstance(clone)) {
@SuppressWarnings("unchecked") // clone class <= object class <= T
T t = (T) clone;
return t;
} else {
throw new ClassCastException(clone.getClass().getName());
}
}
private static void rethrow(Throwable cause)
throws CloneNotSupportedException {
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
if (cause instanceof CloneNotSupportedException) {
throw (CloneNotSupportedException) cause;
}
CloneNotSupportedException e = new CloneNotSupportedException();
e.initCause(cause);
throw e;
}
}

View file

@ -140,41 +140,92 @@ public class UnitOfWorkTest extends AbstractEmbeddedCassandraTest {
});
}
}
@Test
public void testSelectAfterDeleted() throws Exception {
Widget w1, w2, w3, w4;
UUID key = UUIDs.timeBased();
// This should inserted Widget, but not cache it.
w1 = session.<Widget>insert(widget).value(widget::id, key).value(widget::name, RandomString.make(20)).sync();
@Test
public void testSelectAfterUpdated() throws Exception {
Widget w1, w2, w3, w4, w5, w6;
UUID key = UUIDs.timeBased();
try (UnitOfWork uow = session.begin()) {
// This should inserted Widget, but not cache it.
w1 = session.<Widget>insert(widget).value(widget::id, key).value(widget::name, RandomString.make(20)).sync();
// This should read from the database and return a Widget.
w2 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
try (UnitOfWork uow = session.begin()) {
// This should read from the database and return a Widget.
w2 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
.sync(uow).orElse(null);
Assert.assertEquals(w1, w2);
// This should remove the object from the cache.
session.delete(widget).where(widget::id, eq(key))
// This should remove the object from the cache.
//TODO(gburd): w3 = session.
session.<Widget>update(w2)
.set(widget::name, "Bill")
.where(widget::id, eq(key))
.sync(uow);
// This should fail to read from the cache.
w3 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
// Fetch from session cache, should have old name.
w4 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
.sync().orElse(null);
Assert.assertEquals(w4, w2);
Assert.assertEquals(w4.name(), w1.name());
// This should skip the cache.
w5 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
.uncached()
.sync().orElse(null);
Assert.assertNotEquals(w5, w2); // Not the same instance
Assert.assertTrue(w2.equals(w5)); // But they have the same values
Assert.assertFalse(w5.equals(w2)); // TODO(gburd): should also work
Assert.assertEquals(w5.name(), "Bill");
uow.commit().andThen(() -> {
Assert.assertEquals(w1, w2);
});
}
// The name changed, this should miss cache and not find anything in the database.
w6 = session.<Widget>select(widget).where(widget::name, eq(w1.name())).single()
.sync().orElse(null);
Assert.assertTrue(w2.equals(w5));
}
@Test
public void testSelectAfterDeleted() throws Exception {
Widget w1, w2, w3, w4;
UUID key = UUIDs.timeBased();
// This should inserted Widget, but not cache it.
w1 = session.<Widget>insert(widget).value(widget::id, key).value(widget::name, RandomString.make(20)).sync();
try (UnitOfWork uow = session.begin()) {
// This should read from the database and return a Widget.
w2 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
.sync(uow).orElse(null);
Assert.assertEquals(w3, null);
// This should remove the object from the cache.
session.delete(widget).where(widget::id, eq(key))
.sync(uow);
uow.commit().andThen(() -> {
Assert.assertEquals(w1, w2);
Assert.assertEquals(w3, null);
});
}
// This should fail to read from the cache.
w3 = session.<Widget>select(widget).where(widget::id, eq(key)).single()
.sync(uow).orElse(null);
w4 = session.<Widget>select(widget).where(widget::name, eq(w1.name())).single()
Assert.assertEquals(w3, null);
uow.commit().andThen(() -> {
Assert.assertEquals(w1, w2);
Assert.assertEquals(w3, null);
});
}
w4 = session.<Widget>select(widget).where(widget::name, eq(w1.name())).single()
.sync().orElse(null);
Assert.assertEquals(w4, null);
}
Assert.assertEquals(w4, null);
}
/*
* @Test public void testSelectAfterInsertProperlyCachesEntity() throws
@ -200,7 +251,7 @@ public class UnitOfWorkTest extends AbstractEmbeddedCassandraTest {
*
* // This should read the widget from the database, no object identity but
* values should match. w4 = session.<Widget>select(widget) .where(widget::id,
* eq(key)) .ignoreCache() .single() .sync() .orElse(null);
* eq(key)) .uncached() .single() .sync() .orElse(null);
*
* Assert.assertNotEquals(w1, w4); Assert.assertTrue(w1.equals(w4)); }
*/