Skip to content

Commit

Permalink
NEW: MergeBeans function instead of updating existing bean from json (#…
Browse files Browse the repository at this point in the history
…88)

Co-authored-by: Roland Praml <[email protected]>

FIX: Merge of discriminator

FIX: Update reference (#89)
  • Loading branch information
nPraml authored and rPraml committed Aug 9, 2023
1 parent a7fe860 commit f398820
Show file tree
Hide file tree
Showing 32 changed files with 698 additions and 215 deletions.
126 changes: 126 additions & 0 deletions ebean-api/src/main/java/io/ebean/BeanMergeOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package io.ebean;

import io.ebean.bean.PersistenceContext;
import io.ebean.plugin.Property;

/**
* Merge options, when merging two beans. You can fine tune, how the merge should happen.
* For example you can exclude some special properties or write your custom merge handler.
*
* @author Roland Praml, FOCONIS AG
*/
public class BeanMergeOptions {

/**
* Interface to write your own merge handler.
*
* @param <T>
*/
@FunctionalInterface
public interface MergeHandler<T> {
/**
* The new <code>bean</code> and the <code>existing</code> is passed. Together with <code>property</code>
* and <code>path</code>, you can decide, if you want to continue with merge or not.
*/
boolean mergeBeans(T bean, T existing, Property property, String path);

}

private PersistenceContext persistenceContext;

private MergeHandler<?> mergeHandler;

private boolean mergeId = true;

private boolean mergeVersion = false;

private boolean clearCollections = true;

private boolean addExistingToPersistenceContext = true;

/**
* Return the persistence context, that is used during merge.
* If no one is specified, the persistence context of the bean will be used
*/
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}

/**
* Sets the persistence context, that is used during merge.
* * If no one is specified, the persistence context of the bean will be used
*/
public void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}

/**
* Returns the merge handler, if you want to do special handling for some properties.
*/
public MergeHandler<?> getMergeHandler() {
return mergeHandler;
}

/**
* Sets the merge handler, if you want to do special handling for some properties.
*/
public <T> void setMergeHandler(MergeHandler<T> mergeHandler) {
this.mergeHandler = mergeHandler;
}

/**
* Returns if we should merge the ID property (default=true).
*/
public boolean isMergeId() {
return mergeId;
}

/**
* Should we merge the ID property (default=true).
*/
public void setMergeId(boolean mergeId) {
this.mergeId = mergeId;
}

/**
* Returns if we should merge the version property (default=false).
*/
public boolean isMergeVersion() {
return mergeVersion;
}

/**
* Should we merge the version property (default=false).
*/
public void setMergeVersion(boolean mergeVersion) {
this.mergeVersion = mergeVersion;
}

/**
* Returns if we should clear/replace beanCollections (default=true).
*/
public boolean isClearCollections() {
return clearCollections;
}

/**
* Should we clear/replace beanCollections (default=true).
*/
public void setClearCollections(boolean clearCollections) {
this.clearCollections = clearCollections;
}

/**
* Returns if we should add existing beans to the persistenceContext (default=true).
*/
public boolean isAddExistingToPersistenceContext() {
return addExistingToPersistenceContext;
}

/**
* Should we add existing beans to the persistenceContext (default=true).
*/
public void setAddExistingToPersistenceContext(boolean addExistingToPersistenceContext) {
this.addExistingToPersistenceContext = addExistingToPersistenceContext;
}
}
5 changes: 1 addition & 4 deletions ebean-api/src/main/java/io/ebean/DB.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@

import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.Callable;

/**
Expand Down
6 changes: 6 additions & 0 deletions ebean-api/src/main/java/io/ebean/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,12 @@ public interface Database {
*/
void merge(Object bean, MergeOptions options, Transaction transaction);

/**
* Merges two beans (without saving them). All modified properties from <code>bean</code> are copied to <code>existing</code>.
* Returns <code>existing</code> bean. If <code>null</code> is passed, a new instance of bean is retuned.
*/
<T> T mergeBeans(T bean, T existing, BeanMergeOptions options);

/**
* Insert the bean.
* <p>
Expand Down
10 changes: 9 additions & 1 deletion ebean-api/src/main/java/io/ebean/bean/BeanCollection.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,11 @@ enum ModifyListenMode {
* <p>
* For maps this returns the entrySet as we need the keys of the map.
*/
Collection<?> actualEntries();
Collection<?> actualEntries(boolean load);

default Collection<?> actualEntries() {
return actualEntries(false);
}
/**
* Returns entries, that were lazily added at the end of the list. Might be null.
*/
Expand Down Expand Up @@ -247,4 +250,9 @@ default Collection<E> getLazyAddedEntries(boolean reset) {
* Return a shallow copy of this collection that is modifiable.
*/
BeanCollection<E> shallowCopy();

/**
* Clears the underlying collection.
*/
void clear();
}
5 changes: 4 additions & 1 deletion ebean-api/src/main/java/io/ebean/common/BeanList.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ public Collection<E> actualDetails() {
}

@Override
public Collection<?> actualEntries() {
public Collection<?> actualEntries(boolean load) {
if (load) {
init();
}
return list;
}

Expand Down
5 changes: 4 additions & 1 deletion ebean-api/src/main/java/io/ebean/common/BeanMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,10 @@ public Collection<E> actualDetails() {
* Returns the map entrySet.
*/
@Override
public Collection<?> actualEntries() {
public Collection<?> actualEntries(boolean load) {
if (load) {
init();
}
return map.entrySet();
}

Expand Down
5 changes: 4 additions & 1 deletion ebean-api/src/main/java/io/ebean/common/BeanSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ public Collection<E> actualDetails() {
}

@Override
public Collection<?> actualEntries() {
public Collection<?> actualEntries(boolean load) {
if (load) {
init();
}
return set;
}

Expand Down
11 changes: 2 additions & 9 deletions ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,11 @@
*/
public interface JsonBeanReader<T> {

/**
* Read the JSON into given bean. Will update existing properties.
*/
T read(T target);

/**
* Read the JSON returning a bean.
*/
default T read() {
return read(null);
}

T read();

/**
* Create a new reader taking the context from the existing one but using a new JsonParser.
*/
Expand Down
13 changes: 13 additions & 0 deletions ebean-api/src/main/java/io/ebean/text/json/JsonContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.BeanMergeOptions;
import io.ebean.FetchPath;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Property;
Expand Down Expand Up @@ -65,47 +66,59 @@ public interface JsonContext {
* instances, so the object identity will not be preserved here.
*
* @throws JsonIOException When IOException occurs
* @deprecated use {@link #toBean(Class, JsonParser)} and {@link io.ebean.Database#mergeBeans(Object, Object, BeanMergeOptions)}
*/
@Deprecated
<T> void toBean(T target, JsonParser parser) throws JsonIOException;

/**
* Read json parser input into a given Bean additionally using JsonReadOptions.<br>
* See {@link #toBean(Class, JsonParser)} for details modified.
*
* @throws JsonIOException When IOException occurs
* @deprecated use {@link #toBean(Class, JsonParser, JsonReadOptions)} and {@link io.ebean.Database#mergeBeans(Object, Object, BeanMergeOptions)}
*/
@Deprecated
<T> void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException;

/**
* Read json reader input into a given Bean.<br>
* See {@link #toBean(Class, JsonParser)} for details
*
* @throws JsonIOException When IOException occurs
* @deprecated use {@link #toBean(Class, Reader)} and {@link io.ebean.Database#mergeBeans(Object, Object, BeanMergeOptions)}
*/
@Deprecated
<T> void toBean(T target, Reader json) throws JsonIOException;

/**
* Read json reader input into a given Bean additionally using JsonReadOptions.<br>
* See {@link #toBean(Class, JsonParser)} for details modified.
*
* @throws JsonIOException When IOException occurs
* @deprecated use {@link #toBean(Class, Reader, JsonReadOptions)} and {@link io.ebean.Database#mergeBeans(Object, Object, BeanMergeOptions)}
*/
@Deprecated
<T> void toBean(T target, Reader json, JsonReadOptions options) throws JsonIOException;

/**
* Read json string input into a given Bean.<br>
* See {@link #toBean(Class, JsonParser)} for details
*
* @throws JsonIOException When IOException occurs
* @deprecated use {@link #toBean(Class, String)} and {@link io.ebean.Database#mergeBeans(Object, Object, BeanMergeOptions)}
*/
@Deprecated
<T> void toBean(T target, String json) throws JsonIOException;

/**
* Read json string input into a given Bean additionally using JsonReadOptions.<br>
* See {@link #toBean(Class, JsonParser)} for details
*
* @throws JsonIOException When IOException occurs
* @deprecated use {@link #toBean(Class, String, JsonReadOptions)} and {@link io.ebean.Database#mergeBeans(Object, Object, BeanMergeOptions)}
*/
@Deprecated
<T> void toBean(T target, String json, JsonReadOptions options) throws JsonIOException;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,4 @@ public interface SpiJsonReader {

Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException;

boolean update();
}
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,12 @@ public void merge(Object bean, MergeOptions options, @Nullable Transaction trans
executeInTrans((txn) -> persister.merge(desc, checkEntityBean(bean), options, txn), transaction);
}

@Override
public <T> T mergeBeans(T bean, T existing, BeanMergeOptions options) {
BeanDescriptor<?> desc = desc(bean.getClass());
return (T) desc.mergeBeans(checkEntityBean(bean), (EntityBean) existing, options);
}

@Override
public void lock(Object bean) {
BeanDescriptor<?> desc = desc(bean.getClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -708,22 +708,15 @@ public void metricPersistNoBatch(PersistRequest.Type type, long startNanos) {
iudMetrics.addNoBatch(type, startNanos);
}

public void merge(EntityBean bean, EntityBean existing) {
EntityBeanIntercept fromEbi = bean._ebean_getIntercept();
EntityBeanIntercept toEbi = existing._ebean_getIntercept();
int propertyLength = toEbi.propertyLength();
String[] names = properties();
for (int i = 0; i < propertyLength; i++) {
if (fromEbi.isLoadedProperty(i)) {
BeanProperty property = beanProperty(names[i]);
if (!toEbi.isLoadedProperty(i)) {
Object val = property.getValue(bean);
property.setValue(existing, val);
} else if (property.isMany()) {
property.merge(bean, existing);
}
}
/**
* Copies all modified fields from <code>bean</code> to <code>existing</code>.
* It returns normally the existing bean (or a new instance, if it was null)
*/
public EntityBean mergeBeans(EntityBean bean, EntityBean existing, BeanMergeOptions options) {
if (existing == null) {
existing = createEntityBean();
}
return new BeanMergeHelp(existing, options).mergeBeans(this, bean, existing);
}

/**
Expand Down Expand Up @@ -1781,7 +1774,7 @@ public EntityBean createEntityBeanForJson() {
/**
* We actually need to do a query because we don't know the type without the discriminator value.
*/
private T findReferenceBean(Object id, PersistenceContext pc) {
public T findReferenceBean(Object id, PersistenceContext pc) {
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.expressionFactory());
query.setPersistenceContext(pc);
return query.setId(id).findOne();
Expand Down Expand Up @@ -2044,8 +2037,8 @@ public void contextPut(PersistenceContext pc, Object id, Object bean) {
* Put the bean into the persistence context if it is absent.
*/
@Override
public Object contextPutIfAbsent(PersistenceContext pc, Object id, EntityBean localBean) {
return pc.putIfAbsent(rootBeanType, id, localBean);
public EntityBean contextPutIfAbsent(PersistenceContext pc, Object id, EntityBean localBean) {
return (EntityBean) pc.putIfAbsent(rootBeanType, id, localBean);
}

/**
Expand Down Expand Up @@ -3391,12 +3384,13 @@ void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) {
jsonHelp.jsonWriteProperties(writeJson, bean);
}

public T jsonRead(SpiJsonReader jsonRead, String path, T target) throws IOException {
return jsonHelp.jsonRead(jsonRead, path, true, target);
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path, true);
}

T jsonReadObject(SpiJsonReader jsonRead, String path, T target) throws IOException {
return jsonHelp.jsonRead(jsonRead, path, false, target);

T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path, false);
}

public List<BeanProperty[]> uniqueProps() {
Expand Down
Loading

0 comments on commit f398820

Please sign in to comment.