文字をUnicodeに変換してEnumに持たせている対象文字のUnicodeと比べ、一致していたらValidationエラーにするという自作Valid用Annotationを作りました。
例では、「髙」(はしご高)が文字列に含まれていた場合に、Validationエラーとして弾いています。
ちなみに、この「髙」はIBM拡張漢字です。
実装
SpecificCharValid.java
/**
* 文字列の中に、特定の文字が含まれていた場合はバリデーションエラーを返すAnnotation <br>
* どの文字が対象となるかは{@link SpecificCharEnum}参照
*/
@Documented
@Constraint(validatedBy = {SpecificCharValidator.class})
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SpecificCharValid {
String message() default "The characters that cannot be used are included.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
SpecificCharValid[] value();
}
}
これで、@SpecificCharValid
アノテーションとして、フィールドに適用することができます。
@Target({ElementType.FIELD})
でオブジェクトのフィールドに適用できるようにします。
ここでは、アノテーションとして宣言する文字列を設定する程度な記述です。
実際の処理は次のクラスで行います。
SpecificCharValidator.java
@Slf4j
public class SpecificCharValidator implements ConstraintValidator<SpecificCharValid, String> {
@Override
public void initialize(SpecificCharValid constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
char[] chars = value.toCharArray();
for (char c : chars) {
String unicodeStr = Integer.toHexString(c);
if (SpecificCharEnum.isSpecificChar(unicodeStr)) {
String targetStr = ((ConstraintValidatorContextImpl) context)
.getConstraintViolationCreationContexts().get(0).getPath().toString();
log.warn("検知対象の文字が含まれています。対象項目:{} 値:「{}」", targetStr, value);
return false;
}
}
return true;
}
}
@SpecificCharValid
アノテーションの実装クラスです。
ConstraintValidatorをimplementsしています。
isValid()をOverrideしていて、trueならエラー無し、falseならエラー有りとなります。
Enumが持っているUnicodeと、与えられた文字列に含まれる文字のUnicodeを一つずつ検証しています。
検証対象の文字列は引数のvalueとしてそのまま受け取るのですが、「そのvalueがどこのフィールドに入っていたのか」というフィールドの情報はConstraintValidatorContextに格納されており、上の実装のようにConstraintValidatorContextImplにキャストすることで取り出すことができます。
今回は、エラーとなった時にフィールド名も一緒にログで出力できるようにしました。
SpecificCharEnum.java
/**
* 検知対象文字のEnum
*/
@Slf4j
public enum SpecificCharEnum {
SPECIFIC_CHAR_ENUM_1("髙","9ad9");
private final String specificChar;
private final String unicodeStr;
SpecificCharEnum(String specificChar, String unicodeStr) {
this.specificChar = specificChar;
this.unicodeStr = unicodeStr;
}
public String getUnicodeStr() {
return unicodeStr;
}
public String getSpecificChar() {
return specificChar;
}
public static boolean isSpecificChar(String targetUnicode){
for (SpecificCharEnum specificCharEnum: SpecificCharEnum.values()){
if (specificCharEnum.getUnicodeStr().equals(targetUnicode)){
log.warn("検知対象の文字を検知しました。対象:{}",specificCharEnum.getSpecificChar());
return true;
}
}
return false;
}
}
エラーで弾く対象となる文字をEnumで持っています。
Unicode情報を持っており、isSpecificCharで与えられたUnicodeと各EnumのUnicodeを比較しています。
テスト
SpecificCharValidatorTests.java
class SpecificCharValidatorTests {
private Validator validator;
@BeforeEach
void setUp() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@MethodSource("testStrList")
@ParameterizedTest
void specificCharTest(String targetStr, boolean expect) {
TestBean testBean = new TestBean(targetStr);
Set<ConstraintViolation<TestBean>> violations = validator.validate(testBean);
assertThat(violations.isEmpty()).isEqualTo(expect);
}
static Stream<Arguments> testStrList() {
return Stream.of(
Arguments.arguments("", true),
Arguments.arguments(null, true),
Arguments.arguments("夏が近づき太平洋高気圧が勢力を増すようになると", true),
Arguments.arguments("夏が近づき太平洋髙気圧が勢力を増すようになると", false),
Arguments.arguments("髙等学校", false),
Arguments.arguments("高等学校", true)
);
}
private static class TestBean {
@SpecificCharValid
private String targetStr;
TestBean(String targetStr) {
this.targetStr = targetStr;
}
}
}
バリデーションエラーに引っ掛かった時のログとしてはこんな感じに出ます。
16:14:27.851 [main] WARN com.example.demo.validator.SpecificCharEnum - 制御対象の文字を検知しました。対象:髙
16:14:27.851 [main] WARN com.example.demo.validator.SpecificCharValidator - 検知対象の文字が含まれています。対象項目:targetStr 値:「髙等学校」
その他
文字のUnicodeを探すときに参考になるサイト
プログラム書いて出力してもらう
また、対象の文字が分かっているのであればプログラムでUnicodeを出力してしまうのもアリ。
上のEnumに追加する想定でUnicodeを出力するプログラムを書いてみる。
@Disabled
@Test
void outputUnicode(){
//検知対象にしたい文字をListに追加する。
List<String> strList = List.of("髙", "あ", "い", "①");
for(int i=0;i<strList.size(); i++){
char[] charArray = strList.get(i).toCharArray();
for (char c: charArray){
System.out.println("SPECIFIC_CHAR_ENUM_"+
(i+1)+"(\""+strList.get(i)+"\",\""
+Integer.toHexString(c)+"\"),");
}
}
}
出力結果
SPECIFIC_CHAR_ENUM_1("髙","9ad9"),
SPECIFIC_CHAR_ENUM_2("あ","3042"),
SPECIFIC_CHAR_ENUM_3("い","3044"),
SPECIFIC_CHAR_ENUM_4("①","2460"),
Process finished with exit code 0