Validieren von Telefonnummern

Unmöglich können wir das Betragen anderer mit Strenge prüfen, wenn wir nicht selbst zuerst unsere Pflicht erfüllen.

Demosthenes

Im letzten Beitrag wurde für die Klasse InternationalTelephoneNumber aus dem Projekt Telephone ein Serialisierer und ein Deserialisierer für Jackson erstellt. Damit ist es möglich diese Klasse direkt in REST Request einzusetzen. Zusätzlich wäre eine Validierung der Telefonnummern wünschenswert. Auf diese Weise könnten Telefonnummern mit speziellen Vorwahlen oder Durchwahlen abgelehnt werden.

public class MemberDonation {
  @NotBlank
  private String firstname;
  @NotBlank
  private String lastname;
  @Past
  private LocalDate dayOfBirth;
  @GermanCountryCode
  private InternationalPhoneNumber phoneNumber;
  @Positive
  private Money donation;
}

In dem hier dargestellten Beispiel, sollen durch die Annotation @GermanCountryCode, nur Telefonnummern mit dem Country Code 49 zugelassen werden.

Ähnlich wie die Klasse InternationalPhoneNumber haben auch die Klassen LocalDate und Money eine String Darstellung in der JSON Anfrage und werden durch spezielle Jackson Module (
jackson-modules-java8, jackson-datatype-money) umgewandelt.

Um den Country Code per Annotation zu validieren, müssen zunächst die entsprechenden Annotation erstellt werden. Dies sind die folgenden Annotationen @CountryCode und @GermanCountryCode.

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface CountryCode {
    String message() default "{de.schegge.validator.CountryCode.message}";

    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    String[] value();
    boolean allowed() default true;
}

Die allgemeinere Annotation @CountryCode definiert eine Liste von Country Codes im Attribut value und ein boolean Flag allowed. Ist das Flag positiv, dann sind die angegebenen Country Codes erlaubt, ansonsten sind sie verboten.

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,        ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {})
@CountryCode("49")
public @interface GermanCountryCode {
    @OverridesAttribute(constraint = CountryCode.class)
    String message() default "{de.schegge.validator.GermanCountryCode.message}";

    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @OverridesAttribute(constraint = CountryCode.class)
    boolean allowed() default true;
}

Die Annotation @GermanCountryCode ist eine Spezialisierung der Annotation @CountryCode und legt den Wert von value auf "49" fest.

Die Implementierung des CountryCodeValidator für die Klasse InternationalPhoneNumber ist recht einfach gehalten. Der Country Code des aktuellen Wertes wird in der vorgegebenen Liste gesucht. Das Ergebnis wird mit dem Flag verglichen. Ist das Flag positiv und wurde der Country Code gefunden ist die Validierung erfolgreich. Wurde der Country Code nicht gefunden und das Flag ist negativ, dann ist die Validierung auch erfolgreich.

public class CountryCodeValidator implements ConstraintValidator<CountryCode, InternationalPhoneNumber> {
    private List<String> countryCodes;
    private boolean allowed;

    @Override
    public void initialize(CountryCode constraintAnnotation) {
        countryCodes = Arrays.asList(constraintAnnotation.value());
        allowed = constraintAnnotation.allowed();
    }

    @Override
    public boolean isValid(InternationalPhoneNumber phoneNumber, ConstraintValidatorContext constraintValidatorContext) {
        if (phoneNumber == null) {
            return true;
        }
        return allowed == countryCodes.contains(phoneNumber.getCountryCode());
    }
}

Als Ergänzung zum CountryCodeValidator gibt es noch den StringCountryCodeValidator. Dieser Validator prüft den Country Code in Strings, die nach DIN 5008 formatierte Telefonnummern enthalten.

public class StringCountryCodeValidator implements ConstraintValidator<CountryCode, String> {
    private CountryCodeValidator wrapped;

    @Override
    public void initialize(CountryCode constraintAnnotation) {
        wrapped.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String phoneNumber, ConstraintValidatorContext constraintValidatorContext) {
        if (phoneNumber == null) {
            return true;
        }
        return wrapped.isValid(InternationalPhoneNumber.parse(phoneNumber), constraintValidatorContext);
    }
}

Der StringCountryCodeValidator verwendet den CountryCodeValidator und delegiert das Validierung, wenn der String eine passende Telefonnummer enthält.

Die Validatoren für die nationale Vorwahl sind ganz ähnlich aufgebaut und bieten die Annotationen @NationalDestinationCode zum validieren an. Zusätzlich gibt es die Annotation @GermanMobilNumber, die @NationalDestinationCode und @GermanCountryCode kombiniert.

Wer die Validierung ausprobieren möchte, kann die Implementierungen auf Maven Central finden.

<dependency>
    <groupId>de.schegge</groupId>
    <artifactId>telephone</artifactId>
    <version>0.2.1</version>
</dependency>

Leave a Comment