io.ebeaninternal.server.persist.MergeHandler Maven / Gradle / Ivy
package io.ebeaninternal.server.persist;
import io.ebean.CacheMode;
import io.ebean.MergeOptions;
import io.ebean.PersistenceContextScope;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import jakarta.persistence.PersistenceException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Drives the merge processing.
*/
final class MergeHandler {
private static final Pattern PATH_SPLIT = Pattern.compile("\\.");
private final SpiEbeanServer server;
private final BeanDescriptor> desc;
private final EntityBean bean;
private final MergeOptions options;
private final SpiTransaction transaction;
private final Map nodes = new LinkedHashMap<>();
MergeHandler(SpiEbeanServer server, BeanDescriptor> desc, EntityBean bean, MergeOptions options, SpiTransaction transaction) {
this.server = server;
this.desc = desc;
this.bean = bean;
this.options = options;
this.transaction = transaction;
}
/**
* Fetch the Ids for the graph and use them to determine inserts, updates and deletes for the merge paths.
*/
List merge() {
Set paths = options.paths();
if (desc.isIdGeneratedValue() && paths.isEmpty() && !options.isClientGeneratedIds()) {
// just do a single insert or update based on Id value present
Object id = desc.getId(bean);
if (id != null) {
bean._ebean_getIntercept().setForceUpdate(true);
}
return Collections.emptyList();
}
EntityBean outline = fetchOutline(paths);
if (outline == null) {
// considered an insert ...
return Collections.emptyList();
}
// the top level bean is an update
bean._ebean_getIntercept().setForceUpdate(true);
// detect what beans are updates and recursively set forceUpdate as needed
// and outline beans not in the merge graph are generally considered as deletes
MergeContext context = new MergeContext(server, transaction, options.isClientGeneratedIds());
MergeRequest request = new MergeRequest(context, bean, outline);
for (MergeNode value : nodes.values()) {
value.merge(request);
}
return context.getDeletedBeans();
}
/**
* Fetch the outline bean with associated one and associated many beans loaded with Id values only.
*
* We use the Id values to determine what are inserts, updates and deletes as part of the merge.
*/
private EntityBean fetchOutline(Set paths) {
SpiQuery> query = server.createQuery(desc.type());
query.setBeanCacheMode(CacheMode.OFF);
query.setPersistenceContextScope(PersistenceContextScope.QUERY);
query.setId(desc.getId(bean));
query.select(desc.idProperty().name());
for (String path : paths) {
MergeNode node = buildNode(path);
node.addSelectId(query);
}
query.usingTransaction(transaction);
return (EntityBean) server.findOne(query);
}
private MergeNode buildNode(String path) {
String[] split = PATH_SPLIT.split(path);
if (split.length == 1) {
return addRootLevelNode(split[0]);
} else {
return addSubNode(path, split);
}
}
private MergeNode addSubNode(String fullPath, String[] split) {
MergeNode parent = nodes.get(split[0]);
if (parent == null) {
throw new PersistenceException("Unable to find parent path " + split[0] + " in merge paths?");
}
for (int i = 1; i < split.length - 1; i++) {
parent = parent.get(split[i]);
if (parent == null) {
throw new PersistenceException("Unable to find parent path " + split[0] + " in merge paths?");
}
}
return parent.addChild(fullPath, split[split.length - 1]);
}
private MergeNode addRootLevelNode(String rootPath) {
MergeNode node = createMergeNode(rootPath, desc, rootPath);
nodes.put(rootPath, node);
return node;
}
static MergeNode createMergeNode(String fullPath, BeanDescriptor> targetDesc, String path) {
BeanProperty prop = targetDesc.beanProperty(path);
if (!(prop instanceof BeanPropertyAssoc)) {
throw new PersistenceException("merge path [" + path + "] is not a ToMany or ToOne property of " + targetDesc.fullName());
}
if (prop instanceof BeanPropertyAssocMany>) {
BeanPropertyAssocMany> assocMany = (BeanPropertyAssocMany>) prop;
if (assocMany.isManyToMany()) {
return new MergeNodeAssocManyToMany(fullPath, assocMany);
} else {
return new MergeNodeAssocOneToMany(fullPath, assocMany);
}
} else {
return new MergeNodeAssocOne(fullPath, (BeanPropertyAssocOne>) prop);
}
}
}