NEW: CustomDeployParser

This commit is contained in:
Jonas Pöhler
2021-10-22 15:04:46 +02:00
parent 60cf9fa078
commit 50824f1d7f
15 changed files with 287 additions and 6 deletions
@@ -16,6 +16,7 @@ import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.plugin.CustomDeployParser;
import io.ebean.util.StringHelper;
import javax.persistence.EnumType;
@@ -389,6 +390,7 @@ public class DatabaseConfig {
private List<BeanQueryAdapter> queryAdapters = new ArrayList<>();
private final List<BulkTableEventListener> bulkTableEventListeners = new ArrayList<>();
private final List<ServerConfigStartup> configStartupListeners = new ArrayList<>();
private final List<CustomDeployParser> customDeployParsers = new ArrayList<>();
/**
* By default inserts are included in the change log.
@@ -2674,6 +2676,17 @@ public class DatabaseConfig {
return configStartupListeners;
}
/**
* Add a CustomDeployParser.
*/
public void addCustomDeployParser(CustomDeployParser customDeployParser) {
customDeployParsers.add(customDeployParser);
}
public List<CustomDeployParser> getCustomDeployParsers() {
return customDeployParsers;
}
/**
* Register all the BeanPersistListener instances.
* <p>
@@ -0,0 +1,15 @@
package io.ebean.plugin;
import io.ebean.config.dbplatform.DatabasePlatform;
/**
* Fired after all beans are parsed. You may implement own parsers to handle custom annotations.
* (See test case for example)
*
* @author Roland Praml, FOCONIS AG
*/
@FunctionalInterface
public interface CustomDeployParser {
void parse(DeployBeanDescriptorMeta descriptor, DatabasePlatform databasePlatform);
}
@@ -0,0 +1,37 @@
package io.ebean.plugin;
import java.util.Collection;
import java.util.List;
/**
* General deployment information. This is used in {@link CustomDeployParser}.
*
* @author Roland Praml, FOCONIS AG
*/
public interface DeployBeanDescriptorMeta {
/**
* Return a collection of all BeanProperty deployment information.
*/
public Collection<? extends DeployBeanPropertyMeta> propertiesAll();
/**
* Get a BeanProperty by its name.
*/
public DeployBeanPropertyMeta getBeanProperty(String secondaryBeanName);
/**
* Return the DeployBeanDescriptorMeta for the given bean class.
*/
public DeployBeanDescriptorMeta getDeployBeanDescriptorMeta(Class<?> propertyType);
/**
* Returns the discriminator column, if any.
* @return
*/
public String getDiscriminatorColumn();
public String getBaseTable();
DeployBeanPropertyMeta idProperty();
}
@@ -0,0 +1,23 @@
package io.ebean.plugin;
public interface DeployBeanPropertyAssocMeta extends DeployBeanPropertyMeta {
/**
* Return the mappedBy deployment attribute.
* <p>
* This is the name of the property in the 'detail' bean that maps back to
* this 'master' bean.
* </p>
*/
String getMappedBy();
/**
* Return the base table for this association.
* <p>
* This has the table name which is used to determine the relationship for
* this association.
* </p>
*/
String getBaseTable();
}
@@ -0,0 +1,38 @@
package io.ebean.plugin;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public interface DeployBeanPropertyMeta {
/**
* Return the name of the property.
*/
String getName();
/**
* The database column name this is mapped to.
*/
String getDbColumn();
/**
* Return the bean Field associated with this property.
*/
Field getField();
/**
* The property is based on a formula.
*/
void setSqlFormula(String sqlSelect, String sqlJoin);
/**
* Return the bean type.
*/
Class<?> getOwningType();
/**
* Return the property type.
*/
Class<?> getPropertyType();
}
@@ -168,6 +168,7 @@ public final class DefaultContainer implements SpiContainer {
bootup.addPersistListeners(config.getPersistListeners());
bootup.addQueryAdapters(config.getQueryAdapters());
bootup.addServerConfigStartup(config.getServerConfigStartupListeners());
bootup.addCustomDeployParser(config.getCustomDeployParsers());
bootup.addChangeLogInstances(config);
bootup.runServerConfigStartup(config);
return bootup;
@@ -11,6 +11,7 @@ import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.plugin.CustomDeployParser;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.CoreLog;
import org.slf4j.Logger;
@@ -51,6 +52,7 @@ public class BootupClasses implements Predicate<Class<?>> {
private final List<Class<? extends BeanPersistListener>> beanPersistListenerCandidates = new ArrayList<>();
private final List<Class<? extends BeanQueryAdapter>> beanQueryAdapterCandidates = new ArrayList<>();
private final List<Class<? extends ServerConfigStartup>> serverConfigStartupCandidates = new ArrayList<>();
private final List<Class<? extends CustomDeployParser>> customDeployParserCandidates = new ArrayList<>();
private final List<IdGenerator> idGeneratorInstances = new ArrayList<>();
private final List<BeanPersistController> beanPersistControllerInstances = new ArrayList<>();
@@ -60,6 +62,7 @@ public class BootupClasses implements Predicate<Class<?>> {
private final List<BeanPersistListener> beanPersistListenerInstances = new ArrayList<>();
private final List<BeanQueryAdapter> beanQueryAdapterInstances = new ArrayList<>();
private final List<ServerConfigStartup> serverConfigStartupInstances = new ArrayList<>();
private final List<CustomDeployParser> customDeployParserInstances = new ArrayList<>();
// single objects
private Class<? extends ChangeLogPrepare> changeLogPrepareClass;
@@ -169,6 +172,10 @@ public class BootupClasses implements Predicate<Class<?>> {
add(startupInstances, serverConfigStartupInstances, serverConfigStartupCandidates);
}
public void addCustomDeployParser(List<CustomDeployParser> customDeployParser) {
add(customDeployParser, customDeployParserInstances, customDeployParserCandidates);
}
public void addChangeLogInstances(DatabaseConfig config) {
readAuditPrepare = config.getReadAuditPrepare();
readAuditLogger = config.getReadAuditLogger();
@@ -285,6 +292,10 @@ public class BootupClasses implements Predicate<Class<?>> {
return createAdd(beanQueryAdapterInstances, beanQueryAdapterCandidates);
}
public List<CustomDeployParser> getCustomDeployParsers() {
return createAdd(customDeployParserInstances, customDeployParserCandidates);
}
/**
* Return the list of Embeddable classes.
*/
@@ -404,6 +415,11 @@ public class BootupClasses implements Predicate<Class<?>> {
interesting = true;
}
if (CustomDeployParser.class.isAssignableFrom(cls)) {
customDeployParserCandidates.add((Class<? extends CustomDeployParser>) cls);
interesting = true;
}
// single instances, last assigned wins
if (ChangeLogListener.class.isAssignableFrom(cls)) {
changeLogListenerClass = (Class<? extends ChangeLogListener>) cls;
@@ -74,6 +74,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
private final BeanFinderManager beanFinderManager;
private final PersistListenerManager persistListenerManager;
private final BeanQueryAdapterManager beanQueryAdapterManager;
private final CustomDeployParserManager customDeployParserManager;
private final NamingConvention namingConvention;
private final DeployCreateProperties createProperties;
private final BeanManagerFactory beanManagerFactory;
@@ -154,6 +155,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
this.persistListenerManager = new PersistListenerManager(bootupClasses);
this.beanQueryAdapterManager = new BeanQueryAdapterManager(bootupClasses);
this.beanFinderManager = new BeanFinderManager(bootupClasses);
this.customDeployParserManager = new CustomDeployParserManager(bootupClasses);
this.transientProperties = new TransientProperties();
this.changeLogPrepare = config.changeLogPrepare(bootupClasses.getChangeLogPrepare());
this.changeLogListener = config.changeLogListener(bootupClasses.getChangeLogListener());
@@ -301,6 +303,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
readEntityBeanTable();
readEntityDeploymentAssociations();
readInheritedIdGenerators();
deployInfoMap.values().forEach(customDeployParserManager::parse);
// creates the BeanDescriptors
readEntityRelationships();
List<BeanDescriptor<?>> list = new ArrayList<>(descMap.values());
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.deploy;
import java.util.List;
import io.ebean.plugin.CustomDeployParser;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
public class CustomDeployParserManager {
private final List<CustomDeployParser> parsers;
public CustomDeployParserManager(BootupClasses bootupClasses) {
parsers = bootupClasses.getCustomDeployParsers();
}
public void parse(DeployBeanInfo<?> value) {
for (CustomDeployParser parser : parsers) {
parser.parse(value.getDescriptor(), value.getUtil().getDbPlatform());
}
}
}
@@ -16,6 +16,7 @@ import io.ebean.event.BeanPostConstructListener;
import io.ebean.event.BeanPostLoad;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.plugin.DeployBeanDescriptorMeta;
import io.ebean.text.PathProperties;
import io.ebean.util.AnnotationUtil;
import io.ebean.util.SplitName;
@@ -55,7 +56,7 @@ import java.util.Set;
/**
* Describes Beans including their deployment information.
*/
public class DeployBeanDescriptor<T> {
public class DeployBeanDescriptor<T> implements DeployBeanDescriptorMeta {
private static final Map<String, String> EMPTY_NAMED_QUERY = new HashMap<>();
@@ -199,7 +200,7 @@ public class DeployBeanDescriptor<T> {
/**
* Return the DeployBeanInfo for the given bean class.
*/
DeployBeanInfo<?> getDeploy(Class<?> cls) {
public DeployBeanInfo<?> getDeploy(Class<?> cls) {
return manager.deploy(cls);
}
@@ -619,6 +620,7 @@ public class DeployBeanDescriptor<T> {
* Return the base table. Only properties mapped to the base table are by
* default persisted.
*/
@Override
public String getBaseTable() {
return baseTable;
}
@@ -696,6 +698,7 @@ public class DeployBeanDescriptor<T> {
/**
* Get a BeanProperty by its name.
*/
@Override
public DeployBeanProperty getBeanProperty(String propName) {
return propMap.get(propName);
}
@@ -815,6 +818,7 @@ public class DeployBeanDescriptor<T> {
/**
* Return a collection of all BeanProperty deployment information.
*/
@Override
public Collection<DeployBeanProperty> propertiesAll() {
return propMap.values();
}
@@ -884,6 +888,7 @@ public class DeployBeanDescriptor<T> {
/**
* Return the BeanProperty that is the Id.
*/
@Override
public DeployBeanProperty idProperty() {
if (idProperty != null) {
return idProperty;
@@ -1180,4 +1185,14 @@ public class DeployBeanDescriptor<T> {
}
return base;
}
@Override
public String getDiscriminatorColumn() {
return inheritInfo == null ? null : inheritInfo.getDiscriminatorColumn();
}
@Override
public DeployBeanDescriptorMeta getDeployBeanDescriptorMeta(Class<?> propertyType) {
return getDeploy(propertyType).getDescriptor();
}
}
@@ -1,11 +1,25 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.annotation.*;
import io.ebean.annotation.CreatedTimestamp;
import io.ebean.annotation.DocCode;
import io.ebean.annotation.DocProperty;
import io.ebean.annotation.DocSortable;
import io.ebean.annotation.Formula;
import io.ebean.annotation.MutationDetection;
import io.ebean.annotation.Platform;
import io.ebean.annotation.SoftDelete;
import io.ebean.annotation.UpdatedTimestamp;
import io.ebean.annotation.WhenCreated;
import io.ebean.annotation.WhenModified;
import io.ebean.annotation.Where;
import io.ebean.annotation.WhoCreated;
import io.ebean.annotation.WhoModified;
import io.ebean.config.ScalarTypeConverter;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebean.core.type.ScalarType;
import io.ebean.plugin.DeployBeanPropertyMeta;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -35,7 +49,7 @@ import java.util.Set;
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class DeployBeanProperty {
public class DeployBeanProperty implements DeployBeanPropertyMeta {
private static final int ID_ORDER = 1000000;
private static final int UNIDIRECTIONAL_ORDER = 100000;
@@ -406,6 +420,7 @@ public class DeployBeanProperty {
this.owningType = owningType;
}
@Override
public Class<?> getOwningType() {
return owningType;
}
@@ -434,6 +449,7 @@ public class DeployBeanProperty {
/**
* Return the name of the property.
*/
@Override
public String getName() {
return name;
}
@@ -448,6 +464,7 @@ public class DeployBeanProperty {
/**
* Return the bean Field associated with this property.
*/
@Override
public Field getField() {
return field;
}
@@ -551,6 +568,7 @@ public class DeployBeanProperty {
/**
* The property is based on a formula.
*/
@Override
public void setSqlFormula(String formulaSelect, String formulaJoin) {
this.sqlFormulaSelect = formulaSelect;
this.sqlFormulaJoin = formulaJoin.isEmpty() ? null : formulaJoin;
@@ -654,6 +672,7 @@ public class DeployBeanProperty {
/**
* The database column name this is mapped to.
*/
@Override
public String getDbColumn() {
if (sqlFormulaSelect != null) {
return sqlFormulaSelect;
@@ -847,6 +866,7 @@ public class DeployBeanProperty {
/**
* Return the property type.
*/
@Override
public Class<?> getPropertyType() {
return propertyType;
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.plugin.DeployBeanPropertyAssocMeta;
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
@@ -7,7 +8,7 @@ import io.ebeaninternal.server.deploy.PropertyForeignKey;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
*/
public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty implements DeployBeanPropertyAssocMeta {
/**
* The type of the joined bean.
@@ -129,6 +130,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
* this 'master' bean.
* </p>
*/
@Override
public String getMappedBy() {
return mappedBy;
}
@@ -173,4 +175,9 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
public void setTargetType(Class<?> targetType) {
this.targetType = (Class<T>)targetType;
}
@Override
public String getBaseTable() {
return getBeanTable().getBaseTable();
}
}
@@ -0,0 +1,61 @@
package org.tests.model.tevent;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import io.ebean.annotation.Formula;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.plugin.CustomDeployParser;
import io.ebean.plugin.DeployBeanDescriptorMeta;
import io.ebean.plugin.DeployBeanPropertyMeta;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
/**
* Custom Annotation parser which parses &#64;Count annotation
*
* @author Roland Praml, FOCONIS AG
*/
public class CustomFormulaAnnotationParser implements CustomDeployParser {
private int counter;
@Target(FIELD)
@Retention(RUNTIME)
@Formula(select="TODO", join = "TODO") // meta-formula
public @interface Count {
String value();
}
@Override
public void parse(final DeployBeanDescriptorMeta descriptor, final DatabasePlatform databasePlatform) {
for (DeployBeanPropertyMeta prop : descriptor.propertiesAll()) {
readField(descriptor, prop);
}
}
private void readField(DeployBeanDescriptorMeta descriptor, DeployBeanPropertyMeta prop) {
Count countAnnot = AnnotationUtil.get(prop.getField(), Count.class);
if (countAnnot != null) {
// @Count found, so build the (complex) count formula
DeployBeanPropertyAssocMany<?> countProp = (DeployBeanPropertyAssocMany<?>) descriptor.getBeanProperty(countAnnot.value());
counter++;
String tmpTable = "f"+counter;
String sqlSelect = "coalesce(" + tmpTable + ".child_count, 0)";
String parentId = countProp.getMappedBy() + "_id";
String tableName = countProp.getBeanTable().getBaseTable();
String sqlJoin = "left join (select " + parentId +", count(*) as child_count from " + tableName + " GROUP BY " + parentId + " )"
+ " " + tmpTable + " on " + tmpTable + "." +parentId + " = ${ta}." + descriptor.idProperty().getDbColumn();
prop.setSqlFormula(sqlSelect, sqlJoin);
// prop.setSqlFormula("f1.child_count",
// "join (select parent_id, count(*) as child_count from child_entity GROUP BY parent_id) f1 on f1.parent_id = ${ta}.id");
}
}
}
@@ -41,6 +41,11 @@ public class TEventOne {
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
List<TEventMany> logs;
@CustomFormulaAnnotationParser.Count("logs")
//@Formula(select = "f1.child_count",
//join = "left join (select event_id, count(*) as child_count from tevent_many GROUP BY event_id ) as f1 on f1.event_id = ${ta}.id")
Long customFormula;
public TEventOne(String name, Status status) {
this.name = name;
this.status = status;
@@ -63,6 +68,10 @@ public class TEventOne {
return count;
}
public Long getCustomFormula() {
return customFormula;
}
public Double getTotalUnits() {
return totalUnits;
}
@@ -48,7 +48,7 @@ public class TestAggregationCount extends BaseTestCase {
List<TEventOne> list = query.findList();
String sql = sqlOf(query, 5);
assertThat(sql).contains("select t0.id, t0.name, t0.status, t0.version, t0.event_id from tevent_one t0");
assertThat(sql).contains("select t0.id, t0.name, t0.status, coalesce(f1.child_count, 0), t0.version, t0.event_id from tevent_one t0");
for (TEventOne eventOne : list) {
// lazy loading on Aggregation properties