feat: 暂存
This commit is contained in:
parent
20beef41a4
commit
95e5b1800a
@ -21,4 +21,11 @@ public abstract class DynamicClassLoader extends URLClassLoader {
|
||||
* @return 结果
|
||||
*/
|
||||
public abstract Map<String, byte[]> getClassBytes();
|
||||
|
||||
/**
|
||||
* 将指定的类二进制替换为其他类
|
||||
*
|
||||
* @param path 目标类路径
|
||||
*/
|
||||
public abstract void replace(String name, String path);
|
||||
}
|
||||
|
@ -1,7 +1,13 @@
|
||||
package com.flyfish.framework.compiler.core;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -30,6 +36,21 @@ class MemoryClassLoader extends DynamicClassLoader {
|
||||
return classBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replace(String name, String path) {
|
||||
String actualPath = "classpath:" + path;
|
||||
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
try {
|
||||
Resource[] resources = resolver.getResources(actualPath);
|
||||
if (ArrayUtils.isNotEmpty(resources)) {
|
||||
byte[] content = StreamUtils.copyToByteArray(resources[0].getInputStream());
|
||||
classBytes.put(name, content);
|
||||
defineClass(name, content);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复获取package为null的问题,定义并验证class
|
||||
*
|
||||
|
@ -2,9 +2,9 @@ package com.flyfish.framework.r2dbc.config;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.r2dbc.config.callback.ReferenceR2dbcCallback;
|
||||
import com.flyfish.framework.r2dbc.config.convert.AdditionalConverters;
|
||||
import com.flyfish.framework.r2dbc.config.convert.FullMappingR2dbcConverter;
|
||||
import com.flyfish.framework.r2dbc.config.convert.FullReactiveDataAccessStrategy;
|
||||
import com.flyfish.framework.r2dbc.config.mapping.AdditionalConverters;
|
||||
import com.flyfish.framework.r2dbc.config.mapping.FullMappingR2dbcConverter;
|
||||
import com.flyfish.framework.r2dbc.config.mapping.FullReactiveDataAccessStrategy;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager;
|
||||
import com.flyfish.framework.r2dbc.metadata.impl.SimpleR2dbcMetadataManager;
|
||||
import com.flyfish.framework.r2dbc.operations.R2dbcReactiveEntityOperations;
|
||||
|
@ -1,99 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config.convert;
|
||||
|
||||
import com.flyfish.framework.utils.JacksonUtil;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 全支持的数据访问策略,将数组扁平化转换,方便支持json
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class FullReactiveDataAccessStrategy extends DefaultReactiveDataAccessStrategy {
|
||||
|
||||
private final R2dbcConverter converter;
|
||||
|
||||
private final R2dbcDialect dialect;
|
||||
|
||||
/**
|
||||
* 最大化配置的构造器,仅提供该实现
|
||||
*
|
||||
* @param dialect 别名实现
|
||||
* @param converter 转换器
|
||||
*/
|
||||
public FullReactiveDataAccessStrategy(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
super(dialect, converter);
|
||||
this.converter = converter;
|
||||
this.dialect = dialect;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public OutboundRow getOutboundRow(@NonNull Object object) {
|
||||
Assert.notNull(object, "Entity object must not be null");
|
||||
|
||||
OutboundRow row = new OutboundRow();
|
||||
|
||||
converter.write(object, row);
|
||||
|
||||
RelationalPersistentEntity<?> entity = getRequiredPersistentEntity(ClassUtils.getUserClass(object));
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
Parameter value = row.get(property.getColumnName());
|
||||
if (value != null && shouldConvertArrayValue(property, value)) {
|
||||
Parameter writeValue = getArrayValue(value);
|
||||
row.put(property.getColumnName(), writeValue);
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private Parameter getArrayValue(Parameter value) {
|
||||
if (value.getType().equals(byte[].class)) {
|
||||
return value;
|
||||
}
|
||||
return Parameter.fromOrEmpty(JacksonUtil.toJson(value.getValue()).orElse(null), String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要转换数据值
|
||||
*
|
||||
* @param property 属性
|
||||
* @param value 值表示
|
||||
* @return 结果
|
||||
*/
|
||||
private boolean shouldConvertArrayValue(RelationalPersistentProperty property, Parameter value) {
|
||||
|
||||
if (!property.isCollectionLike()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private RelationalPersistentEntity<?> getRequiredPersistentEntity(Class<?> typeToRead) {
|
||||
return converter.getMappingContext().getRequiredPersistentEntity(typeToRead);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.flyfish.framework.r2dbc.config.convert;
|
||||
package com.flyfish.framework.r2dbc.config.mapping;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
@ -1,4 +1,4 @@
|
||||
package com.flyfish.framework.r2dbc.config.convert;
|
||||
package com.flyfish.framework.r2dbc.config.mapping;
|
||||
|
||||
import com.flyfish.framework.utils.JacksonUtil;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
@ -9,7 +9,6 @@ import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
@ -0,0 +1,180 @@
|
||||
package com.flyfish.framework.r2dbc.config.mapping;
|
||||
|
||||
import com.flyfish.framework.utils.JacksonUtil;
|
||||
import io.r2dbc.spi.Readable;
|
||||
import io.r2dbc.spi.ReadableMetadata;
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.core.PredefinedStatementMapper;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.core.StatementMapper;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.query.FullUpdateMapper;
|
||||
import org.springframework.data.r2dbc.query.UpdateMapper;
|
||||
import org.springframework.data.relational.core.dialect.RenderContextFactory;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.domain.RowDocument;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* 全支持的数据访问策略,将数组扁平化转换,方便支持json
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class FullReactiveDataAccessStrategy implements ReactiveDataAccessStrategy {
|
||||
|
||||
private final DefaultReactiveDataAccessStrategy delegated;
|
||||
|
||||
private final R2dbcConverter converter;
|
||||
|
||||
private final UpdateMapper updateMapper;
|
||||
|
||||
private final StatementMapper statementMapper;
|
||||
|
||||
/**
|
||||
* 最大化配置的构造器,仅提供该实现
|
||||
*
|
||||
* @param dialect 别名实现
|
||||
* @param converter 转换器
|
||||
*/
|
||||
public FullReactiveDataAccessStrategy(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
Assert.notNull(dialect, "Dialect must not be null");
|
||||
Assert.notNull(converter, "RelationalConverter must not be null");
|
||||
this.delegated = new DefaultReactiveDataAccessStrategy(dialect, converter);
|
||||
|
||||
this.converter = converter;
|
||||
this.updateMapper = new FullUpdateMapper(dialect, converter);
|
||||
RenderContextFactory factory = new RenderContextFactory(dialect);
|
||||
this.statementMapper = new PredefinedStatementMapper(dialect, factory.createRenderContext(), this.updateMapper,
|
||||
delegated.getMappingContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<SqlIdentifier> getAllColumns(@NonNull Class<?> entityType) {
|
||||
return delegated.getAllColumns(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<SqlIdentifier> getIdentifierColumns(@NonNull Class<?> entityType) {
|
||||
return delegated.getIdentifierColumns(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public OutboundRow getOutboundRow(@NonNull Object object) {
|
||||
Assert.notNull(object, "Entity object must not be null");
|
||||
|
||||
OutboundRow row = new OutboundRow();
|
||||
|
||||
converter.write(object, row);
|
||||
|
||||
RelationalPersistentEntity<?> entity = getRequiredPersistentEntity(ClassUtils.getUserClass(object));
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
Parameter value = row.get(property.getColumnName());
|
||||
if (value != null && shouldConvertArrayValue(property, value)) {
|
||||
Parameter writeValue = getArrayValue(value);
|
||||
row.put(property.getColumnName(), writeValue);
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Parameter getBindValue(@NonNull Parameter value) {
|
||||
return this.updateMapper.getBindValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public <T> BiFunction<Row, RowMetadata, T> getRowMapper(@NonNull Class<T> typeToRead) {
|
||||
return delegated.getRowMapper(typeToRead);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public RowDocument toRowDocument(@NonNull Class<?> type, @NonNull Readable row, @NonNull Iterable<? extends ReadableMetadata> metadata) {
|
||||
return delegated.toRowDocument(type, row, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public SqlIdentifier getTableName(@NonNull Class<?> type) {
|
||||
return delegated.getTableName(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public PreparedOperation<?> processNamedParameters(@NonNull String query, @NonNull NamedParameterProvider parameterProvider) {
|
||||
return delegated.processNamedParameters(query, parameterProvider);
|
||||
}
|
||||
|
||||
private Parameter getArrayValue(Parameter value) {
|
||||
if (value.getType().equals(byte[].class)) {
|
||||
return value;
|
||||
}
|
||||
return Parameter.fromOrEmpty(JacksonUtil.toJson(value.getValue()).orElse(null), String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toSql(@NonNull SqlIdentifier identifier) {
|
||||
return this.updateMapper.toSql(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public StatementMapper getStatementMapper() {
|
||||
return this.statementMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public R2dbcConverter getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要转换数据值
|
||||
*
|
||||
* @param property 属性
|
||||
* @param value 值表示
|
||||
* @return 结果
|
||||
*/
|
||||
private boolean shouldConvertArrayValue(RelationalPersistentProperty property, Parameter value) {
|
||||
if (!property.isCollectionLike()) {
|
||||
return false;
|
||||
}
|
||||
if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) {
|
||||
return true;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private RelationalPersistentEntity<?> getRequiredPersistentEntity(Class<?> typeToRead) {
|
||||
return converter.getMappingContext().getRequiredPersistentEntity(typeToRead);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package com.flyfish.framework.r2dbc.query.adaptor;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 函数查询
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Getter
|
||||
public class FunctionCriteria implements CriteriaDefinition {
|
||||
|
||||
private final CriteriaDefinition previous;
|
||||
|
||||
private final Combinator combinator;
|
||||
|
||||
private final SqlIdentifier column;
|
||||
|
||||
private final String functionName;
|
||||
|
||||
private final List<Object> values;
|
||||
|
||||
private FunctionCriteria(SqlIdentifier column, String functionName, Object... values) {
|
||||
this(null, null, column, functionName, values);
|
||||
}
|
||||
|
||||
private FunctionCriteria(@Nullable CriteriaDefinition previous, @Nullable Combinator combinator,
|
||||
SqlIdentifier column, String functionName, Object... values) {
|
||||
this.previous = previous;
|
||||
this.combinator = combinator;
|
||||
this.column = column;
|
||||
this.functionName = functionName;
|
||||
this.values = Arrays.asList(values);
|
||||
}
|
||||
|
||||
public static FunctionStep where(String column) {
|
||||
return new DefaultFunctionStep(column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGroup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<CriteriaDefinition> getGroup() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator getComparator() {
|
||||
return Comparator.INITIAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoreCase() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return null != previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Criteria and() {
|
||||
return Criteria.empty().and(this);
|
||||
}
|
||||
|
||||
public Criteria or() {
|
||||
return Criteria.empty().or(this);
|
||||
}
|
||||
|
||||
public FunctionStep and(String column) {
|
||||
return new DefaultFunctionStep(column, this, Combinator.AND);
|
||||
}
|
||||
|
||||
public FunctionStep or(String column) {
|
||||
return new DefaultFunctionStep(column, this, Combinator.OR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主要操作步骤,支持若干函数
|
||||
*/
|
||||
public interface FunctionStep {
|
||||
|
||||
/**
|
||||
* json包含,mysql特有
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
FunctionCriteria jsonContains(Object value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的查询链实现
|
||||
*/
|
||||
private static class DefaultFunctionStep implements FunctionStep {
|
||||
|
||||
private final CriteriaDefinition previous;
|
||||
|
||||
private final SqlIdentifier column;
|
||||
|
||||
private final Combinator combinator;
|
||||
|
||||
private DefaultFunctionStep(String column) {
|
||||
this(column, Criteria.empty(), Combinator.INITIAL);
|
||||
}
|
||||
|
||||
private DefaultFunctionStep(String column, CriteriaDefinition previous, Combinator combinator) {
|
||||
this.column = SqlIdentifier.quoted(column);
|
||||
this.previous = previous;
|
||||
this.combinator = combinator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCriteria jsonContains(Object value) {
|
||||
return create(column, "JSON_CONTAINS", value);
|
||||
}
|
||||
|
||||
private FunctionCriteria create(SqlIdentifier column, String functionName, Object... values) {
|
||||
return new FunctionCriteria(previous, combinator, column, functionName, values);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,14 +4,8 @@ import com.flyfish.framework.query.Queries;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.sql.Expression;
|
||||
import org.springframework.data.relational.core.sql.Functions;
|
||||
import org.springframework.data.relational.core.sql.SimpleFunction;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -43,12 +37,16 @@ public class R2dbcCriteriaAdaptor implements CriteriaAdaptor<Criteria> {
|
||||
|
||||
@Override
|
||||
public Criteria hasId(String column, Object value) {
|
||||
return Criteria.where(String.format("JSON_CONTAINS(%s, %s)", column, value)).isTrue();
|
||||
return FunctionCriteria.where(column)
|
||||
.jsonContains(value)
|
||||
.and();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria has(String column, Object value) {
|
||||
return Criteria.where(String.format("JSON_CONTAINS(%s, %s)", column, value)).isTrue();
|
||||
return FunctionCriteria.where(column)
|
||||
.jsonContains(value)
|
||||
.and();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.springframework.data.r2dbc.core;
|
||||
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.query.UpdateMapper;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.sql.render.RenderContext;
|
||||
|
||||
/**
|
||||
* 创建适配类,暴露构造器方法
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class PredefinedStatementMapper extends DefaultStatementMapper {
|
||||
|
||||
public PredefinedStatementMapper(R2dbcDialect dialect, RenderContext renderContext, UpdateMapper updateMapper,
|
||||
MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext) {
|
||||
super(dialect, renderContext, updateMapper, mappingContext);
|
||||
}
|
||||
}
|
@ -0,0 +1,408 @@
|
||||
package org.springframework.data.r2dbc.query;
|
||||
|
||||
import com.flyfish.framework.r2dbc.query.adaptor.FunctionCriteria;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.relational.core.dialect.Escaper;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.ValueFunction;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.binding.BindMarker;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkers;
|
||||
import org.springframework.r2dbc.core.binding.MutableBindings;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 全量支持的mapper
|
||||
*
|
||||
* @author wangyu
|
||||
* 低级重写,后续官方支持后删除
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class FullUpdateMapper extends UpdateMapper {
|
||||
|
||||
private final R2dbcDialect dialect;
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> mappingContext;
|
||||
|
||||
|
||||
public FullUpdateMapper(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
super(dialect, converter);
|
||||
this.dialect = dialect;
|
||||
this.mappingContext = (MappingContext) converter.getMappingContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写此方法,以允许使用自定义函数
|
||||
*
|
||||
* @param markers 绑定标记对象,必须非空
|
||||
* @param criteria 需映射的条件定义,必须非空
|
||||
* @param table 必须非空
|
||||
* @param entity 相关的 {@link RelationalPersistentEntity},可以为空
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public BoundCondition getMappedObject(@NonNull BindMarkers markers, @NonNull CriteriaDefinition criteria, @NonNull Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
Assert.notNull(markers, "BindMarkers must not be null");
|
||||
Assert.notNull(criteria, "CriteriaDefinition must not be null");
|
||||
Assert.notNull(table, "Table must not be null");
|
||||
|
||||
MutableBindings bindings = new MutableBindings(markers);
|
||||
if (criteria.isEmpty()) {
|
||||
throw new IllegalArgumentException("Cannot map empty Criteria");
|
||||
}
|
||||
Condition mapped = unroll(criteria, table, entity, bindings);
|
||||
return new BoundCondition(bindings, mapped);
|
||||
}
|
||||
|
||||
private Condition unroll(CriteriaDefinition criteria, Table table, @Nullable RelationalPersistentEntity<?> entity,
|
||||
MutableBindings bindings) {
|
||||
|
||||
CriteriaDefinition current = criteria;
|
||||
|
||||
// reverse unroll criteria chain
|
||||
Map<CriteriaDefinition, CriteriaDefinition> forwardChain = new HashMap<>();
|
||||
|
||||
while (current.hasPrevious()) {
|
||||
forwardChain.put(current.getPrevious(), current);
|
||||
current = current.getPrevious();
|
||||
}
|
||||
|
||||
// perform the actual mapping
|
||||
Condition mapped = getCondition(current, bindings, table, entity);
|
||||
while (forwardChain.containsKey(current)) {
|
||||
|
||||
CriteriaDefinition criterion = forwardChain.get(current);
|
||||
Condition result = null;
|
||||
|
||||
Condition condition = getCondition(criterion, bindings, table, entity);
|
||||
if (condition != null) {
|
||||
result = combine(criterion, mapped, criterion.getCombinator(), condition);
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
mapped = result;
|
||||
}
|
||||
current = criterion;
|
||||
}
|
||||
|
||||
if (mapped == null) {
|
||||
throw new IllegalStateException("Cannot map empty Criteria");
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Condition unrollGroup(List<? extends CriteriaDefinition> criteria, Table table,
|
||||
CriteriaDefinition.Combinator combinator, @Nullable RelationalPersistentEntity<?> entity,
|
||||
MutableBindings bindings) {
|
||||
|
||||
Condition mapped = null;
|
||||
for (CriteriaDefinition criterion : criteria) {
|
||||
|
||||
if (criterion.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Condition condition = unroll(criterion, table, entity, bindings);
|
||||
|
||||
mapped = combine(criterion, mapped, combinator, condition);
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
|
||||
private Condition combine(CriteriaDefinition criteria, @Nullable Condition currentCondition,
|
||||
CriteriaDefinition.Combinator combinator, Condition nextCondition) {
|
||||
|
||||
if (currentCondition == null) {
|
||||
currentCondition = nextCondition;
|
||||
} else if (combinator == CriteriaDefinition.Combinator.INITIAL) {
|
||||
currentCondition = currentCondition.and(Conditions.nest(nextCondition));
|
||||
} else if (combinator == CriteriaDefinition.Combinator.AND) {
|
||||
currentCondition = currentCondition.and(nextCondition);
|
||||
} else if (combinator == CriteriaDefinition.Combinator.OR) {
|
||||
currentCondition = currentCondition.or(nextCondition);
|
||||
} else {
|
||||
throw new IllegalStateException("Combinator " + combinator + " not supported");
|
||||
}
|
||||
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Condition getCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
if (criteria.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (criteria.isGroup()) {
|
||||
|
||||
Condition condition = unrollGroup(criteria.getGroup(), table, criteria.getCombinator(), entity, bindings);
|
||||
|
||||
return condition == null ? null : Conditions.nest(condition);
|
||||
}
|
||||
|
||||
return mapCondition(criteria, bindings, table, entity);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private Object convertValue(CriteriaDefinition.Comparator comparator, @Nullable Object value, TypeInformation<?> typeHint) {
|
||||
|
||||
if ((CriteriaDefinition.Comparator.IN.equals(comparator) || CriteriaDefinition.Comparator.NOT_IN.equals(comparator))
|
||||
&& value instanceof Collection<?> collection && !collection.isEmpty()) {
|
||||
|
||||
Collection<Object> mapped = new ArrayList<>(collection.size());
|
||||
|
||||
for (Object o : collection) {
|
||||
mapped.add(convertValue(o, typeHint));
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
return convertValue(value, typeHint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心改造方法,增加对方法标识的适配,剔除额外的column属性
|
||||
*
|
||||
* @param criteria 通用查询
|
||||
* @param bindings 绑定参数
|
||||
* @param table 表
|
||||
* @param entity 实体信息
|
||||
* @return 结果
|
||||
*/
|
||||
private Condition mapCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
Field propertyField = createPropertyField(entity, criteria.getColumn(), mappingContext);
|
||||
Column column = table.column(propertyField.getMappedColumnName());
|
||||
TypeInformation<?> actualType = propertyField.getTypeHint().getRequiredActualType();
|
||||
|
||||
Object mappedValue;
|
||||
Class<?> typeHint;
|
||||
|
||||
CriteriaDefinition.Comparator comparator = criteria.getComparator();
|
||||
if (criteria.getValue() instanceof Parameter parameter) {
|
||||
|
||||
mappedValue = convertValue(comparator, parameter.getValue(), propertyField.getTypeHint());
|
||||
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
|
||||
} else if (criteria.getValue() instanceof ValueFunction<?> valueFunction) {
|
||||
|
||||
mappedValue = valueFunction.map(v -> convertValue(comparator, v, propertyField.getTypeHint()))
|
||||
.apply(getEscaper(comparator));
|
||||
|
||||
typeHint = actualType.getType();
|
||||
} else {
|
||||
|
||||
mappedValue = convertValue(comparator, criteria.getValue(), propertyField.getTypeHint());
|
||||
typeHint = actualType.getType();
|
||||
}
|
||||
|
||||
// 特殊处理方法
|
||||
if (criteria instanceof FunctionCriteria functionCriteria) {
|
||||
return createFunction(column, mappedValue, typeHint, bindings, functionCriteria);
|
||||
}
|
||||
// 原处理方法
|
||||
return createCondition(column, mappedValue, typeHint, bindings, comparator, criteria.isIgnoreCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建函数引用
|
||||
*
|
||||
* @param column 引用的列
|
||||
* @param mappedValue 映射后的值
|
||||
* @param valueType 值类型
|
||||
* @param bindings 绑定上下文
|
||||
* @param criteria 查询信息
|
||||
* @return 结果
|
||||
*/
|
||||
private Condition createFunction(Column column, @Nullable Object mappedValue, Class<?> valueType,
|
||||
MutableBindings bindings, FunctionCriteria criteria) {
|
||||
// 处理多值情况
|
||||
if (mappedValue instanceof Iterable) {
|
||||
// 大小 + 1, 容纳列名
|
||||
List<Expression> expressions = new ArrayList<>(
|
||||
mappedValue instanceof Collection ? ((Collection<?>) mappedValue).size() + 1 : 10);
|
||||
expressions.add(column);
|
||||
|
||||
for (Object o : (Iterable<?>) mappedValue) {
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
expressions.add(bind(o, valueType, bindings, bindMarker));
|
||||
}
|
||||
return FunctionCondition.of(criteria.getFunctionName(), expressions);
|
||||
} else {
|
||||
// 单值绑定
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
|
||||
return FunctionCondition.of(criteria.getFunctionName(), column, expression);
|
||||
}
|
||||
}
|
||||
|
||||
private Condition createCondition(Column column, @Nullable Object mappedValue, Class<?> valueType,
|
||||
MutableBindings bindings, CriteriaDefinition.Comparator comparator, boolean ignoreCase) {
|
||||
|
||||
if (comparator.equals(CriteriaDefinition.Comparator.IS_NULL)) {
|
||||
return column.isNull();
|
||||
}
|
||||
|
||||
if (comparator.equals(CriteriaDefinition.Comparator.IS_NOT_NULL)) {
|
||||
return column.isNotNull();
|
||||
}
|
||||
|
||||
if (comparator == CriteriaDefinition.Comparator.IS_TRUE) {
|
||||
Expression bind = booleanBind(column, mappedValue, valueType, bindings, ignoreCase);
|
||||
|
||||
return column.isEqualTo(bind);
|
||||
}
|
||||
|
||||
if (comparator == CriteriaDefinition.Comparator.IS_FALSE) {
|
||||
Expression bind = booleanBind(column, mappedValue, valueType, bindings, ignoreCase);
|
||||
|
||||
return column.isEqualTo(bind);
|
||||
}
|
||||
|
||||
Expression columnExpression = column;
|
||||
if (ignoreCase) {
|
||||
columnExpression = Functions.upper(column);
|
||||
}
|
||||
|
||||
if (comparator == CriteriaDefinition.Comparator.NOT_IN || comparator == CriteriaDefinition.Comparator.IN) {
|
||||
|
||||
Condition condition;
|
||||
|
||||
if (mappedValue instanceof Iterable) {
|
||||
|
||||
List<Expression> expressions = new ArrayList<>(
|
||||
mappedValue instanceof Collection ? ((Collection<?>) mappedValue).size() : 10);
|
||||
|
||||
for (Object o : (Iterable<?>) mappedValue) {
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
expressions.add(bind(o, valueType, bindings, bindMarker));
|
||||
}
|
||||
|
||||
condition = Conditions.in(columnExpression, expressions.toArray(new Expression[0]));
|
||||
|
||||
} else {
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
|
||||
condition = Conditions.in(columnExpression, expression);
|
||||
}
|
||||
|
||||
if (comparator == CriteriaDefinition.Comparator.NOT_IN) {
|
||||
condition = condition.not();
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
if (comparator == CriteriaDefinition.Comparator.BETWEEN || comparator == CriteriaDefinition.Comparator.NOT_BETWEEN) {
|
||||
|
||||
Pair<Object, Object> pair = (Pair<Object, Object>) mappedValue;
|
||||
|
||||
Expression begin = bind(pair.getFirst(), valueType, bindings,
|
||||
bindings.nextMarker(column.getName().getReference()), ignoreCase);
|
||||
Expression end = bind(pair.getSecond(), valueType, bindings, bindings.nextMarker(column.getName().getReference()),
|
||||
ignoreCase);
|
||||
|
||||
return comparator == CriteriaDefinition.Comparator.BETWEEN ? Conditions.between(columnExpression, begin, end)
|
||||
: Conditions.notBetween(columnExpression, begin, end);
|
||||
}
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
|
||||
switch (comparator) {
|
||||
case EQ: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.isEqual(columnExpression, expression);
|
||||
}
|
||||
case NEQ: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.isEqual(columnExpression, expression).not();
|
||||
}
|
||||
case LT: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isLess(expression);
|
||||
}
|
||||
case LTE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isLessOrEqualTo(expression);
|
||||
}
|
||||
case GT: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isGreater(expression);
|
||||
}
|
||||
case GTE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isGreaterOrEqualTo(expression);
|
||||
}
|
||||
case LIKE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.like(columnExpression, expression);
|
||||
}
|
||||
case NOT_LIKE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.notLike(columnExpression, expression);
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException("Comparator " + comparator + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private Expression booleanBind(Column column, Object mappedValue, Class<?> valueType, MutableBindings bindings,
|
||||
boolean ignoreCase) {
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
|
||||
return bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
}
|
||||
|
||||
private Expression bind(@Nullable Object mappedValue, Class<?> valueType, MutableBindings bindings,
|
||||
BindMarker bindMarker) {
|
||||
return bind(mappedValue, valueType, bindings, bindMarker, false);
|
||||
}
|
||||
|
||||
private Expression bind(@Nullable Object mappedValue, Class<?> valueType, MutableBindings bindings,
|
||||
BindMarker bindMarker, boolean ignoreCase) {
|
||||
|
||||
if (mappedValue != null) {
|
||||
bindings.bind(bindMarker, mappedValue);
|
||||
} else {
|
||||
bindings.bindNull(bindMarker, valueType);
|
||||
}
|
||||
|
||||
return ignoreCase ? Functions.upper(SQL.bindMarker(bindMarker.getPlaceholder()))
|
||||
: SQL.bindMarker(bindMarker.getPlaceholder());
|
||||
}
|
||||
|
||||
|
||||
private Escaper getEscaper(CriteriaDefinition.Comparator comparator) {
|
||||
|
||||
if (comparator == CriteriaDefinition.Comparator.LIKE || comparator == CriteriaDefinition.Comparator.NOT_LIKE) {
|
||||
return dialect.getLikeEscaper();
|
||||
}
|
||||
|
||||
return Escaper.DEFAULT;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package org.springframework.data.relational.core.sql;
|
||||
|
||||
import org.springframework.data.relational.core.sql.render.FunctionVisitor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可以渲染函数的条件
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class FunctionCondition extends ConstantCondition implements Condition {
|
||||
|
||||
private final SimpleFunction function;
|
||||
|
||||
private FunctionCondition(SimpleFunction function) {
|
||||
super("");
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public static Condition of(String functionName, List<Expression> args) {
|
||||
return new FunctionCondition(SimpleFunction.create(functionName, args));
|
||||
}
|
||||
|
||||
public static Condition of(String functionName, Expression... args) {
|
||||
return of(functionName, Arrays.asList(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
FunctionVisitor visitor = new FunctionVisitor();
|
||||
return function.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2019-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.relational.core.sql.Visitor} delegating {@link Condition} rendering to condition
|
||||
* {@link org.springframework.data.relational.core.sql.Visitor}s.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Daniele Canteri
|
||||
* @see AndCondition
|
||||
* @see OrCondition
|
||||
* @see IsNull
|
||||
* @see Comparison
|
||||
* @see Like
|
||||
* @see In
|
||||
* @since 1.1
|
||||
*/
|
||||
class ConditionVisitor extends TypedSubtreeVisitor<Condition> implements PartRenderer {
|
||||
|
||||
private final RenderContext context;
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
|
||||
ConditionVisitor(RenderContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation enterMatched(Condition segment) {
|
||||
|
||||
DelegatingVisitor visitor = getDelegation(segment);
|
||||
|
||||
return visitor != null ? Delegation.delegateTo(visitor) : Delegation.retain();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DelegatingVisitor getDelegation(Condition segment) {
|
||||
|
||||
if (segment instanceof AndCondition) {
|
||||
return new MultiConcatConditionVisitor(context, (AndCondition) segment, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof OrCondition) {
|
||||
return new MultiConcatConditionVisitor(context, (OrCondition) segment, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof IsNull) {
|
||||
return new IsNullVisitor(context, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof Between) {
|
||||
return new BetweenVisitor((Between) segment, context, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof Comparison) {
|
||||
return new ComparisonVisitor(context, (Comparison) segment, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof Like) {
|
||||
return new LikeVisitor((Like) segment, context, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof In) {
|
||||
|
||||
if (((In) segment).hasExpressions()) {
|
||||
return new InVisitor(context, builder::append);
|
||||
} else {
|
||||
return new EmptyInVisitor(context, builder::append);
|
||||
}
|
||||
}
|
||||
|
||||
if (segment instanceof NestedCondition) {
|
||||
return new NestedConditionVisitor(context, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof ConstantCondition) {
|
||||
return new ConstantConditionVisitor(context, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof Not) {
|
||||
return new NotConditionVisitor(context, builder::append);
|
||||
}
|
||||
|
||||
if (segment instanceof FunctionCondition functionCondition) {
|
||||
return new FunctionVisitor(functionCondition, context, builder::append);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getRenderedPart() {
|
||||
return builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import org.springframework.data.relational.core.sql.FunctionCondition;
|
||||
import org.springframework.data.relational.core.sql.SimpleFunction;
|
||||
import org.springframework.data.relational.core.sql.Visitable;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class FunctionVisitor extends FilteredSubtreeVisitor {
|
||||
|
||||
private final RenderContext context;
|
||||
private final RenderTarget target;
|
||||
private @Nullable PartRenderer current;
|
||||
private final StringBuilder part = new StringBuilder();
|
||||
|
||||
public FunctionVisitor(FunctionCondition condition, RenderContext context, RenderTarget target) {
|
||||
super(it -> it == condition);
|
||||
this.context = context;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation enterNested(Visitable segment) {
|
||||
if (segment instanceof SimpleFunction) {
|
||||
SimpleFunctionVisitor visitor = new SimpleFunctionVisitor(context);
|
||||
current = visitor;
|
||||
return Delegation.delegateTo(visitor);
|
||||
}
|
||||
throw new IllegalStateException("Cannot provide visitor for " + segment);
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation leaveNested(Visitable segment) {
|
||||
if (current != null) {
|
||||
part.append(current.getRenderedPart());
|
||||
current = null;
|
||||
}
|
||||
return super.leaveNested(segment);
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation leaveMatched(Visitable segment) {
|
||||
target.onRendered(part);
|
||||
return super.leaveMatched(segment);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user