com.tomtom.speedtools.apivalidation.ApiValidator.java Source code

Java tutorial

Introduction

Here is the source code for com.tomtom.speedtools.apivalidation.ApiValidator.java

Source

/*
 * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com).
 *
 * 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
 *
 *    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 com.tomtom.speedtools.apivalidation;

import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.tomtom.speedtools.apivalidation.errors.*;
import com.tomtom.speedtools.apivalidation.exceptions.ApiBadRequestException;
import com.tomtom.speedtools.domain.ColorConverter;
import com.tomtom.speedtools.domain.Uid;
import com.tomtom.speedtools.locale.LocaleUtil;
import com.tomtom.speedtools.utils.MathUtils;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * The class WebParameterValidator implements a simple way to validate monitoring parameters and throw a BAD_REQUEST
 * (HTTP status code 400) is a parameter is not ok.
 */
public final class ApiValidator {
    private static final Logger LOG = LoggerFactory.getLogger(ApiValidator.class);

    @Nonnull
    private final static String FORMAT_DATE_ISO = "yyyy-MM-dd";

    private enum Status {
        NOT_STARTED, IN_PROGRESS, FINISHED
    }

    @Nonnull
    private final List<ApiValidationError> errors = new ArrayList<>();
    private Status status = Status.NOT_STARTED;

    public void start() {
        status = Status.IN_PROGRESS;
    }

    public void done() throws ApiBadRequestException {
        status = Status.FINISHED;
        throwIfExceptions();
    }

    public void checkInteger(final boolean required, @Nonnull final String name, @Nullable final Integer value,
            final int min, final int max) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value, min, max)) {
                errors.add(new ApiIntegerOutOfRangeError(name, value, min, max));
            }
        }
    }

    public void checkLong(final boolean required, @Nonnull final String name, @Nullable final Long value,
            final long min, final long max) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value, min, max)) {
                errors.add(new ApiLongOutOfRangeError(name, value, min, max));
            }
        }
    }

    /**
     * Check a double value.
     *
     * @param required                Specifies whether value may be null or not.
     * @param name                    Name of value.
     * @param value                   Actual value.
     * @param min                     Minimum value.
     * @param max                     Maximum value.
     * @param positiveInfinityAllowed The value of positiveInfinityAllowed indicates whether or not {@link
     *                                Double#POSITIVE_INFINITY} is allowed as a value or not. If positiveInfinityAllowed
     *                                is false, the value must be in range [min, max] (inclusive). If
     *                                positiveInfinityAllowed is true, the value must either be in range [min, max] or
     *                                may be {@link Double#POSITIVE_INFINITY}. Note that {@link
     *                                Double#NEGATIVE_INFINITY} is never allowed.
     */
    public void checkDouble(final boolean required, @Nonnull final String name, @Nullable final Double value,
            final double min, final double max, final boolean positiveInfinityAllowed) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);

        if (value != null) {
            if (!MathUtils.isBetween(value, min, max)) {
                if (!positiveInfinityAllowed || !Double.isInfinite(value)) {
                    errors.add(new ApiDoubleOutOfRangeError(name, value, min, max));
                }
            }
        }
    }

    public void checkNotNull(final boolean required, @Nonnull final String name, @Nullable final Object value) {
        assert name != null;
        assertNotFinished();
        if ((value == null) && required) {
            errors.add(new ApiParameterMissingError(name));
        }
    }

    public void checkNotNullAndValidate(final boolean required, @Nonnull final String name,
            @Nullable final ApiDTO value) {
        assert name != null;
        checkNotNull(required, name, value);
        if (value != null) {
            value.validate();
        }
    }

    public void checkNotNullAndValidateAll(final boolean required, @Nonnull final String name,
            @Nullable final Collection<? extends ApiDTO> value) {
        assert name != null;
        checkNotNull(required, name, value);
        if (value != null) {
            for (final ApiDTO binder : value) {
                checkNotNull(true, name, binder);
                binder.validate();
            }
        }
    }

    public <T> void checkNotNullAndValidateEnum(final boolean required, @Nonnull final String name,
            @Nullable final T value) {
        assert name != null;
        assert (value == null) || value.getClass().isEnum();
        checkNotNull(required, name, value);
    }

    public void checkAllowedValues(final boolean required, @Nonnull final String name, @Nullable final String value,
            @Nonnull final String... allowedValues) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            boolean found = false;
            for (final String allowedValue : allowedValues) {
                if (allowedValue.equals(value)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                final StringBuilder sb = new StringBuilder();
                for (final String allowedValue : allowedValues) {
                    sb.append('|');
                    sb.append(allowedValue);
                }
                errors.add(new ApiParameterSyntaxError(name, value, sb.toString()));
            }
        }
    }

    public void checkString(final boolean required, @Nonnull final String name, @Nullable final String value,
            final int minLength, final int maxLength) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value.length(), minLength, maxLength)) {
                errors.add(new ApiStringLengthError(name, value.length(), minLength, maxLength));
            }
        }
    }

    public void checkCountryISO2(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value.length(), ApiValidationConstants.API_COUNTRY_ISO2_MIN_LENGTH,
                    ApiValidationConstants.API_COUNTRY_ISO2_MAX_LENGTH)) {
                errors.add(new ApiStringLengthError(name, value.length(),
                        ApiValidationConstants.API_COUNTRY_ISO2_MIN_LENGTH,
                        ApiValidationConstants.API_COUNTRY_ISO2_MAX_LENGTH));
            }

            // Check if the code is known.
            boolean found = false;
            for (final String country : Locale.getISOCountries()) {
                if (country.equals(value)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                final StringBuilder sb = new StringBuilder();
                for (final String country : Locale.getISOCountries()) {
                    sb.append('|');
                    sb.append(country);
                }
                errors.add(new ApiParameterSyntaxError(name, value, sb.toString()));
            }
        }
    }

    public void checkCurrencyISO3(final boolean required, @Nonnull final String name,
            @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value.length(), ApiValidationConstants.API_CURRENCY_ISO3_MIN_LENGTH,
                    ApiValidationConstants.API_CURRENCY_ISO3_MAX_LENGTH)) {
                errors.add(new ApiStringLengthError(name, value.length(),
                        ApiValidationConstants.API_CURRENCY_ISO3_MIN_LENGTH,
                        ApiValidationConstants.API_CURRENCY_ISO3_MAX_LENGTH));
            }

            // Get all currency codes.
            final Set<Currency> currencies = new HashSet<>();
            for (final Locale locale : Locale.getAvailableLocales()) {
                try {
                    final Currency currency = Currency.getInstance(locale);
                    currencies.add(currency);
                    String iso3Country = "___";
                    try {
                        iso3Country = locale.getISO3Country();
                    } catch (final MissingResourceException ignored) {
                        // Ignore.
                    }
                    String iso3Language = "___";
                    try {
                        iso3Language = locale.getISO3Language();
                    } catch (final MissingResourceException ignored) {
                        // Ignore.
                    }
                    LOG.trace("checkCurrencyISO3: {}-{}: {}", iso3Country, iso3Language,
                            currency.getCurrencyCode());
                } catch (final IllegalArgumentException ignored) {
                    // Locale not found, OK to ignore.
                }
            }

            // Check if the code is known.
            boolean found = false;
            for (final Currency currency : currencies) {
                if (currency.getCurrencyCode().equals(value)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                final StringBuilder sb = new StringBuilder();
                for (final Currency currency : currencies) {
                    sb.append('|');
                    sb.append(currency.getCurrencyCode());
                }
                errors.add(new ApiParameterSyntaxError(name, value, sb.toString()));
            }
        }
    }

    public void checkEmailAddress(final boolean required, @Nonnull final String name,
            @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value.length(), ApiValidationConstants.API_EMAIL_ADDRESS_MIN_LENGTH,
                    ApiValidationConstants.API_EMAIL_ADDRESS_MAX_LENGTH)) {
                errors.add(new ApiStringLengthError(name, value.length(),
                        ApiValidationConstants.API_EMAIL_ADDRESS_MIN_LENGTH,
                        ApiValidationConstants.API_EMAIL_ADDRESS_MAX_LENGTH));
            }
            try {
                final InternetAddress emailAddress = new InternetAddress(value);
                emailAddress.validate();
            } catch (final AddressException e) {
                errors.add(new ApiParameterSyntaxError(name, value, "name@domain.tld (RFC2822 compliant)",
                        e.getMessage()));
            }
        }
    }

    public void checkTelephoneNumber(final boolean required, @Nonnull final String name,
            @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value.length(), ApiValidationConstants.API_TELEPHONE_NUMBER_MIN_LENGTH,
                    ApiValidationConstants.API_TELEPHONE_NUMBER_MAX_LENGTH)) {
                errors.add(new ApiStringLengthError(name, value.length(),
                        ApiValidationConstants.API_TELEPHONE_NUMBER_MIN_LENGTH,
                        ApiValidationConstants.API_TELEPHONE_NUMBER_MAX_LENGTH));
            }
            try {
                PhoneNumberUtil.getInstance().parse(value, null);
            } catch (final NumberParseException e) {
                errors.add(new ApiParameterSyntaxError(name, value, "+312012345678", e.getMessage()));
            }
        }
    }

    public void checkTimeZone(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!MathUtils.isBetween(value.length(), ApiValidationConstants.API_TIME_ZONE_MIN_LENGTH,
                    ApiValidationConstants.API_TIME_ZONE_MAX_LENGTH)) {
                errors.add(new ApiStringLengthError(name, value.length(),
                        ApiValidationConstants.API_TIME_ZONE_MIN_LENGTH,
                        ApiValidationConstants.API_TIME_ZONE_MAX_LENGTH));
            }
        }
    }

    public void checkLocale(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            try {
                LocaleUtil.parse(value);
            } catch (final IllegalArgumentException e) {
                errors.add(new ApiParameterSyntaxError(name, value, "ll[_CC] | ll_[CC]_var | _CC[_var]",
                        e.getMessage()));
            }
        }
    }

    public void checkUrl(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            boolean ok = false;
            try {
                //noinspection UnusedDeclaration,UnusedAssignment
                final URL url = new URL(value);
                ok = true;
            } catch (final MalformedURLException ignored) {

            }
            if (!ok) {
                errors.add(new ApiParameterSyntaxError(name, name, "url"));
            }
        }
    }

    public void checkDate(final boolean required, @Nonnull final String name, @Nullable final DateTime value,
            @Nonnull final DateTime minDate, @Nonnull final DateTime maxDate) {
        assert name != null;
        assert minDate != null;
        assert maxDate != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (value.isBefore(minDate) || value.isAfter(maxDate)) {
                errors.add(new ApiDateOutOfRangeError(name, value, minDate, maxDate));
            }
        }
    }

    public void checkDate(final boolean required, @Nonnull final String name, @Nullable final Date value,
            @Nonnull final DateTime minDate, @Nonnull final DateTime maxDate) {
        checkDate(required, name, new DateTime(value), minDate, maxDate);
    }

    public void checkLocalDate(final boolean required, @Nonnull final String name, @Nullable final String value,
            @Nonnull final LocalDate minDate, @Nonnull final LocalDate maxDate) {
        checkNotNull(required, name, value);
        if (value != null) {
            try {
                final LocalDate localDate = LocalDate.parse(value);
                // Check range.
                if (localDate.isBefore(minDate) || localDate.isAfter(maxDate)) {
                    errors.add(new ApiLocalDateOutOfRangeError(name, localDate, minDate, maxDate));
                }
            } catch (final IllegalArgumentException ignored) {
                errors.add(new ApiLocalDateFormatError(name, value, FORMAT_DATE_ISO));
            }
        }
    }

    public void checkUid(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            if (!Uid.isValid(value)) {
                errors.add(new ApiUidSyntaxError(name, value));
            }
        }
    }

    public void checkColor(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);
        if (value != null) {
            try {
                ColorConverter.toColorRGB(value);
            } catch (final IllegalArgumentException ignored) {
                errors.add(new ApiColorFormatError(name, value));
            }
        }
    }

    public void checkIBAN(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);

        if (value != null) {
            if ((value.length() < ApiValidationConstants.API_IBAN_MIN_LENGTH)
                    || (value.length() > ApiValidationConstants.API_IBAN_MAX_LENGTH)) {
                errors.add(new ApiIBANFormatError(name, value));
            }
        }
    }

    public void checkBIC(final boolean required, @Nonnull final String name, @Nullable final String value) {
        assert name != null;
        assertNotFinished();
        checkNotNull(required, name, value);

        if (value != null) {
            if ((value.length() != ApiValidationConstants.API_BIC_MIN_LENGTH)
                    && (value.length() != ApiValidationConstants.API_BIC_MAX_LENGTH)) {
                errors.add(new ApiBICFormatError(name, value));
            }
        }
    }

    public void assertFinished() {
        if (status != Status.FINISHED) {
            LOG.error("assertFinished: Validation framework error. Validation was not finished properly.");
            assert false;
        }
    }

    public void assertNotStarted() {
        if (status != Status.NOT_STARTED) {
            LOG.error("assertNotStarted: Validation framework error. Validation was called before setter.");
            assert false;
        }
    }

    public void assertNotFinished() {
        if (status == Status.FINISHED) {
            LOG.error("assertNotFinished: Validation framework error. Validation was unexpectedly finished.");
            assert false;
        }
    }

    private void throwIfExceptions() throws ApiBadRequestException {
        if (!errors.isEmpty()) {
            LOG.debug("throwIfExceptions: API validation error: {}", errors);
            throw new ApiBadRequestException(errors);
        }
    }
}