package io.ebeaninternal.server.persist;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanCollectionUtil;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.IntersectionRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static io.ebeaninternal.server.persist.DmlUtil.isNullOrZero;
public class SaveManyBeans extends SaveManyBase {
private static final Logger log = LoggerFactory.getLogger(SaveManyBeans.class);
private final boolean cascade;
private final boolean publish;
private final BeanDescriptor<?> targetDescriptor;
private final boolean isMap;
private final boolean saveRecurseSkippable;
private final DeleteMode deleteMode;
private final boolean untouchedBeanCollection;
private final Collection<?> collection;
private int sortOrder;
SaveManyBeans(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
super(persister, insertedParent, many, parentBean, request);
this.cascade = many.getCascadeInfo().isSave();
this.publish = request.isPublish();
this.targetDescriptor = many.getTargetDescriptor();
this.isMap = many.getManyType().isMap();
this.saveRecurseSkippable = many.isSaveRecurseSkippable();
this.deleteMode = targetDescriptor.isSoftDelete() ? DeleteMode.SOFT : DeleteMode.HARD;
this.untouchedBeanCollection = untouchedBeanCollection();
this.collection = cascade ? BeanCollectionUtil.getActualEntries(value) : null;
}
private boolean untouchedBeanCollection() {
return value instanceof BeanCollection && !((BeanCollection<?>) value).wasTouched();
}
@Override
void save() {
if (many.hasJoinTable()) {
boolean saveIntersectionFromThisDirection = isSaveIntersection();
if (cascade) {
saveAssocManyDetails();
}
if (saveIntersectionFromThisDirection) {
saveAssocManyIntersection();
} else {
resetModifyState();
}
} else {
if (isModifyListenMode() || many.hasOrderColumn()) {
removeAssocManyOrphans();
}
if (cascade) {
saveAssocManyDetails();
}
}
if (!insertedParent && !untouchedBeanCollection) {
request.addUpdatedManyForL2Cache(many);
}
}
private boolean isSaveIntersection() {
if (!many.isManyToMany()) {
return true;
}
return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().rootName());
}
private boolean isModifyListenMode() {
return BeanCollection.ModifyListenMode.REMOVALS == many.getModifyListenMode();
}
private void saveAssocManyDetails() {
if (collection != null) {
processDetails();
}
}
private void processDetails() {
BeanProperty orderColumn = null;
boolean hasOrderColumn = many.hasOrderColumn();
if (hasOrderColumn) {
if (!insertedParent && canSkipForOrderColumn() && saveRecurseSkippable) {
return;
}
orderColumn = targetDescriptor.getOrderColumn();
}
if (insertedParent) {
targetDescriptor.preAllocateIds(collection.size());
}
if (!insertedParent && many.isOrphanRemoval() && request.isForcedUpdate()) {
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
persister.deleteManyDetails(transaction, many.getBeanDescriptor(), parentBean, many, detailIds, deleteMode);
}
transaction.depth(+1);
saveAllBeans(orderColumn);
if (hasOrderColumn) {
resetModifyState();
}
transaction.depth(-1);
}
private void saveAllBeans(BeanProperty orderColumn) {
Object mapKeyValue = null;
boolean skipSavingThisBean;
for (Object detailBean : collection) {
sortOrder++;
if (isMap) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) detailBean;
mapKeyValue = entry.getKey();
detailBean = entry.getValue();
}
if (detailBean instanceof EntityBean) {
EntityBean detail = (EntityBean) detailBean;
EntityBeanIntercept ebi = detail._ebean_getIntercept();
if (many.hasJoinTable()) {
skipSavingThisBean = targetDescriptor.isReference(ebi);
} else {
if (orderColumn != null && !Objects.equals(sortOrder, orderColumn.getValue(detail))) {
orderColumn.setValue(detail, sortOrder);
ebi.setDirty(true);
}
if (targetDescriptor.isReference(ebi)) {
skipSavingThisBean = true;
} else if (ebi.isNewOrDirty()) {
skipSavingThisBean = false;
many.setJoinValuesToChild(parentBean, detail, mapKeyValue);
} else {
skipSavingThisBean = saveRecurseSkippable;
}
}
if (!skipSavingThisBean) {
persister.saveRecurse(detail, transaction, parentBean, request.getFlags());
if (many.hasOrderColumn()) {
final BeanDescriptor<?> beanDescriptor = many.getBeanDescriptor();
beanDescriptor.contextClear(transaction.getPersistenceContext(), beanDescriptor.getId(parentBean));
}
}
}
}
}
private boolean canSkipForOrderColumn() {
return untouchedBeanCollection && noDirtyBeans();
}
private boolean noDirtyBeans() {
for (Object bean : collection) {
if (bean instanceof EntityBean && ((EntityBean) bean)._ebean_getIntercept().isDirty()) {
return false;
}
}
return true;
}
private boolean hasNewOrDirtyBeans() {
if (collection == null) {
return false;
}
for (Object bean : collection) {
if (bean instanceof EntityBean && ((EntityBean) bean)._ebean_getIntercept().isNewOrDirty()) {
return true;
}
}
return false;
}
private List<Object> collectIds(Collection<?> collection, BeanDescriptor<?> targetDescriptor, boolean isMap) {
List<Object> detailIds = new ArrayList<>();
for (Object detailBean : collection) {
if (isMap) {
detailBean = ((Map.Entry<?, ?>) detailBean).getValue();
}
if (detailBean instanceof EntityBean) {
Object id = targetDescriptor.getId((EntityBean) detailBean);
if (!isNullOrZero(id)) {
detailIds.add(id);
}
}
}
return detailIds;
}
private void saveAssocManyIntersection() {
if (value == null) {
return;
}
if (request.isQueueSaveMany()) {
request.addSaveMany(this);
} else {
saveAssocManyIntersection(false);
}
}
@Override
public void saveBatch() {
saveAssocManyIntersection(true);
}
private void saveAssocManyIntersection(boolean queue) {
boolean forcedUpdate = request.isForcedUpdate();
boolean vanillaCollection = !(value instanceof BeanCollection<?>);
if (vanillaCollection || forcedUpdate) {
persister.deleteManyIntersection(parentBean, many, transaction, publish, queue);
}
Collection<?> deletions = null;
Collection<?> additions;
if (insertedParent || vanillaCollection || forcedUpdate) {
if (value instanceof Map<?, ?>) {
additions = ((Map<?, ?>) value).values();
} else if (value instanceof Collection<?>) {
additions = (Collection<?>) value;
} else {
throw new PersistenceException("Unhandled ManyToMany type " + value.getClass().getName() + " for " + many.getFullBeanName());
}
if (!vanillaCollection) {
BeanCollection<?> manyValue = (BeanCollection<?>) value;
setListenMode(manyValue, many);
manyValue.modifyReset();
}
} else {
BeanCollection<?> manyValue = (BeanCollection<?>) value;
if (setListenMode(manyValue, many)) {
additions = manyValue.getActualDetails();
} else {
additions = manyValue.getModifyAdditions();
deletions = manyValue.getModifyRemovals();
}
manyValue.modifyReset();
}
transaction.depth(+1);
if (additions != null && !additions.isEmpty()) {
for (Object other : additions) {
EntityBean otherBean = (EntityBean) other;
if (deletions != null && deletions.remove(otherBean)) {
String m = "Inserting and Deleting same object? " + otherBean;
if (transaction.isLogSummary()) {
transaction.logSummary(m);
}
log.warn(m);
} else {
if (!many.hasImportedId(otherBean)) {
throw new PersistenceException("ManyToMany bean " + otherBean + " does not have an Id value.");
} else {
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherBean, publish);
SpiSqlUpdate sqlInsert = intRow.createInsert(server);
persister.executeOrQueue(sqlInsert, transaction, queue);
}
}
}
}
if (deletions != null && !deletions.isEmpty()) {
for (Object other : deletions) {
EntityBean otherDelete = (EntityBean) other;
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish);
SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD);
persister.executeOrQueue(sqlDelete, transaction, queue);
}
}
transaction.depth(-1);
}
private void removeAssocManyOrphans() {
if (value == null) {
return;
}
if (!(value instanceof BeanCollection<?>)) {
} else {
BeanCollection<?> c = (BeanCollection<?>) value;
Set<?> modifyRemovals = c.getModifyRemovals();
if (insertedParent) {
c.setModifyListening(many.getModifyListenMode());
}
if (!many.hasOrderColumn()) {
c.modifyReset();
}
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
for (Object removedBean : modifyRemovals) {
if (removedBean instanceof EntityBean) {
EntityBean eb = (EntityBean) removedBean;
if (eb._ebean_intercept().isOrphanDelete()) {
persister.deleteRequest(persister.createDeleteRemoved(removedBean, transaction, request.getFlags()));
}
}
}
}
}
}
private boolean setListenMode(BeanCollection<?> manyValue, BeanPropertyAssocMany<?> prop) {
BeanCollection.ModifyListenMode mode = manyValue.getModifyListening();
if (mode == null) {
manyValue.setModifyListening(prop.getModifyListenMode());
return true;
}
return false;
}
}