diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index def5457d45..674796eb80 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -668,6 +668,33 @@ public void setTenantValue(EntityBean entityBean, Object tenantId) { setValue(entityBean, tenantId); } + /** + * By default, getIntercept and setIntercept will check, if the passed bean is an instance of descriptor type. + *
+ * If the property is not part of the type hierarchy (i.e. is this property from this descriptor) + * an UnsuppoertedOperation is thrown. + *
+ * If inheritance is involved, this method returns false instead of an exception, if the property might exist in
+ * one of the child beans. This is necessary for getIntercept, as it returns null in this case.
+ * @return true if property can be
+ */
+ private boolean checkPropertyAccess(EntityBean bean) {
+ if (bean == null || descriptor.type().isInstance(bean)) { // null = fall through - NPE is catched later.
+ return true;
+ }
+ InheritInfo inheritInfo = descriptor.inheritInfo();
+ if (inheritInfo == null || inheritInfo.isRoot() || !inheritInfo.getRoot().getType().isInstance(bean)) {
+ throw new IllegalArgumentException(propertyIncomatibleMsg(bean));
+ } else {
+ return false;
+ }
+ }
+
+ private String propertyIncomatibleMsg(EntityBean bean) {
+ String beanType = bean == null ? "null" : bean.getClass().getName();
+ return "Property " + name + " on [" + descriptor + "] is incompatible with type[" + beanType + "]";
+ }
+
/**
* Set the value of the property without interception or
* PropertyChangeSupport.
@@ -684,6 +711,9 @@ public void setValue(EntityBean bean, Object value) {
* Set the value of the property.
*/
public void setValueIntercept(EntityBean bean, Object value) {
+ if (!checkPropertyAccess(bean)) {
+ throw new IllegalArgumentException(propertyIncomatibleMsg(bean));
+ }
try {
setter.setIntercept(bean, value);
} catch (Exception ex) {
@@ -795,6 +825,9 @@ public Object getValue(EntityBean bean) {
}
public Object getValueIntercept(EntityBean bean) {
+ if (!checkPropertyAccess(bean)) {
+ return null;
+ }
try {
return getter.getIntercept(bean);
} catch (Exception ex) {
diff --git a/ebean-test/src/test/java/org/tests/inheritance/TestPropertyAccess.java b/ebean-test/src/test/java/org/tests/inheritance/TestPropertyAccess.java
new file mode 100644
index 0000000000..b710c67c60
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/inheritance/TestPropertyAccess.java
@@ -0,0 +1,118 @@
+package org.tests.inheritance;
+
+import io.ebean.DB;
+import io.ebean.plugin.ExpressionPath;
+import org.junit.jupiter.api.Test;
+import org.tests.model.basic.*;
+
+import java.sql.Timestamp;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * @author Roland Praml, FOCONIS AG
+ */
+public class TestPropertyAccess {
+
+
+ private ExpressionPath custCretime = DB.getDefault().pluginApi().beanType(Customer.class).expressionPath("cretime");
+ private ExpressionPath custName = DB.getDefault().pluginApi().beanType(Customer.class).expressionPath("name");
+ // Note: Animal.name is only present in "Cat" not in "Dog"
+ private ExpressionPath animalName = DB.getDefault().pluginApi().beanType(Animal.class).expressionPath("name");
+ private ExpressionPath productName = DB.getDefault().pluginApi().beanType(Product.class).expressionPath("name");
+ private ExpressionPath animalSpecies = DB.getDefault().pluginApi().beanType(Animal.class).expressionPath("species");
+
+ @Test
+ void testOnInheritance() {
+ Cat cat = new Cat();
+ cat.setName("Tom");
+ DB.save(cat);
+
+ Dog dog = new Dog();
+ dog.setRegistrationNumber("FOO");
+ DB.save(dog);
+
+
+ Animal animal = DB.find(Animal.class, cat.getId());
+ assertThat(animalName.pathGet(animal)).isEqualTo("Tom");
+
+ animal = DB.find(Animal.class, dog.getId());
+ assertThat(animalName.pathGet(animal)).isNull();
+
+ animalName.pathSet(cat, "Jerry");
+ assertThat(cat.getName()).isEqualTo("Jerry");
+
+ assertThatThrownBy(() -> animalName.pathSet(dog, "Jerry"))
+ .isInstanceOf(IllegalArgumentException.class);
+
+ animalSpecies.pathSet(cat, "Angora");
+ animalSpecies.pathSet(dog, "Bulldog");
+
+ assertThat(cat.getSpecies()).isEqualTo("Angora");
+ assertThat(dog.getSpecies()).isEqualTo("Bulldog");
+ }
+
+ @Test
+ void testOnMappedSuperClass() {
+ Customer cust = new Customer();
+
+ Timestamp ts = new Timestamp(123);
+ custCretime.pathSet(cust, ts);
+ assertThat(custCretime.pathGet(cust)).isEqualTo(ts);
+
+ custName.pathSet(cust, "Roland");
+ assertThat(custName.pathGet(cust)).isEqualTo("Roland");
+
+ }
+
+ @Test
+ void testOnPlainBean() {
+ Product product = new Product();
+
+ productName.pathSet(product, "Roland");
+ assertThat(productName.pathGet(product)).isEqualTo("Roland");
+ }
+
+ @Test
+ void testOnContactGroup() {
+ ContactGroup cg = new ContactGroup();
+
+ // CHECKEM: Ist it OK to us the "custCretime" on "contactGroup"
+ assertThatThrownBy(() -> custCretime.pathGet(cg)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testOnCrossUsage() {
+ Product product = new Product();
+ Customer cust = new Customer();
+ Cat cat = new Cat();
+ Dog dog = new Dog();
+
+
+ assertThatThrownBy(() -> custCretime.pathGet(product)).isInstanceOf(IllegalArgumentException.class);
+ custCretime.pathGet(cust);
+ assertThatThrownBy(() -> custCretime.pathGet(cat)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> custCretime.pathGet(dog)).isInstanceOf(IllegalArgumentException.class);
+
+ assertThatThrownBy(() -> custName.pathGet(product)).isInstanceOf(IllegalArgumentException.class);
+ custName.pathGet(cust);
+ assertThatThrownBy(() -> custName.pathGet(cat)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> custName.pathGet(dog)).isInstanceOf(IllegalArgumentException.class);
+
+ assertThatThrownBy(() -> animalName.pathGet(product)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> animalName.pathGet(cust)).isInstanceOf(IllegalArgumentException.class);
+ animalName.pathGet(cat);
+ animalName.pathGet(dog);
+
+ productName.pathGet(product);
+ assertThatThrownBy(() -> productName.pathGet(cust)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> productName.pathGet(cat)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> productName.pathGet(dog)).isInstanceOf(IllegalArgumentException.class);
+
+ assertThatThrownBy(() -> animalSpecies.pathGet(product)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> animalSpecies.pathGet(cust)).isInstanceOf(IllegalArgumentException.class);
+ animalSpecies.pathGet(cat);
+ animalSpecies.pathGet(dog);
+ }
+}