This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 1821958ecbd Refactor rule configuration reference validation (#39920)
1821958ecbd is described below
commit 1821958ecbd48f2c3495570e9c6d50fcdbd8f953
Author: Liang Zhang <[email protected]>
AuthorDate: Tue Sep 22 14:27:30 2026 +0800
Refactor rule configuration reference validation (#39920)
* Refactor WhereClauseShardingConditionEngine
* Refactor rule configuration reference validation
---
.../encrypt/config/EncryptRuleConfiguration.java | 5 +-
.../EncryptRuleConfigurationValidator.java | 60 -------
.../validator/ValidEncryptRuleConfiguration.java | 57 -------
.../config/EncryptRuleConfigurationTest.java | 9 +
.../mask/config/MaskRuleConfiguration.java | 4 +-
.../validator/MaskRuleConfigurationValidator.java | 49 ------
.../mask/config/MaskRuleConfigurationTest.java | 8 +
.../ReadwriteSplittingRuleConfiguration.java | 4 +-
...adwriteSplittingRuleConfigurationValidator.java | 47 -----
.../ValidReadwriteSplittingRuleConfiguration.java | 57 -------
.../ReadwriteSplittingRuleConfigurationTest.java | 13 ++
.../reference/ConfigurationReferenceExists.java | 36 +++-
.../ConfigurationReferenceExistsValidator.java | 133 +++++++++++++++
.../ConfigurationReferenceExistsValidatorTest.java | 190 +++++++++++++++++++++
14 files changed, 389 insertions(+), 283 deletions(-)
diff --git
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
b/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
index 71ad246c497..f25aaabdac8 100644
---
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
+++
b/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfiguration.java
@@ -21,10 +21,10 @@ import com.cedarsoftware.util.CaseInsensitiveSet;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import
org.apache.shardingsphere.encrypt.config.rule.EncryptTableRuleConfiguration;
-import
org.apache.shardingsphere.encrypt.config.validator.ValidEncryptRuleConfiguration;
import
org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
import
org.apache.shardingsphere.infra.config.rule.function.EnhancedRuleConfiguration;
import
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
+import
org.apache.shardingsphere.infra.config.rule.validator.constraint.reference.ConfigurationReferenceExists;
import
org.apache.shardingsphere.infra.config.rule.validator.constraint.spi.SPITypeExists;
import
org.apache.shardingsphere.infra.config.rule.validator.group.RuleConfigurationTypeValidationGroup;
@@ -40,7 +40,8 @@ import java.util.stream.Collectors;
*/
@RequiredArgsConstructor
@Getter
-@ValidEncryptRuleConfiguration(groups =
RuleConfigurationTypeValidationGroup.class)
+@ConfigurationReferenceExists(referencePaths =
{"tables.columns.cipher.encryptorName",
"tables.columns.assistedQuery.encryptorName",
"tables.columns.likeQuery.encryptorName"}, pool = "encryptors",
+ groups = RuleConfigurationTypeValidationGroup.class)
public final class EncryptRuleConfiguration implements
DatabaseRuleConfiguration, EnhancedRuleConfiguration {
@NotNull
diff --git
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/validator/EncryptRuleConfigurationValidator.java
b/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/validator/EncryptRuleConfigurationValidator.java
deleted file mode 100644
index 1f54d6aaa10..00000000000
---
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/validator/EncryptRuleConfigurationValidator.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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
- *
- * http://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.apache.shardingsphere.encrypt.config.validator;
-
-import org.apache.shardingsphere.encrypt.config.EncryptRuleConfiguration;
-import
org.apache.shardingsphere.encrypt.config.rule.EncryptColumnItemRuleConfiguration;
-import
org.apache.shardingsphere.encrypt.config.rule.EncryptColumnRuleConfiguration;
-import
org.apache.shardingsphere.encrypt.config.rule.EncryptTableRuleConfiguration;
-
-import javax.validation.ConstraintValidator;
-import javax.validation.ConstraintValidatorContext;
-
-/**
- * Encrypt rule configuration validator.
- */
-public final class EncryptRuleConfigurationValidator implements
ConstraintValidator<ValidEncryptRuleConfiguration, EncryptRuleConfiguration> {
-
- @Override
- public boolean isValid(final EncryptRuleConfiguration value, final
ConstraintValidatorContext context) {
- return value.getTables().stream().allMatch(each -> isTableValid(each,
value, context));
- }
-
- private boolean isTableValid(final EncryptTableRuleConfiguration
tableConfig, final EncryptRuleConfiguration ruleConfig, final
ConstraintValidatorContext context) {
- return tableConfig.getColumns().stream().allMatch(each ->
isColumnValid(each, ruleConfig, context));
- }
-
- private boolean isColumnValid(final EncryptColumnRuleConfiguration
columnConfig, final EncryptRuleConfiguration ruleConfig, final
ConstraintValidatorContext context) {
- if (!isEncryptorReferenceValid(columnConfig.getCipher(), ruleConfig,
context)) {
- return false;
- }
- if (columnConfig.getAssistedQuery().isPresent() &&
!isEncryptorReferenceValid(columnConfig.getAssistedQuery().get(), ruleConfig,
context)) {
- return false;
- }
- return !columnConfig.getLikeQuery().isPresent() ||
isEncryptorReferenceValid(columnConfig.getLikeQuery().get(), ruleConfig,
context);
- }
-
- private boolean isEncryptorReferenceValid(final
EncryptColumnItemRuleConfiguration columnItemConfig, final
EncryptRuleConfiguration ruleConfig, final ConstraintValidatorContext context) {
- if
(ruleConfig.getEncryptors().containsKey(columnItemConfig.getEncryptorName())) {
- return true;
- }
- context.disableDefaultConstraintViolation();
- context.buildConstraintViolationWithTemplate(String.format("references
unconfigured encryptor `%s`",
columnItemConfig.getEncryptorName())).addPropertyNode("tables").addConstraintViolation();
- return false;
- }
-}
diff --git
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/validator/ValidEncryptRuleConfiguration.java
b/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/validator/ValidEncryptRuleConfiguration.java
deleted file mode 100644
index aa87f2799ee..00000000000
---
a/features/encrypt/api/src/main/java/org/apache/shardingsphere/encrypt/config/validator/ValidEncryptRuleConfiguration.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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
- *
- * http://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.apache.shardingsphere.encrypt.config.validator;
-
-import javax.validation.Constraint;
-import javax.validation.Payload;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Valid encrypt rule configuration constraint.
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = EncryptRuleConfigurationValidator.class)
-@Documented
-public @interface ValidEncryptRuleConfiguration {
-
- /**
- * Get message.
- *
- * @return message
- */
- String message() default "contains invalid references";
-
- /**
- * Get groups.
- *
- * @return groups
- */
- Class<?>[] groups() default {};
-
- /**
- * Get payload.
- *
- * @return payload
- */
- Class<? extends Payload>[] payload() default {};
-}
diff --git
a/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
b/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
index 627b5e66f9c..2439035ab56 100644
---
a/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
+++
b/features/encrypt/api/src/test/java/org/apache/shardingsphere/encrypt/config/EncryptRuleConfigurationTest.java
@@ -36,6 +36,7 @@ import java.util.Properties;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -93,6 +94,14 @@ class EncryptRuleConfigurationTest {
assertThrows(InvalidRuleConfigurationException.class, () ->
RuleConfigurationValidator.validate(ruleConfig));
}
+ @Test
+ void assertUnconfiguredEncryptorViolation() {
+ EncryptRuleConfiguration ruleConfig = createRuleConfiguration(new
EncryptColumnRuleConfiguration(
+ "foo_col", new
EncryptColumnItemRuleConfiguration("foo_cipher", "bar_encryptor")));
+ InvalidRuleConfigurationException actual =
assertThrows(InvalidRuleConfigurationException.class, () ->
RuleConfigurationValidator.validate(ruleConfig));
+ assertThat(actual.getMessage(), containsString("Property `tables`
references unconfigured encryptors `bar_encryptor`."));
+ }
+
private static Stream<Arguments> invalidRuleConfigurationArguments() {
EncryptColumnRuleConfiguration invalidAssistedQueryName =
createValidColumnConfiguration();
invalidAssistedQueryName.setAssistedQuery(new
EncryptColumnItemRuleConfiguration("", "foo_encryptor"));
diff --git
a/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/MaskRuleConfiguration.java
b/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/MaskRuleConfiguration.java
index 436d4c64f33..6685ff93404 100644
---
a/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/MaskRuleConfiguration.java
+++
b/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/MaskRuleConfiguration.java
@@ -23,10 +23,10 @@ import lombok.RequiredArgsConstructor;
import
org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
import
org.apache.shardingsphere.infra.config.rule.function.EnhancedRuleConfiguration;
import
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
+import
org.apache.shardingsphere.infra.config.rule.validator.constraint.reference.ConfigurationReferenceExists;
import
org.apache.shardingsphere.infra.config.rule.validator.constraint.spi.SPITypeExists;
import
org.apache.shardingsphere.infra.config.rule.validator.group.RuleConfigurationTypeValidationGroup;
import org.apache.shardingsphere.mask.config.rule.MaskTableRuleConfiguration;
-import
org.apache.shardingsphere.mask.config.validator.ValidMaskRuleConfiguration;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@@ -40,7 +40,7 @@ import java.util.stream.Collectors;
*/
@RequiredArgsConstructor
@Getter
-@ValidMaskRuleConfiguration(groups =
RuleConfigurationTypeValidationGroup.class)
+@ConfigurationReferenceExists(referencePaths = "tables.columns.maskAlgorithm",
pool = "maskAlgorithms", groups = RuleConfigurationTypeValidationGroup.class)
public final class MaskRuleConfiguration implements DatabaseRuleConfiguration,
EnhancedRuleConfiguration {
@NotNull
diff --git
a/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/validator/MaskRuleConfigurationValidator.java
b/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/validator/MaskRuleConfigurationValidator.java
deleted file mode 100644
index ff30672097c..00000000000
---
a/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/validator/MaskRuleConfigurationValidator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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
- *
- * http://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.apache.shardingsphere.mask.config.validator;
-
-import org.apache.shardingsphere.mask.config.MaskRuleConfiguration;
-import org.apache.shardingsphere.mask.config.rule.MaskColumnRuleConfiguration;
-import org.apache.shardingsphere.mask.config.rule.MaskTableRuleConfiguration;
-
-import javax.validation.ConstraintValidator;
-import javax.validation.ConstraintValidatorContext;
-
-/**
- * Mask rule configuration validator.
- */
-public final class MaskRuleConfigurationValidator implements
ConstraintValidator<ValidMaskRuleConfiguration, MaskRuleConfiguration> {
-
- @Override
- public boolean isValid(final MaskRuleConfiguration value, final
ConstraintValidatorContext context) {
- return value.getTables().stream().allMatch(each -> isTableValid(each,
value, context));
- }
-
- private boolean isTableValid(final MaskTableRuleConfiguration tableConfig,
final MaskRuleConfiguration ruleConfig, final ConstraintValidatorContext
context) {
- return tableConfig.getColumns().stream().allMatch(each ->
isColumnValid(each, ruleConfig, context));
- }
-
- private boolean isColumnValid(final MaskColumnRuleConfiguration
columnConfig, final MaskRuleConfiguration ruleConfig, final
ConstraintValidatorContext context) {
- if
(ruleConfig.getMaskAlgorithms().containsKey(columnConfig.getMaskAlgorithm())) {
- return true;
- }
- context.disableDefaultConstraintViolation();
- context.buildConstraintViolationWithTemplate(String.format("references
unconfigured mask algorithm `%s`",
columnConfig.getMaskAlgorithm())).addPropertyNode("tables").addConstraintViolation();
- return false;
- }
-}
diff --git
a/features/mask/api/src/test/java/org/apache/shardingsphere/mask/config/MaskRuleConfigurationTest.java
b/features/mask/api/src/test/java/org/apache/shardingsphere/mask/config/MaskRuleConfigurationTest.java
index fba5178de20..cf1e999367d 100644
---
a/features/mask/api/src/test/java/org/apache/shardingsphere/mask/config/MaskRuleConfigurationTest.java
+++
b/features/mask/api/src/test/java/org/apache/shardingsphere/mask/config/MaskRuleConfigurationTest.java
@@ -34,6 +34,7 @@ import java.util.Properties;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -75,6 +76,13 @@ class MaskRuleConfigurationTest {
assertThrows(InvalidRuleConfigurationException.class, () ->
RuleConfigurationValidator.validate(ruleConfig));
}
+ @Test
+ void assertUnconfiguredMaskAlgorithmViolation() {
+ MaskRuleConfiguration ruleConfig = createRuleConfiguration(new
MaskColumnRuleConfiguration("foo_col", "bar_mask"));
+ InvalidRuleConfigurationException actual =
assertThrows(InvalidRuleConfigurationException.class, () ->
RuleConfigurationValidator.validate(ruleConfig));
+ assertThat(actual.getMessage(), containsString("Property `tables`
references unconfigured maskAlgorithms `bar_mask`."));
+ }
+
private static Stream<Arguments> invalidRuleConfigurationArguments() {
AlgorithmConfiguration algorithmConfig = new
AlgorithmConfiguration("FIXTURE", new Properties());
return Stream.of(
diff --git
a/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfiguration.java
b/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfiguration.java
index 1e573d01325..1dd16925812 100644
---
a/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfiguration.java
+++
b/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfiguration.java
@@ -22,10 +22,10 @@ import lombok.RequiredArgsConstructor;
import
org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
import
org.apache.shardingsphere.infra.config.rule.function.DistributedRuleConfiguration;
import
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
+import
org.apache.shardingsphere.infra.config.rule.validator.constraint.reference.ConfigurationReferenceExists;
import
org.apache.shardingsphere.infra.config.rule.validator.constraint.spi.SPITypeExists;
import
org.apache.shardingsphere.infra.config.rule.validator.group.RuleConfigurationTypeValidationGroup;
import
org.apache.shardingsphere.readwritesplitting.config.rule.ReadwriteSplittingDataSourceGroupRuleConfiguration;
-import
org.apache.shardingsphere.readwritesplitting.config.validator.ValidReadwriteSplittingRuleConfiguration;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@@ -38,7 +38,7 @@ import java.util.Map;
*/
@RequiredArgsConstructor
@Getter
-@ValidReadwriteSplittingRuleConfiguration(groups =
RuleConfigurationTypeValidationGroup.class)
+@ConfigurationReferenceExists(referencePaths =
"dataSourceGroups.loadBalancerName", pool = "loadBalancers", allowEmpty = true,
groups = RuleConfigurationTypeValidationGroup.class)
public final class ReadwriteSplittingRuleConfiguration implements
DatabaseRuleConfiguration, DistributedRuleConfiguration {
@NotNull
diff --git
a/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/validator/ReadwriteSplittingRuleConfigurationValidator.java
b/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/validator/ReadwriteSplittingRuleConfigurationValidator.java
deleted file mode 100644
index 1c732eb681a..00000000000
---
a/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/validator/ReadwriteSplittingRuleConfigurationValidator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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
- *
- * http://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.apache.shardingsphere.readwritesplitting.config.validator;
-
-import com.google.common.base.Strings;
-import
org.apache.shardingsphere.readwritesplitting.config.ReadwriteSplittingRuleConfiguration;
-import
org.apache.shardingsphere.readwritesplitting.config.rule.ReadwriteSplittingDataSourceGroupRuleConfiguration;
-
-import javax.validation.ConstraintValidator;
-import javax.validation.ConstraintValidatorContext;
-
-/**
- * Readwrite-splitting rule configuration validator.
- */
-public final class ReadwriteSplittingRuleConfigurationValidator implements
ConstraintValidator<ValidReadwriteSplittingRuleConfiguration,
ReadwriteSplittingRuleConfiguration> {
-
- @Override
- public boolean isValid(final ReadwriteSplittingRuleConfiguration value,
final ConstraintValidatorContext context) {
- return value.getDataSourceGroups().stream().allMatch(each ->
isLoadBalancerReferenceValid(each, value, context));
- }
-
- private boolean isLoadBalancerReferenceValid(final
ReadwriteSplittingDataSourceGroupRuleConfiguration dataSourceGroupConfig,
- final
ReadwriteSplittingRuleConfiguration ruleConfig, final
ConstraintValidatorContext context) {
- if (Strings.isNullOrEmpty(dataSourceGroupConfig.getLoadBalancerName())
||
ruleConfig.getLoadBalancers().containsKey(dataSourceGroupConfig.getLoadBalancerName()))
{
- return true;
- }
- context.disableDefaultConstraintViolation();
- context.buildConstraintViolationWithTemplate(String.format("references
unconfigured load balancer `%s`", dataSourceGroupConfig.getLoadBalancerName()))
- .addPropertyNode("dataSourceGroups").addConstraintViolation();
- return false;
- }
-}
diff --git
a/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/validator/ValidReadwriteSplittingRuleConfiguration.java
b/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/validator/ValidReadwriteSplittingRuleConfiguration.java
deleted file mode 100644
index b4cd57e365e..00000000000
---
a/features/readwrite-splitting/api/src/main/java/org/apache/shardingsphere/readwritesplitting/config/validator/ValidReadwriteSplittingRuleConfiguration.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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
- *
- * http://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.apache.shardingsphere.readwritesplitting.config.validator;
-
-import javax.validation.Constraint;
-import javax.validation.Payload;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Valid readwrite-splitting rule configuration constraint.
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = ReadwriteSplittingRuleConfigurationValidator.class)
-@Documented
-public @interface ValidReadwriteSplittingRuleConfiguration {
-
- /**
- * Get message.
- *
- * @return message
- */
- String message() default "contains invalid references";
-
- /**
- * Get groups.
- *
- * @return groups
- */
- Class<?>[] groups() default {};
-
- /**
- * Get payload.
- *
- * @return payload
- */
- Class<? extends Payload>[] payload() default {};
-}
diff --git
a/features/readwrite-splitting/api/src/test/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfigurationTest.java
b/features/readwrite-splitting/api/src/test/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfigurationTest.java
index ced5782afa7..74f058f6bbc 100644
---
a/features/readwrite-splitting/api/src/test/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfigurationTest.java
+++
b/features/readwrite-splitting/api/src/test/java/org/apache/shardingsphere/readwritesplitting/config/ReadwriteSplittingRuleConfigurationTest.java
@@ -22,6 +22,7 @@ import
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationVa
import
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
import
org.apache.shardingsphere.readwritesplitting.config.rule.ReadwriteSplittingDataSourceGroupRuleConfiguration;
import
org.apache.shardingsphere.readwritesplitting.transaction.TransactionalReadQueryStrategy;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -31,6 +32,8 @@ import java.util.Collections;
import java.util.Properties;
import java.util.stream.Stream;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -51,6 +54,8 @@ class ReadwriteSplittingRuleConfigurationTest {
new ReadwriteSplittingDataSourceGroupRuleConfiguration(
"foo_group", "foo_write",
Collections.singletonList("foo_read"), null)),
Collections.emptyMap())),
+ Arguments.of("Group with empty load balancer name",
createRuleConfiguration(new ReadwriteSplittingDataSourceGroupRuleConfiguration(
+ "foo_group", "foo_write",
Collections.singletonList("foo_read"), ""))),
Arguments.of("Complete configuration", new
ReadwriteSplittingRuleConfiguration(Collections.singleton(dataSourceGroupConfig),
Collections.singletonMap("foo_load_balancer", new
AlgorithmConfiguration("FIXTURE", new Properties())))));
}
@@ -61,6 +66,14 @@ class ReadwriteSplittingRuleConfigurationTest {
assertThrows(InvalidRuleConfigurationException.class, () ->
RuleConfigurationValidator.validate(ruleConfig));
}
+ @Test
+ void assertUnconfiguredLoadBalancerViolation() {
+ ReadwriteSplittingRuleConfiguration ruleConfig =
createRuleConfiguration(new ReadwriteSplittingDataSourceGroupRuleConfiguration(
+ "foo_group", "foo_write",
Collections.singletonList("foo_read"), "bar_load_balancer"));
+ InvalidRuleConfigurationException actual =
assertThrows(InvalidRuleConfigurationException.class, () ->
RuleConfigurationValidator.validate(ruleConfig));
+ assertThat(actual.getMessage(), containsString("Property
`dataSourceGroups` references unconfigured loadBalancers
`bar_load_balancer`."));
+ }
+
private static Stream<Arguments> invalidRuleConfigurationArguments() {
AlgorithmConfiguration loadBalancer = new
AlgorithmConfiguration("FIXTURE", new Properties());
return Stream.of(
diff --git
a/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/validator/ValidMaskRuleConfiguration.java
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExists.java
similarity index 55%
rename from
features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/validator/ValidMaskRuleConfiguration.java
rename to
infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExists.java
index e180304829a..82cd7bd5ca0 100644
---
a/features/mask/api/src/main/java/org/apache/shardingsphere/mask/config/validator/ValidMaskRuleConfiguration.java
+++
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExists.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.mask.config.validator;
+package
org.apache.shardingsphere.infra.config.rule.validator.constraint.reference;
import javax.validation.Constraint;
import javax.validation.Payload;
@@ -26,20 +26,42 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Valid mask rule configuration constraint.
+ * Constraint for names that reference configurations in a map on the same
rule configuration.
+ * Property paths use public getters. Collections are traversed, and optional
values are unwrapped.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = MaskRuleConfigurationValidator.class)
+@Constraint(validatedBy = ConfigurationReferenceExistsValidator.class)
@Documented
-public @interface ValidMaskRuleConfiguration {
+public @interface ConfigurationReferenceExists {
/**
- * Get message.
+ * Get reference property paths from the annotated object.
*
- * @return message
+ * @return reference property paths
*/
- String message() default "contains invalid references";
+ String[] referencePaths();
+
+ /**
+ * Get the name of the map property containing configured values.
+ *
+ * @return configuration pool property name
+ */
+ String pool();
+
+ /**
+ * Whether an empty reference name is allowed.
+ *
+ * @return whether an empty reference name is allowed
+ */
+ boolean allowEmpty() default false;
+
+ /**
+ * Get the violation message. Use {pool} for the configuration pool
property and {reference} for the missing name.
+ *
+ * @return violation message
+ */
+ String message() default "references unconfigured {pool} `{reference}`";
/**
* Get groups.
diff --git
a/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExistsValidator.java
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExistsValidator.java
new file mode 100644
index 00000000000..75c464e1567
--- /dev/null
+++
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExistsValidator.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.apache.shardingsphere.infra.config.rule.validator.constraint.reference;
+
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.ValidationException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Configuration reference exists validator.
+ */
+public final class ConfigurationReferenceExistsValidator implements
ConstraintValidator<ConfigurationReferenceExists, Object> {
+
+ private ReferencePathNode referencePaths;
+
+ private String poolProperty;
+
+ private boolean allowEmpty;
+
+ private String message;
+
+ @Override
+ public void initialize(final ConfigurationReferenceExists
constraintAnnotation) {
+ ShardingSpherePreconditions.checkState(0 !=
constraintAnnotation.referencePaths().length, () -> new ValidationException("At
least one configuration reference property is required."));
+ referencePaths = new ReferencePathNode();
+ for (String each : constraintAnnotation.referencePaths()) {
+ referencePaths.addPath(parseReferencePaths(each));
+ }
+ poolProperty = constraintAnnotation.pool();
+ ShardingSpherePreconditions.checkState(!poolProperty.isEmpty() &&
!poolProperty.contains("."),
+ () -> new ValidationException("Configuration pool must be a
property on the annotated object."));
+ allowEmpty = constraintAnnotation.allowEmpty();
+ message = constraintAnnotation.message();
+ }
+
+ private String[] parseReferencePaths(final String referencePath) {
+ String[] result = referencePath.split("\\.", -1);
+ for (String each : result) {
+ ShardingSpherePreconditions.checkNotEmpty(each, () -> new
ValidationException(String.format("Invalid configuration reference property
path `%s`.", referencePath)));
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isValid(final Object value, final
ConstraintValidatorContext context) {
+ Object pool = getProperty(value, poolProperty);
+ ShardingSpherePreconditions.checkState(null == pool || pool instanceof
Map, () -> new ValidationException(String.format("Configuration pool property
`%s` must be a map.", poolProperty)));
+ Map<?, ?> configurations = null == pool ? Collections.emptyMap() :
(Map<?, ?>) pool;
+ return areReferencesValid(value, referencePaths, configurations,
context, null, "");
+ }
+
+ private Object getProperty(final Object value, final String property) {
+ try {
+ Method getter = value.getClass().getMethod("get" +
Character.toUpperCase(property.charAt(0)) + property.substring(1));
+ return getter.invoke(value);
+ } catch (final ReflectiveOperationException ex) {
+ throw new ValidationException(String.format("Can not read property
`%s` from `%s`.", property, value.getClass().getName()), ex);
+ }
+ }
+
+ private boolean areReferencesValid(final Object value, final
ReferencePathNode node, final Map<?, ?> pool,
+ final ConstraintValidatorContext
context, final String rootProperty, final String path) {
+ if (null == value) {
+ return true;
+ }
+ if (value instanceof Optional) {
+ return areReferencesValid(((Optional<?>) value).orElse(null),
node, pool, context, rootProperty, path);
+ }
+ if (value instanceof Collection) {
+ return areCollectionReferencesValid((Collection<?>) value, node,
pool, context, rootProperty, path);
+ }
+ return (!node.pathEnd || isReferenceValid(value, pool, context,
rootProperty, path)) && areChildReferencesValid(value, node, pool, context,
rootProperty, path);
+ }
+
+ private boolean areCollectionReferencesValid(final Collection<?> values,
final ReferencePathNode node, final Map<?, ?> pool,
+ final
ConstraintValidatorContext context, final String rootProperty, final String
path) {
+ return values.stream().allMatch(each -> areReferencesValid(each, node,
pool, context, rootProperty, path));
+ }
+
+ private boolean isReferenceValid(final Object value, final Map<?, ?> pool,
final ConstraintValidatorContext context, final String rootProperty, final
String path) {
+ ShardingSpherePreconditions.checkState(value instanceof String, () ->
new ValidationException(String.format("Configuration reference property `%s`
must be a string.", path)));
+ if (allowEmpty && ((String) value).isEmpty() ||
pool.containsKey(value)) {
+ return true;
+ }
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(message.replace("{pool}",
poolProperty).replace("{reference}", (String)
value)).addPropertyNode(rootProperty).addConstraintViolation();
+ return false;
+ }
+
+ private boolean areChildReferencesValid(final Object value, final
ReferencePathNode node, final Map<?, ?> pool,
+ final ConstraintValidatorContext
context, final String rootProperty, final String path) {
+ return node.children.entrySet().stream().allMatch(each ->
areReferencesValid(
+ getProperty(value, each.getKey()), each.getValue(), pool,
context, null == rootProperty ? each.getKey() : rootProperty, path.isEmpty() ?
each.getKey() : path + "." + each.getKey()));
+ }
+
+ private static final class ReferencePathNode {
+
+ private final Map<String, ReferencePathNode> children = new
LinkedHashMap<>();
+
+ private boolean pathEnd;
+
+ private void addPath(final String[] properties) {
+ ReferencePathNode current = this;
+ for (String each : properties) {
+ current = current.children.computeIfAbsent(each, ignored ->
new ReferencePathNode());
+ }
+ current.pathEnd = true;
+ }
+ }
+}
diff --git
a/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExistsValidatorTest.java
b/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExistsValidatorTest.java
new file mode 100644
index 00000000000..931bf608e94
--- /dev/null
+++
b/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/constraint/reference/ConfigurationReferenceExistsValidatorTest.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.apache.shardingsphere.infra.config.rule.validator.constraint.reference;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.bval.jsr.ApacheValidationProvider;
+import org.junit.jupiter.api.Test;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.Validator;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ConfigurationReferenceExistsValidatorTest {
+
+ private static final Validator VALIDATOR =
Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory().getValidator();
+
+ @Test
+ void assertConfiguredDirectReference() {
+ assertTrue(VALIDATOR.validate(new DirectConfiguration("foo_provider",
Collections.singletonMap("foo_provider", "configured"))).isEmpty());
+ }
+
+ @Test
+ void assertUnconfiguredDirectReference() {
+ Set<ConstraintViolation<DirectConfiguration>> actual =
VALIDATOR.validate(new DirectConfiguration("bar_provider",
Collections.singletonMap("foo_provider", "configured")));
+ assertThat(actual.size(), is(1));
+ assertThat(actual.iterator().next().getPropertyPath().toString(),
is("providerName"));
+ assertThat(actual.iterator().next().getMessage(), is("references
unconfigured providers `bar_provider`"));
+ }
+
+ @Test
+ void assertUnconfiguredReferenceWithMissingPool() {
+ Set<ConstraintViolation<DirectConfiguration>> actual =
VALIDATOR.validate(new DirectConfiguration("bar_provider", null));
+ assertThat(actual.size(), is(1));
+ assertThat(actual.iterator().next().getMessage(), is("references
unconfigured providers `bar_provider`"));
+ }
+
+ @Test
+ void assertConfiguredNestedReference() {
+ NestedConfiguration config = new
NestedConfiguration(Collections.singleton(new Group(Collections.singleton(
+ new Item(new Reference("foo_algorithm"), null)))),
Collections.singletonMap("foo_algorithm", "configured"));
+ assertTrue(VALIDATOR.validate(config).isEmpty());
+ }
+
+ @Test
+ void assertFirstUnconfiguredNestedReference() {
+ NestedConfiguration config = new
NestedConfiguration(Collections.singleton(new Group(Collections.singleton(
+ new Item(new Reference("bar_algorithm"), new
Reference("baz_algorithm"))))), Collections.emptyMap());
+ Set<ConstraintViolation<NestedConfiguration>> actual =
VALIDATOR.validate(config);
+ assertThat(actual.size(), is(1));
+ assertThat(actual.iterator().next().getPropertyPath().toString(),
is("groups"));
+ assertThat(actual.iterator().next().getMessage(), is("references
unconfigured algorithms `bar_algorithm`"));
+ }
+
+ @Test
+ void assertReferencesValidatedPerSourceObject() {
+ Item first = new Item(new Reference("foo_algorithm"), new
Reference("first_missing"));
+ Item second = new Item(new Reference("second_missing"), null);
+ NestedConfiguration config = new
NestedConfiguration(Collections.singleton(new Group(Arrays.asList(first,
second))),
+ Collections.singletonMap("foo_algorithm", "configured"));
+ Set<ConstraintViolation<NestedConfiguration>> actual =
VALIDATOR.validate(config);
+ assertThat(actual.size(), is(1));
+ assertThat(actual.iterator().next().getMessage(), is("references
unconfigured algorithms `first_missing`"));
+ }
+
+ @Test
+ void assertEmptyReference() {
+ NestedConfiguration config = new
NestedConfiguration(Collections.singleton(new Group(Collections.singleton(new
Item(new Reference(""), null)))), Collections.emptyMap());
+ assertTrue(VALIDATOR.validate(config).isEmpty());
+ }
+
+ @Test
+ void assertEmptyReferenceWithMissingPool() {
+ NestedConfiguration config = new
NestedConfiguration(Collections.singleton(new Group(Collections.singleton(new
Item(new Reference(""), null)))), null);
+ assertTrue(VALIDATOR.validate(config).isEmpty());
+ }
+
+ @Test
+ void assertOptionalReference() {
+ NestedConfiguration config = new
NestedConfiguration(Collections.singleton(new Group(Collections.singleton(
+ new Item(new Reference("foo_algorithm"), new
Reference("foo_algorithm"))))), Collections.singletonMap("foo_algorithm",
"configured"));
+ assertTrue(VALIDATOR.validate(config).isEmpty());
+ }
+
+ @Test
+ void assertDisallowedEmptyReference() {
+ Set<ConstraintViolation<DirectConfiguration>> actual =
VALIDATOR.validate(new DirectConfiguration("", Collections.emptyMap()));
+ assertThat(actual.size(), is(1));
+ }
+
+ @Test
+ void assertInvalidReferenceProperty() {
+ assertThrows(ValidationException.class, () -> VALIDATOR.validate(new
InvalidReferenceConfiguration(Collections.emptyMap())));
+ }
+
+ @Test
+ void assertInvalidPoolProperty() {
+ assertThrows(ValidationException.class, () -> VALIDATOR.validate(new
InvalidPoolConfiguration("not a map")));
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ @ConfigurationReferenceExists(referencePaths = "providerName", pool =
"providers")
+ public static final class DirectConfiguration {
+
+ private final String providerName;
+
+ private final Map<String, String> providers;
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ @ConfigurationReferenceExists(referencePaths =
{"groups.items.primary.name", "groups.items.secondary.name"}, pool =
"algorithms", allowEmpty = true)
+ public static final class NestedConfiguration {
+
+ private final Collection<Group> groups;
+
+ private final Map<String, String> algorithms;
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ public static final class Group {
+
+ private final Collection<Item> items;
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ public static final class Item {
+
+ private final Reference primary;
+
+ private final Reference secondary;
+
+ public Optional<Reference> getSecondary() {
+ return Optional.ofNullable(secondary);
+ }
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ public static final class Reference {
+
+ private final String name;
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ @ConfigurationReferenceExists(referencePaths = "missing", pool =
"providers")
+ public static final class InvalidReferenceConfiguration {
+
+ private final Map<String, String> providers;
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ @ConfigurationReferenceExists(referencePaths = "pool", pool = "pool")
+ public static final class InvalidPoolConfiguration {
+
+ private final String pool;
+ }
+}