Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provided showcase for planned removal of @DocStore and @Inheritance #3210

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package org.tests.docstore;


import io.ebean.xtest.BaseTestCase;
import io.ebean.BeanState;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import io.ebean.text.json.JsonReadOptions;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Product;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.docstore.CustomerReport;
import org.tests.model.docstore.ProductReport;
import org.tests.model.docstore.ReportContainer;

import java.util.Arrays;

Expand All @@ -21,7 +25,6 @@ public void testToJson() throws Exception {
ResetBasicData.reset();



String json = server().json().toJson(getCustomerReport());
assertThat(json).isEqualTo("{\"dtype\":\"CR\",\"friends\":[{\"id\":2},{\"id\":3}],\"customer\":{\"id\":1}}");
}
Expand Down Expand Up @@ -52,9 +55,9 @@ public void testEmbeddedDocs() throws Exception {
String json = server().json().toJson(report);

assertThat(json).isEqualTo("{\"dtype\":\"CR\","
+ "\"embeddedReports\":[{\"dtype\":\"PR\",\"title\":\"This is a good product\",\"product\":{\"id\":1}}],"
+ "\"friends\":[{\"id\":2},{\"id\":3}],"
+ "\"customer\":{\"id\":1}}");
+ "\"embeddedReports\":[{\"dtype\":\"PR\",\"title\":\"This is a good product\",\"product\":{\"id\":1}}],"
+ "\"friends\":[{\"id\":2},{\"id\":3}],"
+ "\"customer\":{\"id\":1}}");

JsonReadOptions opts = new JsonReadOptions();
opts.setEnableLazyLoading(true);
Expand Down Expand Up @@ -85,4 +88,85 @@ private ProductReport getProductReport() {
report.setProduct(product);
return report;
}

/**
* Tests the inheritance support for DocStore/Jsons.
* We do not use default jackson serialization. In Report-class
* there are (de)serializers, that will delegate the (de)serialization
* back to ebean.
* <p>
* Big advantage: Ebean supports Inheritance with JSONS and some kind
* of "autodiscovery".
* <p>
* In theory, Jackson could do serialization with `@JsonSubTypes`, but
* they have to be specified in the top class. See
* https://github.com/FasterXML/jackson-databind/issues/2104
*/
@Test
public void testInheritance() {
ReportContainer container = new ReportContainer();
container.setReport(new ProductReport());
DB.save(container);

ReportContainer container2 = new ReportContainer();
container2.setReport(new CustomerReport());
DB.save(container2);

container = DB.find(ReportContainer.class, container.getId());
container2 = DB.find(ReportContainer.class, container2.getId());

assertThat(container.getReport()).isInstanceOf(ProductReport.class);
assertThat(container2.getReport()).isInstanceOf(CustomerReport.class);
}

/**
* This test shows how we do the (de)serialization when we reference entites, that are persisted in the DB
*/
@Test
public void testReferenceBean() {
ResetBasicData.reset();
ReportContainer container = new ReportContainer();
CustomerReport report = new CustomerReport();
container.setReport(report);

Object robId = DB.find(Customer.class).where().eq("name", "Rob").findIds().get(0);
report.setFriends(DB.find(Customer.class).where().eq("name", "Fiona").findList());
LoggedSql.start();
report.setCustomer(DB.reference(Customer.class, robId));
DB.save(container);
assertThat(LoggedSql.stop()).hasSize(1); // no lazy load

container = DB.find(ReportContainer.class, container.getId());
assertThat(((CustomerReport) container.getReport()).getCustomer().getName()).isEqualTo("Rob");
assertThat(((CustomerReport) container.getReport()).getFriends().get(0).getName()).isEqualTo("Fiona");
}

/**
* This test shows the dirty detection on docstore beans.
*/
@Test
public void testJsonBeanState() {
ResetBasicData.reset();
ReportContainer container = new ReportContainer();
CustomerReport report = new CustomerReport();
report.setTitle("Foo");
container.setReport(report);

BeanState state = DB.beanState(container.getReport());
assertThat(state.isNew()).isTrue();

DB.save(container);
container = DB.find(ReportContainer.class, container.getId());

state = DB.beanState(container.getReport());
assertThat(state.isDirty()).isFalse();
assertThat(state.isNew()).isFalse(); // this detection does not work on JSONs

container.getReport().setTitle("Bar");
state = DB.beanState(container.getReport());
assertThat(state.isDirty()).isTrue();
//BTW: ValuePair has no equals & hashcode
//assertThat(state.dirtyValues()).hasSize(1).containsEntry("title", new ValuePair("Bar", "Foo"));
assertThat(state.dirtyValues()).hasSize(1).hasToString("{title=Bar,Foo}");
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package org.tests.model.docstore;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.ebean.annotation.DocStore;

import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Inheritance;
import jakarta.persistence.OneToMany;

import java.util.List;

@DocStore
@Inheritance
@DiscriminatorColumn
@JsonSerialize(using = ReportJsonSerializer.class)
@JsonDeserialize(using = ReportJsonDeserializer.class)
public class Report {
private String title;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.tests.model.docstore;

import io.ebean.annotation.DbJson;
import org.tests.model.basic.BasicDomain;

import javax.persistence.Entity;

/**
* The reportcontainer itself persists the Report JSON in the database.
*
* @author Roland Praml, FOCONIS AG
*/
@Entity
public class ReportContainer extends BasicDomain {

// By default, ebean will use Jackson as serializer here.
// It would be great, if ebean will serialize docstore beans automatically with DB.json()
// This is currently achieved through the JsonSerialize/Deserialize annotations in the report class
// (ebean -> jackson -> ebean)
@DbJson(length = 1024 * 1024) // we do not expect report definitions bigger than 1M
private Report report;


public Report getReport() {
return report;
}

public void setReport(Report report) {
this.report = report;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed Materials - Property of FOCONIS AG
* (C) Copyright FOCONIS AG.
*/

package org.tests.model.docstore;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import io.ebean.DB;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.JsonReadOptions;

import java.io.IOException;

/**
* Deserializer, that uses ebean deserialization when deserializing beans from DB.
*/
public class ReportJsonDeserializer extends JsonDeserializer<Report> {


@Override
public Report deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException {
JsonReadOptions options = new JsonReadOptions();
options.setEnableLazyLoading(true);
Report bean = DB.json().toBean(Report.class, parser, options);
((EntityBean) bean)._ebean_getIntercept().setLoaded(); // to make beanstate work
return bean;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.tests.model.docstore;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import io.ebean.DB;
import io.ebean.FetchPath;
import io.ebean.Query;
import io.ebean.text.json.JsonWriteOptions;

import java.io.IOException;
import java.util.Set;

/**
* Serializer, that advises Jackson to use ebean serialization instead.
* <p>
* It also handles the "id" serialization of referenced properties.
* <p>
* Note: This is a very simple class stripped down to provide a test case
* (we use a more generic one in our code)
*/
public class ReportJsonSerializer extends JsonSerializer<Report> {

private static final FetchPath SERIALIZE_TO_DISK = new FetchPath() {
@Override
public boolean hasPath(final String path) {
return true;
}

@Override
public Set<String> getProperties(final String path) {
if (path == null) {
return Set.of("*"); // for json model itself, serialize all properties
} else {
return Set.of("id"); // for referenced DB-beans (e.g. customers), serialize id only
}
}

@Override
public <T> void apply(final Query<T> query) {
throw new UnsupportedOperationException();
}
};

@Override
public void serialize(final Report value, final JsonGenerator jgen, final SerializerProvider serializers) throws IOException {
JsonWriteOptions options = new JsonWriteOptions();
options.setPathProperties(SERIALIZE_TO_DISK);
DB.json().toJson(value, jgen, options);
}

}
Loading