mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
NEW: PersistVisitor
This commit is contained in:
@@ -477,6 +477,19 @@ public final class DB {
|
||||
public static int saveAll(Object... beans) throws OptimisticLockException {
|
||||
return getDefault().saveAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will visit all beans in the persist graph on a given object
|
||||
* <code>start</code>. It will call the visitor for each dirty bean that would
|
||||
* be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can
|
||||
* use this method to implement custom validations.
|
||||
*
|
||||
* @param start could be a bean, a list of beans or a map of beans.
|
||||
* @param visitor the visitor
|
||||
*/
|
||||
public static void visitSave(Object start, PersistVisitor visitor) {
|
||||
getDefault().visitSave(start, visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
|
||||
|
||||
@@ -1017,6 +1017,17 @@ public interface Database {
|
||||
* Save all the beans.
|
||||
*/
|
||||
int saveAll(Object... beans) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* This will visit all beans in the persist graph on a given object
|
||||
* <code>start</code>. It will call the visitor for each dirty bean that would
|
||||
* be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can
|
||||
* use this method to implement custom validations.
|
||||
*
|
||||
* @param start could be a bean, a list of beans or a map of beans.
|
||||
* @param visitor the visitor
|
||||
*/
|
||||
void visitSave(Object start, PersistVisitor visitor);
|
||||
|
||||
/**
|
||||
* Delete the bean.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.Property;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PersistVisitor {
|
||||
|
||||
PersistVisitor visitBean(EntityBean bean);
|
||||
|
||||
default PersistVisitor visitProperty(Property prop) {
|
||||
return this;
|
||||
};
|
||||
|
||||
default PersistVisitor visitCollection(Collection<?> collection) {
|
||||
return this;
|
||||
};
|
||||
|
||||
default PersistVisitor visitMap(Map<?, ?> map) {
|
||||
return this;
|
||||
};
|
||||
|
||||
default void visitEnd() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import io.ebean.FutureRowCount;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.MergeOptionsBuilder;
|
||||
import io.ebean.PagedList;
|
||||
import io.ebean.PersistVisitor;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.Query;
|
||||
@@ -1628,6 +1629,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
persister.save(checkEntityBean(bean), transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSave(Object start, PersistVisitor visitor) {
|
||||
new VisitHandler(descriptorManager).visit(start, visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsDirty(Object bean) {
|
||||
if (!(bean instanceof EntityBean)) {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.ebean.PersistVisitor;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
|
||||
/**
|
||||
* Handler to process persist graphs. It will allow you to visit a persist
|
||||
* action (insert/update) and get information about all beans that will be
|
||||
* affected by that action. This allows you to do validation and other things on
|
||||
* a set of beans.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
class VisitHandler {
|
||||
|
||||
private final Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
|
||||
private final BeanDescriptorManager beanDescriptorManager;
|
||||
|
||||
public VisitHandler(BeanDescriptorManager beanDescriptorManager) {
|
||||
this.beanDescriptorManager = beanDescriptorManager;
|
||||
}
|
||||
|
||||
public void visit(Object start, PersistVisitor visitor) {
|
||||
if (start != null) {
|
||||
if (start instanceof EntityBean) {
|
||||
visitBean((EntityBean) start, visitor);
|
||||
} else {
|
||||
visitMany(start, visitor);
|
||||
}
|
||||
}
|
||||
visitor.visitEnd();
|
||||
}
|
||||
|
||||
private void visitBean(EntityBean bean, PersistVisitor visitor) {
|
||||
if (!seen.add(bean)) {
|
||||
return;
|
||||
}
|
||||
visitor = visitor.visitBean(bean);
|
||||
if (visitor != null) {
|
||||
BeanDescriptor<?> desc = beanDescriptorManager.descriptor(bean.getClass());
|
||||
visitOnes(bean, visitor, desc, desc.propertiesOneImportedSave());
|
||||
visitOnes(bean, visitor, desc, desc.propertiesOneExportedSave());
|
||||
visitManys(bean, visitor, desc, desc.propertiesManySave());
|
||||
visitor.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private void visitManys(EntityBean bean, PersistVisitor visitor, BeanDescriptor<?> desc,
|
||||
BeanPropertyAssocMany<?>[] manys) {
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
for (BeanPropertyAssocMany<?> many : manys) {
|
||||
// check that property is loaded and collection should be cascaded to
|
||||
if (ebi.isLoadedProperty(many.propertyIndex()) && !many.isSkipSaveBeanCollection(bean, ebi.isNew())) {
|
||||
Object manyValue = many.getValue(bean);
|
||||
if (manyValue != null) {
|
||||
PersistVisitor propertyVisitor = visitor.visitProperty(many);
|
||||
if (propertyVisitor != null) {
|
||||
visitMany(manyValue, propertyVisitor);
|
||||
propertyVisitor.visitEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void visitMany(Object many, PersistVisitor visitor) {
|
||||
if (!seen.add(many)) {
|
||||
return;
|
||||
}
|
||||
if (many instanceof Collection) {
|
||||
Collection<?> coll = (Collection<?>) many;
|
||||
PersistVisitor collectionVisitor = visitor.visitCollection(coll);
|
||||
if (collectionVisitor != null) {
|
||||
for (Object elem : coll) {
|
||||
visitBean((EntityBean) elem, collectionVisitor);
|
||||
}
|
||||
collectionVisitor.visitEnd();
|
||||
}
|
||||
} else if (many instanceof Map) {
|
||||
Map<?, ?> map = (Map<?, ?>) many;
|
||||
PersistVisitor mapVisitor = visitor.visitMap(map);
|
||||
if (mapVisitor != null) {
|
||||
for (Object elem : ((Map<?, ?>) many).values()) {
|
||||
visitBean((EntityBean) elem, mapVisitor);
|
||||
}
|
||||
mapVisitor.visitEnd();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Object " + many + " cannot be visited in persist graph");
|
||||
}
|
||||
}
|
||||
|
||||
private void visitOnes(EntityBean bean, PersistVisitor visitor, BeanDescriptor<?> desc,
|
||||
BeanPropertyAssocOne<?>[] ones) {
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
for (BeanPropertyAssocOne<?> prop : ones) {
|
||||
if (ebi.isLoadedProperty(prop.propertyIndex())) {
|
||||
EntityBean detailBean = prop.getValueAsEntityBean(bean);
|
||||
if (detailBean != null && !prop.isSaveRecurseSkippable(detailBean) && !prop.isReference(detailBean)) {
|
||||
PersistVisitor propertyVisitor = visitor.visitProperty(prop);
|
||||
if (propertyVisitor != null) {
|
||||
visitBean(detailBean, propertyVisitor);
|
||||
propertyVisitor.visitEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,6 +293,11 @@ public class TDSpiServer implements SpiServer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSave(Object start, PersistVisitor visitor) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(Object bean) throws OptimisticLockException {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.tests.basic;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.ebean.DB;
|
||||
import io.ebean.PersistVisitor;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.Property;
|
||||
/**
|
||||
* Sample persist visitor that converts the visited beans in a XML-like structure
|
||||
*/
|
||||
public class TPersistVisitor implements PersistVisitor {
|
||||
|
||||
private final StringBuilder sb;
|
||||
private final String indent;
|
||||
private final String tag;
|
||||
private boolean empty = true;
|
||||
|
||||
public TPersistVisitor() {
|
||||
this(new StringBuilder(), "", "root");
|
||||
}
|
||||
private TPersistVisitor(StringBuilder sb, String indent, String tag) {
|
||||
this.sb = sb;
|
||||
this.indent = indent;
|
||||
this.tag = tag;
|
||||
this.sb.append(indent).append('<').append(tag);
|
||||
}
|
||||
|
||||
TPersistVisitor newVisitor(String tag) {
|
||||
if (empty) {
|
||||
sb.append(">\n");
|
||||
empty = false;
|
||||
}
|
||||
return new TPersistVisitor(sb, indent + " ", tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
if (empty) {
|
||||
sb.append("/>\n");
|
||||
} else {
|
||||
this.sb.append(indent).append("</").append(tag).append(">\n");
|
||||
}
|
||||
}
|
||||
|
||||
TPersistVisitor attr(String attr, Object value) {
|
||||
sb.append(' ').append(attr).append('=').append('\'').append(value).append('\'');
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPersistVisitor visitBean(EntityBean bean) {
|
||||
return newVisitor("bean").attr("type", bean.getClass().getSimpleName()).attr("newOrDirty", DB.beanState(bean).isNewOrDirty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistVisitor visitProperty(Property prop) {
|
||||
return newVisitor("property").attr("name", prop.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistVisitor visitCollection(Collection<?> collection) {
|
||||
return newVisitor("collection").attr("size", collection.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,15 @@ package org.tests.basic;
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.tests.model.basic.MRole;
|
||||
import org.tests.model.basic.MUser;
|
||||
|
||||
public class TestM2MCascadeOne extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
@@ -30,8 +33,31 @@ public class TestM2MCascadeOne extends BaseTestCase {
|
||||
u1.addRole(r0);
|
||||
u1.addRole(r1);
|
||||
|
||||
TPersistVisitor tv = new TPersistVisitor();
|
||||
DB.visitSave(u1, tv);
|
||||
assertThat(tv.toString()).isEqualTo("<root>\n"
|
||||
+ " <bean type='MUser' newOrDirty='false'>\n"
|
||||
+ " <property name='roles'>\n"
|
||||
+ " <collection size='2'>\n"
|
||||
+ " <bean type='MRole' newOrDirty='false'/>\n"
|
||||
+ " <bean type='MRole' newOrDirty='true'/>\n"
|
||||
+ " </collection>\n"
|
||||
+ " </property>\n"
|
||||
+ " </bean>\n"
|
||||
+ "</root>\n");
|
||||
|
||||
DB.save(u1);
|
||||
|
||||
|
||||
u1 = DB.find(MUser.class, u.getUserid());
|
||||
|
||||
tv = new TPersistVisitor();
|
||||
DB.visitSave(u1, tv);
|
||||
// collection is unloaded
|
||||
assertThat(tv.toString()).isEqualTo("<root>\n"
|
||||
+ " <bean type='MUser' newOrDirty='false'/>\n"
|
||||
+ "</root>\n");
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user