#115 - Mapping - Add support for @ElementCollection enhancement

Add element collection L2 cache support
This commit is contained in:
Rob Bygrave
2018-03-29 01:15:58 +13:00
parent 07946cd505
commit acd5578d8a
52 changed files with 734 additions and 135 deletions
@@ -63,7 +63,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.ebeanServerName = loader.getName();
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.readOnly = ownerBean._ebean_getIntercept().isReadOnly();
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
}
@Override
@@ -1,9 +1,11 @@
package io.ebeaninternal.api;
import com.fasterxml.jackson.core.JsonGenerator;
import io.ebean.plugin.BeanType;
import io.ebean.text.json.JsonContext;
import io.ebean.text.json.JsonWriteOptions;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.Writer;
@@ -21,4 +23,10 @@ public interface SpiJsonContext extends JsonContext {
* Create a Json Writer for writing beans as JSON supplying a writer.
*/
SpiJsonWriter createJsonWriter(Writer writer);
/**
* Create a Json Reader for reading the JSON content.
*/
SpiJsonReader createJsonRead(BeanType<?> beanType, String json);
}
@@ -0,0 +1,36 @@
package io.ebeaninternal.api.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
import java.util.Map;
public interface SpiJsonReader {
PersistenceContext getPersistenceContext();
SpiJsonReader forJson(JsonParser moreJson, boolean resetContext);
<T> void persistenceContextPut(Object beanId, T currentBean);
Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor<?> beanDesc);
ObjectMapper getObjectMapper();
JsonParser getParser();
JsonToken nextToken() throws IOException;
void pushPath(String path);
void popPath();
void beanVisitor(Object bean, Map<String, Object> unmappedProperties);
Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException;
}
@@ -1,9 +1,10 @@
package io.ebeaninternal.server.text.json;
package io.ebeaninternal.api.json;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.JsonWriter;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
import java.util.Collection;
/**
@@ -11,6 +12,11 @@ import java.util.Collection;
*/
public interface SpiJsonWriter extends JsonWriter {
/**
* Flush the buffer.
*/
void flush() throws IOException;
/**
* Return true if the value is a parent bean.
*/
@@ -60,7 +66,7 @@ public interface SpiJsonWriter extends JsonWriter {
* Write value using underlying Jaskson object mapper if available.
*/
void writeValueUsingObjectMapper(String name, Object value);
/**
* Write the bean properties.
*/
@@ -4,6 +4,7 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -33,6 +34,12 @@ public class CachedBeanDataFromBean {
}
}
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
if (prop.isElementCollection()) {
data.put(prop.getName(), prop.getCacheDataValue(bean));
}
}
long version = desc.getVersion(bean);
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
@@ -21,14 +21,16 @@ public class CachedBeanDataToBean {
}
// load the non-many properties
BeanProperty[] props = desc.propertiesNonMany();
for (BeanProperty prop : props) {
for (BeanProperty prop : desc.propertiesNonMany()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
}
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
for (BeanPropertyAssocMany<?> aMany : many) {
aMany.createReferenceIfNull(bean);
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
if (prop.isElementCollection()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
} else {
prop.createReferenceIfNull(bean);
}
}
ebi.setLoadedLazy();
@@ -35,6 +35,7 @@ import javax.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -107,6 +108,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private boolean updatedManysOnly;
/**
* Element collection change as part of bean cache.
*/
private Map<String, Object> collectionChanges;
/**
* Many properties that were cascade saved (and hence might need caches updated later).
*/
@@ -938,10 +944,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Combine with the beans postUpdate event notification.
*/
public void postElementCollectionUpdate() {
public boolean postElementCollectionUpdate() {
if (controller != null) {
pendingPostUpdateNotify += 2;
}
if (!dirty) {
setNotifyCache();
}
return notifyCache;
}
private void controllerPost() {
@@ -1079,13 +1089,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
postUpdateNotify();
}
/**
* Return true if only many properties where updated.
*/
public boolean isUpdatedManysOnly() {
return updatedManysOnly;
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
@@ -1311,4 +1314,50 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isInsertedParent() {
return Flags.isInsert(flags);
}
/**
* Add an element collection change to L2 bean cache update.
*/
public void addCollectionChange(String name, Object value) {
if (collectionChanges == null) {
collectionChanges = new LinkedHashMap<>();
}
collectionChanges.put(name, value);
}
/**
* Build the bean update for the L2 cache.
*/
public void addBeanUpdate(CacheChangeSet changeSet) {
if (!updatedManysOnly || collectionChanges != null) {
boolean updateNaturalKey = false;
Map<String, Object> changes = new LinkedHashMap<>();
EntityBean bean = getEntityBean();
boolean[] dirtyProperties = getDirtyProperties();
if (dirtyProperties != null) {
for (int i = 0; i < dirtyProperties.length; i++) {
if (dirtyProperties[i]) {
BeanProperty property = beanDescriptor.propertyByIndex(i);
if (property.isCacheDataInclude()) {
Object val = property.getCacheDataValue(bean);
changes.put(property.getName(), val);
if (property.isNaturalKey()) {
updateNaturalKey = true;
changeSet.addNaturalKeyPut(beanDescriptor, idValue, val);
}
}
}
}
}
if (collectionChanges != null) {
// add element collection update
changes.putAll(collectionChanges);
}
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
}
}
}
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collection;
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.bean.BeanDiffVisitor;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.util.ArrayStack;
import java.io.IOException;
@@ -82,10 +82,10 @@ class BeanChangeJson implements BeanDiffVisitor {
void flush() {
try {
newJson.writeEndObject();
newJson.gen().flush();
newJson.flush();
if (oldJson != null) {
oldJson.writeEndObject();
oldJson.gen().flush();
oldJson.flush();
}
} catch (IOException e) {
throw new PersistenceIOException(e);
@@ -7,8 +7,8 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collection;
@@ -41,6 +41,8 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiUpdatePlan;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.cache.CachedManyIds;
@@ -71,8 +73,6 @@ import io.ebeaninternal.server.query.STreeType;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.util.SortByClause;
@@ -662,6 +662,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return properties;
}
public BeanProperty propertyByIndex(int pos) {
return propertiesIndex[pos];
}
/**
* Initialise the Id properties first.
* <p>
@@ -945,7 +949,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
SpiJsonWriter jsonWriter = createJsonWriter(writer);
jsonWriteForInsert(jsonWriter, request.getEntityBean());
jsonWriter.gen().flush();
jsonWriter.flush();
return beanChange(ChangeType.INSERT, request.getBeanId(), writer.toString(), null);
@@ -959,6 +963,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return ebeanServer.jsonExtended().createJsonWriter(writer);
}
SpiJsonReader createJsonReader(String json) {
return ebeanServer.jsonExtended().createJsonRead(this, json);
}
/**
* Populate the diff for inserts with flattened non-null property values.
*/
@@ -3355,7 +3363,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
throw new IllegalStateException("Unexpected - expect Element override");
}
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
throw new IllegalStateException("Unexpected - expect Element override");
}
@@ -3371,11 +3379,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
jsonHelp.jsonWriteProperties(writeJson, bean);
}
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path);
}
protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException {
protected T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonReadObject(jsonRead, path);
}
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -272,6 +273,11 @@ final class BeanDescriptorCacheHelp<T> {
*/
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
if (many.isElementCollection()) {
// held as part of the bean cache so skip
return false;
}
CachedManyIds entry = manyPropGet(parentId, many.getName());
if (entry == null) {
// not in cache so return unsuccessful
@@ -298,9 +304,29 @@ final class BeanDescriptorCacheHelp<T> {
*/
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
cachePutManyIds(parentId, many.getName(), entry);
if (many.isElementCollection()) {
CachedBeanData data = (CachedBeanData) beanCache.get(parentId);
if (data != null) {
try {
// add as JSON to bean cache
String asJson = many.jsonWriteCollection(details);
Map<String,Object> changes = new HashMap<>();
changes.put(many.getName(), asJson);
CachedBeanData newData = data.update(changes, data.getVersion());
if (beanLog.isDebugEnabled()) {
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, parentId, changes);
}
beanCache.put(parentId, newData);
} catch (IOException e) {
logger.error("Error updating L2 cache", e);
}
}
} else {
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
cachePutManyIds(parentId, many.getName(), entry);
}
}
}
@@ -730,38 +756,17 @@ final class BeanDescriptorCacheHelp<T> {
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
for (BeanPropertyAssocMany<?> many : manyCollections) {
Object details = many.getValue(updateRequest.getEntityBean());
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
changeSet.addManyPut(desc, many.getName(), id, entry);
}
}
}
// check if the bean itself was updated
if (!updateRequest.isUpdatedManysOnly()) {
boolean updateNaturalKey = false;
Map<String, Object> changes = new LinkedHashMap<>();
EntityBean bean = updateRequest.getEntityBean();
boolean[] dirtyProperties = updateRequest.getDirtyProperties();
for (int i = 0; i < dirtyProperties.length; i++) {
if (dirtyProperties[i]) {
BeanProperty property = desc.propertiesIndex[i];
if (property.isCacheDataInclude()) {
Object val = property.getCacheDataValue(bean);
changes.put(property.getName(), val);
if (property.isNaturalKey()) {
updateNaturalKey = true;
changeSet.addNaturalKeyPut(desc, id, val);
}
if (!many.isElementCollection()) {
Object details = many.getValue(updateRequest.getEntityBean());
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
changeSet.addManyPut(desc, many.getName(), id, entry);
}
}
}
changeSet.addBeanUpdate(desc, id, changes, updateNaturalKey, updateRequest.getVersion());
}
updateRequest.addBeanUpdate(changeSet);
}
/**
@@ -3,9 +3,9 @@ package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
@@ -56,12 +56,12 @@ class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
}
@Override
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
return readJsonElement(jsonRead, path);
}
@SuppressWarnings("unchecked")
T readJsonElement(ReadJson jsonRead, String path) throws IOException {
T readJsonElement(SpiJsonReader jsonRead, String path) throws IOException {
return (T)targetDescriptor.jsonRead(jsonRead, path);
}
@@ -3,9 +3,9 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.ScalarType;
import java.io.IOException;
@@ -47,7 +47,7 @@ class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
@@ -5,9 +5,9 @@ import com.fasterxml.jackson.core.JsonToken;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.ScalarType;
import java.io.IOException;
@@ -38,7 +38,7 @@ class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
@@ -3,9 +3,9 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.ScalarType;
import java.io.IOException;
@@ -51,7 +51,7 @@ class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
@@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.EJson;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -70,7 +70,7 @@ public class BeanDescriptorJsonHelp<T> {
}
@SuppressWarnings("unchecked")
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
JsonParser parser = jsonRead.getParser();
//noinspection StatementWithEmptyBody
@@ -116,14 +116,14 @@ public class BeanDescriptorJsonHelp<T> {
return (T) inheritInfo.readType(discValue).desc().jsonReadObject(jsonRead, path);
}
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
protected T jsonReadObject(SpiJsonReader readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBeanForJson();
return jsonReadProperties(readJson, bean, path);
}
@SuppressWarnings("unchecked")
protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException {
protected T jsonReadProperties(SpiJsonReader readJson, EntityBean bean, String path) throws IOException {
if (path != null) {
readJson.pushPath(path);
@@ -7,7 +7,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.ArrayList;
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
/**
* Helper for element collection List.
@@ -7,7 +7,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
public class BeanMapHelpElement<T> extends BeanMapHelp<T> {
@@ -13,6 +13,8 @@ import io.ebean.util.SplitName;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenCreated;
@@ -25,8 +27,6 @@ import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
@@ -1453,7 +1453,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
}
public void jsonRead(ReadJson ctx, EntityBean bean) throws IOException {
public void jsonRead(SpiJsonReader ctx, EntityBean bean) throws IOException {
JsonToken event = ctx.nextToken();
if (JsonToken.VALUE_NULL == event) {
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
@@ -7,22 +9,24 @@ import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
@@ -804,7 +808,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
if (value != null) {
ctx.pushParentBeanMany(bean);
if (help != null) {
help.jsonWrite(ctx, name, value, include != null);
jsonWriteCollection(ctx, name, value, include != null);
} else {
if (isTransient && targetDescriptor == null) {
ctx.writeValueUsingObjectMapper(name, value);
@@ -820,7 +824,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
@Override
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
public void jsonRead(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
jsonHelp.jsonRead(readJson, parentBean);
}
@@ -909,10 +913,69 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
elementDescriptor.jsonWriteElement(ctx, element);
}
/**
* A Many (element collection) property in bean cache to held as JSON.
*/
@Override
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
try {
String asJson = (String) cacheData;
Object collection = jsonReadCollection(asJson);
setValue(bean, collection);
} catch (Exception e) {
logger.error("Error setting value from L2 cache", e);
}
}
@Override
public Object getCacheDataValue(EntityBean bean) {
try {
Object collection = getValue(bean);
if (collection == null) {
return null;
}
return jsonWriteCollection(collection);
} catch (Exception e) {
logger.error("Error building value element collection json for L2 cache", e);
return null;
}
}
/**
* Write the collection to JSON.
*/
public String jsonWriteCollection(Object value) throws IOException {
StringWriter writer = new StringWriter(300);
SpiJsonWriter ctx = descriptor.createJsonWriter(writer);
help.jsonWrite(ctx, null, value, false);
ctx.flush();
return writer.toString();
}
/**
* Read the collection as JSON.
*/
public Object jsonReadCollection(String json) throws IOException {
SpiJsonReader ctx = descriptor.createJsonReader(json);
JsonParser parser = ctx.getParser();
JsonToken event = parser.nextToken();
if (JsonToken.VALUE_NULL == event) {
return null;
}
return jsonReadCollection(ctx, null);
}
/**
* Write the collection to JSON.
*/
public void jsonWriteCollection(SpiJsonWriter ctx, String name, Object value, boolean explicitInclude) throws IOException {
help.jsonWrite(ctx, name, value, explicitInclude);
}
/**
* Read the collection (JSON Array) containing entity beans.
*/
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
if (elementDescriptor != null && manyType.isMap()) {
return elementDescriptor.jsonReadCollection(readJson, parentBean);
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.api.json.SpiJsonReader;
import java.io.IOException;
@@ -35,7 +35,7 @@ class BeanPropertyAssocManyJsonHelp {
/**
* Read the JSON for this property.
*/
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
public void jsonRead(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
if (!this.many.jsonDeserialize) {
return;
@@ -61,7 +61,7 @@ class BeanPropertyAssocManyJsonHelp {
/**
* Read a Transient property using Jackson ObjectMapper.
*/
private void jsonReadTransientUsingObjectMapper(ReadJson readJson, EntityBean parentBean) throws IOException {
private void jsonReadTransientUsingObjectMapper(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
if (jsonTransient == null) {
throw new IllegalStateException("Jackson ObjectMapper is required to read this Transient property " + many.getFullBeanName());
@@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.api.json.SpiJsonReader;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -19,7 +19,7 @@ class BeanPropertyAssocManyJsonTransient {
/**
* Use Jackson ObjectMapper to read the transient 'many' property.
*/
void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, ReadJson readJson, EntityBean parentBean) throws IOException {
void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, SpiJsonReader readJson, EntityBean parentBean) throws IOException {
ObjectMapper mapper = readJson.getObjectMapper();
@@ -9,6 +9,8 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
@@ -19,8 +21,6 @@ import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.STreePropertyAssocOne;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.ScalarType;
import javax.persistence.PersistenceException;
@@ -740,7 +740,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
@Override
public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException {
public void jsonRead(SpiJsonReader readJson, EntityBean bean) throws IOException {
if (jsonDeserialize && targetDescriptor != null) {
T assocBean = targetDescriptor.jsonRead(readJson, name);
setValue(bean, assocBean);
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.deploy;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
import io.ebeaninternal.server.text.json.ReadJson;
import java.io.IOException;
@@ -19,7 +19,7 @@ public class BeanPropertySimpleCollection<T> extends BeanPropertyAssocMany<T> {
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
return elementDescriptor.jsonReadCollection(readJson, parentBean);
}
}
@@ -7,7 +7,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanSet;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashSet;
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
/**
* Helper for element collection List.
@@ -1,5 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import java.util.ArrayList;
import java.util.List;
@@ -26,7 +29,9 @@ class ElementHelpList implements ElementHelp {
@Override
public Object collection() {
return list;
BeanList<Object> beanList = new BeanList<>(list);
beanList.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return beanList;
}
}
}
@@ -1,5 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -26,7 +29,9 @@ class ElementHelpMap implements ElementHelp {
@Override
public Object collection() {
return map;
BeanMap<Object, Object> beanMap = new BeanMap<>(map);
beanMap.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return beanMap;
}
}
}
@@ -1,5 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -26,7 +29,9 @@ class ElementHelpSet implements ElementHelp {
@Override
public Object collection() {
return set;
BeanSet<Object> beanSet = new BeanSet<>(set);
beanSet.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return beanSet;
}
}
}
@@ -218,7 +218,7 @@ class AnnotationAssocManys extends AnnotationParser {
elementDescriptor.setProperties(new String[]{"value"});
} else {
elementDescriptor.setProperties(new String[]{"key", "value"});
String dbKeyColumn = "key";
String dbKeyColumn = "mkey";
MapKeyColumn mapKeyColumn = get(prop, MapKeyColumn.class);
if (mapKeyColumn != null) {
dbKeyColumn = mapKeyColumn.name();
@@ -6,12 +6,18 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* Base for saving entity bean collections and element collections.
*/
abstract class SaveManyBase {
private static final Logger log = LoggerFactory.getLogger(SaveManyBase.class);
final PersistRequestBean<?> request;
final SpiEbeanServer server;
final boolean insertedParent;
@@ -48,4 +54,17 @@ abstract class SaveManyBase {
}
c.modifyReset();
}
void postElementCollectionUpdate() {
if (!insertedParent) {
if (request.postElementCollectionUpdate()) {
try {
String asJson = many.jsonWriteCollection(value);
request.addCollectionChange(many.getName(), asJson);
} catch (IOException e) {
log.error("Error build element collection entry for L2 cache", e);
}
}
}
}
}
@@ -49,8 +49,6 @@ class SaveManyElementCollection extends SaveManyBase {
transaction.depth(-1);
resetModifyState();
if (!insertedParent) {
request.postElementCollectionUpdate();
}
postElementCollectionUpdate();
}
}
@@ -50,8 +50,6 @@ class SaveManyElementCollectionMap extends SaveManyBase {
transaction.depth(-1);
resetModifyState();
if (!insertedParent) {
request.postElementCollectionUpdate();
}
postElementCollectionUpdate();
}
}
@@ -1,10 +1,11 @@
package io.ebeaninternal.server.text.json;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.PersistenceIOException;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.json.JsonBeanReader;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
@@ -18,9 +19,9 @@ public class DJsonBeanReader<T> implements JsonBeanReader<T> {
private final BeanDescriptor<T> desc;
private final ReadJson readJson;
private final SpiJsonReader readJson;
public DJsonBeanReader(BeanDescriptor<T> desc, ReadJson readJson) {
public DJsonBeanReader(BeanDescriptor<T> desc, SpiJsonReader readJson) {
this.desc = desc;
this.readJson = readJson;
}
@@ -16,6 +16,8 @@ import io.ebean.text.json.JsonWriteBeanVisitor;
import io.ebean.text.json.JsonWriteOptions;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.util.ParamTypeHelper;
@@ -119,7 +121,7 @@ public class DJsonContext implements SpiJsonContext {
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
try {
return desc.jsonRead(readJson, null);
} catch (IOException e) {
@@ -131,7 +133,7 @@ public class DJsonContext implements SpiJsonContext {
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
return new DJsonBeanReader<>(desc, readJson);
}
@@ -139,7 +141,7 @@ public class DJsonContext implements SpiJsonContext {
public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = (BeanDescriptor<T>) beanType;
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
return new DJsonBeanReader<>(desc, readJson);
}
@@ -172,7 +174,7 @@ public class DJsonContext implements SpiJsonContext {
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
ReadJson readJson = new ReadJson(desc, src, options, determineObjectMapper(options));
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options));
try {
JsonToken currentToken = src.getCurrentToken();
@@ -304,7 +306,7 @@ public class DJsonContext implements SpiJsonContext {
private String toJsonString(Object value, JsonWriteOptions options) throws JsonIOException {
StringWriter writer = new StringWriter(500);
try (JsonGenerator gen = createGenerator(writer)){
try (JsonGenerator gen = createGenerator(writer)) {
toJsonInternal(value, gen, options);
} catch (IOException e) {
throw new JsonIOException(e);
@@ -342,6 +344,14 @@ public class DJsonContext implements SpiJsonContext {
}
}
@Override
public SpiJsonReader createJsonRead(BeanType<?> beanType, String json) {
BeanDescriptor<?> desc = (BeanDescriptor<?>) beanType;
JsonParser parser = createParser(new StringReader(json));
return new ReadJson(desc, parser, null, defaultObjectMapper);
}
@Override
public SpiJsonWriter createJsonWriter(Writer writer) {
JsonGenerator generator = createGenerator(writer);
@@ -1,17 +1,18 @@
package io.ebeaninternal.server.text.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.json.JsonReadBeanVisitor;
import io.ebean.text.json.JsonReadOptions;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.loadcontext.DLoadContext;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Map;
@@ -19,7 +20,7 @@ import java.util.Map;
/**
* Context for JSON read processing.
*/
public class ReadJson {
public class ReadJson implements SpiJsonReader {
private final BeanDescriptor<?> rootDesc;
@@ -100,6 +101,7 @@ public class ReadJson {
/**
* Return the persistence context being used if any.
*/
@Override
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -107,13 +109,15 @@ public class ReadJson {
/**
* Return a new instance of ReadJson using the existing context but with a new JsonParser.
*/
public ReadJson forJson(JsonParser moreJson, boolean resetContext) {
@Override
public SpiJsonReader forJson(JsonParser moreJson, boolean resetContext) {
return new ReadJson(moreJson, this, resetContext);
}
/**
* Add the bean to the persistence context.
*/
@Override
public <T> void persistenceContextPut(Object beanId, T currentBean) {
persistenceContextPutIfAbsent(beanId, (EntityBean) currentBean, rootDesc);
@@ -123,6 +127,7 @@ public class ReadJson {
* Put the bean into the persistence context. If there is already a matching bean in the
* persistence context then return that instance else return null.
*/
@Override
public Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor<?> beanDesc) {
if (persistenceContext == null) {
@@ -153,6 +158,7 @@ public class ReadJson {
/**
* Return the objectMapper used for this request.
*/
@Override
public ObjectMapper getObjectMapper() {
if (objectMapper == null) {
throw new IllegalStateException(
@@ -165,6 +171,7 @@ public class ReadJson {
/**
* Return the JsonParser.
*/
@Override
public JsonParser getParser() {
return parser;
}
@@ -172,6 +179,7 @@ public class ReadJson {
/**
* Return the next JsonToken from the underlying parser.
*/
@Override
public JsonToken nextToken() throws IOException {
return parser.nextToken();
}
@@ -179,6 +187,7 @@ public class ReadJson {
/**
* Push the path onto the stack (traversing a 1-M or M-1 etc)
*/
@Override
public void pushPath(String path) {
if (pathStack != null) {
pathStack.pushPathKey(path);
@@ -188,6 +197,7 @@ public class ReadJson {
/**
* Pop the path stack.
*/
@Override
public void popPath() {
if (pathStack != null) {
pathStack.pop();
@@ -198,6 +208,7 @@ public class ReadJson {
* If there is a JsonReadBeanVisitor registered to the current path then
* call it's visit method with the bean and unmappedProperties.
*/
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void beanVisitor(Object bean, Map<String, Object> unmappedProperties) {
if (visitorMap != null) {
@@ -213,6 +224,7 @@ public class ReadJson {
* <p/>
* Typically this is used to read Transient properties where the type is unknown to Ebean.
*/
@Override
public Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException {
return getObjectMapper().readValue(parser, propertyType);
}
@@ -9,6 +9,7 @@ import io.ebean.text.json.EJson;
import io.ebean.text.json.JsonIOException;
import io.ebean.text.json.JsonWriteBeanVisitor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.util.ArrayStack;
@@ -89,6 +90,11 @@ public class WriteJson implements SpiJsonWriter {
return generator;
}
@Override
public void flush() throws IOException {
generator.flush();
}
@Override
public void writeStartObject(String key) {
try {
@@ -373,7 +379,9 @@ public class WriteJson implements SpiJsonWriter {
public void beginAssocMany(String key) {
try {
pathStack.pushPathKey(key);
generator.writeFieldName(key);
if (key != null) {
generator.writeFieldName(key);
}
generator.writeStartArray();
} catch (IOException e) {
throw new JsonIOException(e);