#115 - Mapping - Add support for @ElementCollection enhancement

Add embedded bean support
This commit is contained in:
Rob Bygrave
2018-03-28 13:22:46 +13:00
parent cca837bcfa
commit 57870aaa05
30 changed files with 1247 additions and 243 deletions
@@ -72,6 +72,11 @@ public interface BeanCollection<E> extends Serializable {
*/
boolean isSkipSave();
/**
* Return true if the collection holds modifications.
*/
boolean holdsModifications();
/**
* Return the bean that owns this collection.
*/
@@ -207,7 +207,8 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
/**
* Return true if there are underlying additions or removals.
*/
boolean holdsModifications() {
@Override
public boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
@@ -11,6 +11,16 @@ import java.util.Map;
*/
public class BeanCollectionUtil {
/**
* Return true if this is a bean collection and not considered dirty.
*/
public static boolean isModified(Object collection) {
if ((collection instanceof BeanCollection<?>)) {
return ((BeanCollection<?>) collection).holdsModifications();
}
return true;
}
/**
* Return the details of the collection or map taking care to avoid
* unnecessary fetching of the data.
@@ -235,7 +235,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* The type of bean this describes.
*/
private final Class<T> beanType;
final Class<T> beanType;
protected final Class<?> rootBeanType;
@@ -428,8 +428,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.name = InternString.intern(deploy.getName());
this.baseTableAlias = "t0";
this.fullName = InternString.intern(deploy.getFullName());
this.locationById = ProfileLocation.createAt(fullName+".byId");
this.locationAll = ProfileLocation.createAt(fullName+".all");
this.locationById = ProfileLocation.createAt(fullName + ".byId");
this.locationAll = ProfileLocation.createAt(fullName + ".all");
this.profileBeanId = deploy.getProfileId();
this.beanType = deploy.getBeanType();
this.rootBeanType = PersistenceContextUtil.root(beanType);
@@ -776,7 +776,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
for (BeanProperty prop : propertiesNonTransient) {
if (prop.isUnique()) {
propertiesUnique.add(new BeanProperty[] { prop });
propertiesUnique.add(new BeanProperty[]{prop});
}
}
// convert unique columns to properties
@@ -860,6 +860,16 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
/**
* Bind all the property values to the SqlUpdate.
*/
public void bindElementValue(SqlUpdate insert, Object value) {
EntityBean bean = (EntityBean) value;
for (BeanProperty property : propertiesBaseScalar) {
insert.setNextParameter(property.getValue(bean));
}
}
/**
* Return the ReadAuditLogger for logging read audit events.
*/
@@ -1420,7 +1430,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
@SuppressWarnings("unchecked")
public void cacheBeanPutAll(Collection<?> beans) {
if (!beans.isEmpty()) {
cacheHelp.beanPutAll((Collection<EntityBean>)beans);
cacheHelp.beanPutAll((Collection<EntityBean>) beans);
}
}
@@ -1879,12 +1889,19 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
/**
* Creates a new entitybean without invoking {@link BeanPostConstructListener#postCreate(Object)}
* Creates a new entity bean without invoking {@link BeanPostConstructListener#postCreate(Object)}
*/
public EntityBean createEntityBean() {
return createEntityBean(false);
}
/**
* Create an entity bean for JSON marshalling (which differs for the element collection case).
*/
public EntityBean createEntityBeanForJson() {
return createEntityBean();
}
/**
* Create a reference bean based on the id.
*/
@@ -2471,7 +2488,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return a property that is part of the SQL tree.
*
* <p>
* The property can be a dynamic formula or a well known bean property.
*/
@Override
@@ -1,34 +1,35 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.PersistenceIOException;
import io.ebean.bean.EntityBean;
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;
import java.util.Arrays;
/**
* Bean descriptor used with ElementCollection (where we don't have a mapped type/class).
* <p>
* This is somewhat a BeanDescriptor created 'on the fly' for a specific element collection property
* with a unidirectional property and mapping etc specific to the property (and not the type if embedded).
*/
class BeanDescriptorElement<T> extends BeanDescriptor<T> {
abstract class BeanDescriptorElement<T> extends BeanDescriptor<T> {
private final ScalarType<Object> scalarType;
private final ElementHelp elementHelp;
final ElementHelp elementHelp;
BeanDescriptorElement(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy);
this.elementHelp = elementHelp;
}
BeanProperty[] props = propertiesNonTransient();
if (props.length == 1) {
this.scalarType = props[0].getScalarType();
} else {
this.scalarType = null;
/**
* Find and return the first base scalar type (and we expect only 1).
*/
ScalarType<Object> firstBaseScalarType() {
BeanProperty[] props = propertiesBaseScalar();
if (props.length != 1) {
throw new IllegalStateException("Expecting 1 property for element scalar but got " + Arrays.toString(props));
}
return props[0].getScalarType();
}
@Override
@@ -36,35 +37,13 @@ class BeanDescriptorElement<T> extends BeanDescriptor<T> {
return true;
}
/**
* Our entity beans used are somewhat fake ones (ElementEntityBean) such that we hold the unidirectional property
* value (foreign key) and the actual element collection value (scalar or embedded plus map key).
*/
@Override
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
return new ElementEntityBean(properties);
}
@Override
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
try {
scalarType.jsonWrite(ctx.gen(), element);
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
break;
}
Object element = scalarType.jsonRead(parser);
add.addElement(element);
} while (true);
return add.collection();
}
}
@@ -0,0 +1,79 @@
package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
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;
/**
* Bean descriptor used with element collection of list/set of embeddable.
*/
class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
private final BeanPropertyAssocOne embeddedProperty;
private final EntityBean prototype;
private BeanDescriptor targetDescriptor;
BeanDescriptorElementEmbedded(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
try {
this.prototype = (EntityBean) beanType.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Unable to create entity bean prototype for "+beanType);
}
BeanPropertyAssocOne<?>[] embedded = propertiesEmbedded();
if (embedded.length == 1) {
embeddedProperty = embedded[0];
} else {
embeddedProperty = null;
}
}
@Override
public void initialiseOther(BeanDescriptorInitContext initContext) {
super.initialiseOther(initContext);
this.targetDescriptor = embeddedProperty.getTargetDescriptor();
}
@Override
public EntityBean createEntityBeanForJson() {
return (EntityBean)prototype._ebean_newInstance();
}
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
@Override
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
writeJsonElement(ctx, element);
}
@Override
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
return readJsonElement(jsonRead, path);
}
@SuppressWarnings("unchecked")
T readJsonElement(ReadJson jsonRead, String path) throws IOException {
return (T)targetDescriptor.jsonRead(jsonRead, path);
}
void writeJsonElement(SpiJsonWriter ctx, Object element) {
try {
if (element == null) {
ctx.writeNull();
} else {
targetDescriptor.jsonWrite(ctx, (EntityBean)element);
}
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
}
@@ -0,0 +1,84 @@
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.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;
import java.util.Map;
/**
* Descriptor for element collection using Map with the value holding an embedded bean.
* <p>
* The expected limitation is that the key is a scalar type.
*/
class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<T> {
private final ScalarType scalarTypeKey;
private final boolean stringKey;
BeanDescriptorElementEmbeddedMap(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
this.scalarTypeKey = firstBaseScalarType();
this.stringKey = String.class.equals(scalarTypeKey.getType());
}
@Override
@SuppressWarnings("unchecked")
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
ctx.writeStartObject();
if (stringKey) {
Object key = entry.getKey();
String keyName = (key == null) ? "null" : key.toString();
ctx.writeFieldName(keyName);
writeJsonElement(ctx, entry.getValue());
} else {
ctx.writeFieldName("key");
scalarTypeKey.jsonWrite(ctx.gen(), entry.getKey());
ctx.writeFieldName("value");
writeJsonElement(ctx, entry.getValue());
}
ctx.writeEndObject();
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (token != JsonToken.START_OBJECT) {
break;
}
if (stringKey) {
String key = parser.nextFieldName();
parser.nextToken();
Object val = readJsonElement(readJson, null);
add.addKeyValue(key, val);
} else {
parser.nextFieldName();
Object key = scalarTypeKey.jsonRead(parser);
parser.nextFieldName();
Object val = readJsonElement(readJson, null);
add.addKeyValue(key, val);
}
token = parser.nextToken();
if (token != JsonToken.END_OBJECT) {
break;
}
} while (true);
return add.collection();
}
}
@@ -1,97 +0,0 @@
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.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;
import java.util.Map;
/**
* Bean descriptor used with ElementCollection (where we don't have a mapped type/class).
*/
class BeanDescriptorElementMap<T> extends BeanDescriptor<T> {
private final ScalarType[] scalarTypes;
private final ElementHelp elementHelp;
private final boolean stringKey;
BeanDescriptorElementMap(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy);
this.elementHelp = elementHelp;
BeanProperty[] props = propertiesNonTransient();
this.scalarTypes = new ScalarType[props.length];
for (int i = 0; i < props.length; i++) {
scalarTypes[i] = props[i].getScalarType();
}
this.stringKey = String.class.equals(scalarTypes[0].getType());
}
@Override
public boolean isElementType() {
return true;
}
@Override
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
return new ElementEntityBean(properties);
}
@Override
@SuppressWarnings("unchecked")
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
ctx.writeStartObject();
if (stringKey) {
Object key = entry.getKey();
String keyName = (key == null) ? "null" : key.toString();
ctx.writeFieldName(keyName);
scalarTypes[1].jsonWrite(ctx.gen(), entry.getValue());
} else {
ctx.writeFieldName("key");
scalarTypes[0].jsonWrite(ctx.gen(), entry.getKey());
ctx.writeFieldName("value");
scalarTypes[1].jsonWrite(ctx.gen(), entry.getValue());
}
ctx.writeEndObject();
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (token != JsonToken.START_OBJECT) {
break;
}
if (stringKey) {
String key = parser.nextFieldName();
parser.nextToken();
Object val = scalarTypes[0].jsonRead(parser);
add.addKeyValue(key, val);
} else {
parser.nextFieldName();
Object key= scalarTypes[0].jsonRead(parser);
parser.nextFieldName();
Object val = scalarTypes[0].jsonRead(parser);
add.addKeyValue(key, val);
}
token = parser.nextToken();
if (token != JsonToken.END_OBJECT) {
break;
}
} while (true);
return add.collection();
}
}
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
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;
/**
* Bean descriptor used with element collection mapped to a list or set of scalar values.
*/
class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
private final ScalarType<Object> scalarType;
BeanDescriptorElementScalar(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
this.scalarType = firstBaseScalarType();
}
public void bindElementValue(SqlUpdate insert, Object value) {
insert.setNextParameter(value);
}
@Override
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
try {
scalarType.jsonWrite(ctx.gen(), element);
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
break;
}
add.addElement(scalarType.jsonRead(parser));
} while (true);
return add.collection();
}
}
@@ -0,0 +1,88 @@
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.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;
import java.util.Arrays;
import java.util.Map;
/**
* Bean descriptor used with element collection mapped to Map where key and value are scalar types.
*/
class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
private final ScalarType scalarTypeKey;
private final ScalarType scalarTypeVal;
private final boolean stringKey;
BeanDescriptorElementScalarMap(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
BeanProperty[] props = propertiesNonTransient();
if (props.length != 2) {
throw new IllegalStateException("Expecting 2 properties for key and value but got " + Arrays.toString(props));
}
this.scalarTypeKey = props[0].getScalarType();
this.scalarTypeVal = props[1].getScalarType();
this.stringKey = String.class.equals(scalarTypeKey.getType());
}
@Override
@SuppressWarnings("unchecked")
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
ctx.writeStartObject();
if (stringKey) {
Object key = entry.getKey();
String keyName = (key == null) ? "null" : key.toString();
ctx.writeFieldName(keyName);
scalarTypeVal.jsonWrite(ctx.gen(), entry.getValue());
} else {
ctx.writeFieldName("key");
scalarTypeKey.jsonWrite(ctx.gen(), entry.getKey());
ctx.writeFieldName("value");
scalarTypeVal.jsonWrite(ctx.gen(), entry.getValue());
}
ctx.writeEndObject();
}
@Override
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (token != JsonToken.START_OBJECT) {
break;
}
if (stringKey) {
String key = parser.nextFieldName();
parser.nextToken();
Object val = scalarTypeVal.jsonRead(parser);
add.addKeyValue(key, val);
} else {
parser.nextFieldName();
Object key = scalarTypeKey.jsonRead(parser);
parser.nextFieldName();
Object val = scalarTypeVal.jsonRead(parser);
add.addKeyValue(key, val);
}
token = parser.nextToken();
if (token != JsonToken.END_OBJECT) {
break;
}
} while (true);
return add.collection();
}
}
@@ -118,7 +118,7 @@ public class BeanDescriptorJsonHelp<T> {
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBean();
EntityBean bean = desc.createEntityBeanForJson();
return jsonReadProperties(readJson, bean, path);
}
@@ -637,7 +637,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Return a BeanTable for an ElementCollection.
*/
public BeanTable getCollectionBeanTable(String fullTableName, Class<?> targetType) {
public BeanTable createCollectionBeanTable(String fullTableName, Class<?> targetType) {
return new BeanTable(this, fullTableName, targetType);
}
@@ -1637,13 +1637,21 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Create a BeanDescriptor for an ElementCollection target.
*/
public <A> BeanDescriptor<A> createElementDescriptor(DeployBeanDescriptor<A> elementDescriptor, ManyType manyType) {
public <A> BeanDescriptor<A> createElementDescriptor(DeployBeanDescriptor<A> elementDescriptor, ManyType manyType, boolean scalar) {
ElementHelp elementHelp = elementHelper(manyType);
if (manyType.isMap()) {
return new BeanDescriptorElementMap<>(this, elementDescriptor, elementHelp);
if (scalar) {
return new BeanDescriptorElementScalarMap<>(this, elementDescriptor, elementHelp);
} else {
return new BeanDescriptorElementEmbeddedMap<>(this, elementDescriptor, elementHelp);
}
}
if (scalar) {
return new BeanDescriptorElementScalar<>(this, elementDescriptor, elementHelp);
} else {
return new BeanDescriptorElementEmbedded<>(this, elementDescriptor, elementHelp);
}
return new BeanDescriptorElement<>(this, elementDescriptor, elementHelp);
}
private ElementHelp elementHelper(ManyType manyType) {
@@ -70,6 +70,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
private final boolean elementCollection;
/**
* Descriptor for the 'target' when the property maps to an element collection.
*/
BeanDescriptor<T> elementDescriptor;
/**
* Order by used when fetch joining the associated many.
*/
@@ -116,6 +121,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
this.hasOrderColumn = deploy.hasOrderColumn();
this.manyToMany = deploy.isManyToMany();
this.elementCollection = deploy.isElementCollection();
this.elementDescriptor = deploy.getElementDescriptor();
this.manyType = deploy.getManyType();
this.mapKey = deploy.getMapKey();
this.fetchOrderBy = deploy.getFetchOrderBy();
@@ -136,6 +142,19 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
initialiseAssocMany();
if (elementCollection) {
// initialise all non-id properties (we don't have an Id property)
elementDescriptor.initialiseOther(initContext);
}
}
@Override
void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) {
if (elementCollection) {
targetDescriptor = elementDescriptor;
} else {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
}
}
private void initialiseAssocMany() {
@@ -170,7 +189,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
String targetTable() {
return targetDescriptor.getBaseTable();
return beanTable.getBaseTable();
}
/**
@@ -883,18 +902,22 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
// Writing as json array rather than object ...
targetDescriptor.jsonWrite(ctx, (EntityBean) entry.getValue());
elementDescriptor.jsonWriteMapEntry(ctx, entry);
}
public void jsonWriteElementValue(SpiJsonWriter ctx, Object element) {
throw new IllegalStateException("Unexpected - expect Element override");
elementDescriptor.jsonWriteElement(ctx, element);
}
/**
* Read the collection (JSON Array) containing entity beans.
*/
public Object jsonReadCollection(ReadJson readJson, EntityBean parentBean) throws IOException {
if (elementDescriptor != null && manyType.isMap()) {
return elementDescriptor.jsonReadCollection(readJson, parentBean);
}
BeanCollection<?> collection = createEmpty(parentBean);
BeanCollectionAdd add = getBeanCollectionAdd(collection, null);
do {
@@ -913,4 +936,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return collection;
}
/**
* Bind all the property values to the SqlUpdate.
*/
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
}
@@ -1,47 +1,21 @@
package io.ebeaninternal.server.deploy;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Map;
public class BeanPropertySimpleCollection<T> extends BeanPropertyAssocMany<T> {
private BeanDescriptor<T> elementDescriptor;
public BeanPropertySimpleCollection(BeanDescriptor<?> descriptor, DeployBeanPropertySimpleCollection<T> deploy) {
super(descriptor, deploy);
this.elementDescriptor = deploy.getElementDescriptor();
}
@Override
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
if (isElementCollection()) {
// initialise all non-id properties (we don't have an Id property)
elementDescriptor.initialiseOther(initContext);
}
}
void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) {
if (isElementCollection()) {
targetDescriptor = elementDescriptor;
} else {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
}
}
@Override
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
elementDescriptor.jsonWriteMapEntry(ctx, entry);
}
@Override
public void jsonWriteElementValue(SpiJsonWriter ctx, Object element) {
elementDescriptor.jsonWriteElement(ctx, element);
public void bindElementValue(SqlUpdate insert, Object value) {
insert.setNextParameter(value);
}
@Override
@@ -4,6 +4,9 @@ import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.ManyType;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.type.TypeReflectHelper;
import java.lang.reflect.Type;
/**
* Property mapped to a List Set or Map.
@@ -179,6 +182,14 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
return fetchOrderBy;
}
/**
* Return the type of the map key (valid only when this property is a Map).
*/
public Class<?> getMapKeyType() {
Type genericType = getField().getGenericType();
return TypeReflectHelper.getMapKeyType(genericType);
}
/**
* Return the default mapKey when returning a Map.
*/
@@ -15,6 +15,7 @@ import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployOrderColumn;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
@@ -204,7 +205,7 @@ class AnnotationAssocManys extends AnnotationParser {
}
}
BeanTable beanTable = factory.getCollectionBeanTable(fullTableName, prop.getTargetType());
BeanTable beanTable = factory.createCollectionBeanTable(fullTableName, prop.getTargetType());
prop.setBeanTable(beanTable);
Class<?> elementType = prop.getTargetType();
@@ -212,8 +213,6 @@ class AnnotationAssocManys extends AnnotationParser {
DeployBeanDescriptor<?> elementDescriptor = factory.createDeployDescriptor(elementType);
elementDescriptor.setBaseTable(new TableName(fullTableName), readConfig.getAsOfViewSuffix(), readConfig.getVersionsBetweenSuffix());
ScalarType<?> scalarType = util.getTypeManager().getScalarType(elementType);
int sortOrder = 0;
if (!prop.getManyType().isMap()) {
elementDescriptor.setProperties(new String[]{"value"});
@@ -225,27 +224,46 @@ class AnnotationAssocManys extends AnnotationParser {
dbKeyColumn = mapKeyColumn.name();
}
DeployBeanProperty keyProp = new DeployBeanProperty(elementDescriptor, elementType, scalarType, null);
ScalarType<?> keyScalarType = util.getTypeManager().getScalarType(prop.getMapKeyType());
DeployBeanProperty keyProp = new DeployBeanProperty(elementDescriptor, elementType, keyScalarType, null);
setElementProperty(keyProp, "key", dbKeyColumn, sortOrder++);
elementDescriptor.addBeanProperty(keyProp);
if (mapKeyColumn != null) {
keyProp.setDbLength(mapKeyColumn.length());
keyProp.setDbScale(mapKeyColumn.scale());
keyProp.setUnique(mapKeyColumn.unique());
}
}
DeployBeanProperty valueProp = new DeployBeanProperty(elementDescriptor, elementType, scalarType, null);
setElementProperty(valueProp, "value", prop.getDbColumn(), sortOrder++);
if (column != null) {
valueProp.setDbLength(column.length());
valueProp.setDbScale(column.scale());
ScalarType<?> valueScalarType = util.getTypeManager().getScalarType(elementType);
boolean scalar = true;
if (valueScalarType == null) {
// embedded value type
scalar = false;
DeployBeanPropertyAssocOne valueProp = new DeployBeanPropertyAssocOne<>(elementDescriptor, elementType);
valueProp.setName("value");
valueProp.setEmbedded();
valueProp.setElementProperty();
valueProp.setSortOrder(sortOrder++);
elementDescriptor.addBeanProperty(valueProp);
} else {
// scalar value type
DeployBeanProperty valueProp = new DeployBeanProperty(elementDescriptor, elementType, valueScalarType, null);
setElementProperty(valueProp, "value", prop.getDbColumn(), sortOrder++);
if (column != null) {
valueProp.setDbLength(column.length());
valueProp.setDbScale(column.scale());
}
elementDescriptor.addBeanProperty(valueProp);
}
elementDescriptor.addBeanProperty(valueProp);
elementDescriptor.setName(prop.getFullBeanName());
factory.createUnidirectional(elementDescriptor, prop.getOwningType(), beanTable, prop.getTableJoin());
prop.setElementDescriptor(factory.createElementDescriptor(elementDescriptor, prop.getManyType()));
prop.setElementDescriptor(factory.createElementDescriptor(elementDescriptor, prop.getManyType(), scalar));
}
private void setElementProperty(DeployBeanProperty elementProp, String name, String dbColumn, int sortOrder) {
@@ -307,7 +325,7 @@ class AnnotationAssocManys extends AnnotationParser {
* Return the full table name
*/
private String getFullTableName(CollectionTable collectionTable) {
if (collectionTable == null) {
if (collectionTable == null || collectionTable.name().isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
@@ -906,10 +906,10 @@ public final class DefaultPersister implements Persister {
return new SaveManyBeans(insertedParent, many, parentBean, request, this);
} else if (many.getManyType().isMap()) {
return new SaveManySimpleMap(insertedParent, many, parentBean, request);
return new SaveManyElementCollectionMap(insertedParent, many, parentBean, request);
} else {
return new SaveManySimpleCollection(insertedParent, many, parentBean, request);
return new SaveManyElementCollection(insertedParent, many, parentBean, request);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.persist;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
@@ -34,4 +35,17 @@ abstract class SaveManyBase {
*/
abstract void save();
void resetModifyState() {
if (value instanceof BeanCollection<?>) {
modifyListenReset((BeanCollection<?>) value);
}
}
void modifyListenReset(BeanCollection<?> c) {
if (insertedParent) {
// after insert set the modify listening mode for private owned etc
c.setModifyListening(many.getModifyListenMode());
}
c.modifyReset();
}
}
@@ -89,20 +89,6 @@ class SaveManyBeans extends SaveManyBase {
return BeanCollection.ModifyListenMode.REMOVALS == many.getModifyListenMode();
}
private void modifyListenReset(BeanCollection<?> c) {
if (insertedParent) {
// after insert set the modify listening mode for private owned etc
c.setModifyListening(many.getModifyListenMode());
}
c.modifyReset();
}
private void resetModifyState() {
if (value instanceof BeanCollection<?>) {
modifyListenReset((BeanCollection<?>) value);
}
}
/**
* Save the details from a OneToMany collection.
*/
@@ -12,9 +12,9 @@ import java.util.Collection;
/**
* Save details for a simple scalar element collection.
*/
class SaveManySimpleCollection extends SaveManyBase {
class SaveManyElementCollection extends SaveManyBase {
SaveManySimpleCollection(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
SaveManyElementCollection(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
super(insertedParent, many, parentBean, request);
}
@@ -22,16 +22,18 @@ class SaveManySimpleCollection extends SaveManyBase {
void save() {
Collection<?> collection = BeanCollectionUtil.getActualEntries(value);
if (collection == null) {
if (collection == null || !BeanCollectionUtil.isModified(value)) {
return;
}
Object parentId = request.getBeanId();
SqlUpdate sqlDelete = many.deleteByParentId(parentId, null);
SpiEbeanServer server = request.getServer();
server.execute(sqlDelete, transaction);
if (!insertedParent) {
SqlUpdate sqlDelete = many.deleteByParentId(parentId, null);
server.execute(sqlDelete, transaction);
}
transaction.depth(+1);
@@ -40,11 +42,12 @@ class SaveManySimpleCollection extends SaveManyBase {
for (Object value : collection) {
sqlInsert.setParameter(1, parentId);
sqlInsert.setParameter(2, value);
sqlInsert.setNextParameter(parentId);
many.bindElementValue(sqlInsert, value);
server.execute(sqlInsert, transaction);
}
transaction.depth(-1);
resetModifyState();
}
}
@@ -13,9 +13,9 @@ import java.util.Set;
/**
* Save details for a simple scalar map element collection.
*/
class SaveManySimpleMap extends SaveManyBase {
class SaveManyElementCollectionMap extends SaveManyBase {
SaveManySimpleMap(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
SaveManyElementCollectionMap(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
super(insertedParent, many, parentBean, request);
}
@@ -23,17 +23,19 @@ class SaveManySimpleMap extends SaveManyBase {
@Override
void save() {
Set<Map.Entry<?, ?>> entries = (Set<Map.Entry<?, ?>>)BeanCollectionUtil.getActualEntries(value);
if (entries == null) {
Set<Map.Entry<?, ?>> entries = (Set<Map.Entry<?, ?>>) BeanCollectionUtil.getActualEntries(value);
if (entries == null || !BeanCollectionUtil.isModified(value)) {
return;
}
Object parentId = request.getBeanId();
SqlUpdate sqlDelete = many.deleteByParentId(parentId, null);
SpiEbeanServer server = request.getServer();
server.execute(sqlDelete, transaction);
if (!insertedParent) {
SqlUpdate sqlDelete = many.deleteByParentId(parentId, null);
server.execute(sqlDelete, transaction);
}
transaction.depth(+1);
@@ -41,12 +43,13 @@ class SaveManySimpleMap extends SaveManyBase {
SqlUpdate sqlInsert = server.createSqlUpdate(insert);
for (Map.Entry<?, ?> entry : entries) {
sqlInsert.setParameter(1, parentId);
sqlInsert.setParameter(2, entry.getKey());
sqlInsert.setParameter(3, entry.getValue());
sqlInsert.setNextParameter(parentId);
sqlInsert.setNextParameter(entry.getKey());
many.bindElementValue(sqlInsert, entry.getValue());
server.execute(sqlInsert, transaction);
}
transaction.depth(-1);
resetModifyState();
}
}
@@ -30,6 +30,13 @@ public class TypeReflectHelper {
}
}
/**
* Return the type of the map key.
*/
public static Class<?> getMapKeyType(Type genericType) {
return getClass(getValueType(genericType));
}
/**
* Return the value type of a collection type (list, set, map values).
*/