ee.ria.xroad.opmonitordaemon.HealthDataRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for ee.ria.xroad.opmonitordaemon.HealthDataRequestHandler.java

Source

/**
 * The MIT License
 * Copyright (c) 2016 Estonian Information System Authority (RIA), Population Register Centre (VRK)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package ee.ria.xroad.opmonitordaemon;

import java.io.OutputStream;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javax.xml.bind.JAXBElement;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

import ee.ria.xroad.common.identifier.ClientId;
import ee.ria.xroad.common.identifier.ServiceId;
import ee.ria.xroad.common.message.SoapMessageImpl;
import ee.ria.xroad.opmonitordaemon.message.FilterCriteriaType;
import ee.ria.xroad.opmonitordaemon.message.GetSecurityServerHealthDataResponseType;
import ee.ria.xroad.opmonitordaemon.message.GetSecurityServerHealthDataType;
import ee.ria.xroad.opmonitordaemon.message.LastPeriodStatisticsType;
import ee.ria.xroad.opmonitordaemon.message.ServiceEventsType;
import ee.ria.xroad.opmonitordaemon.message.ServicesEventsType;

import static ee.ria.xroad.opmonitordaemon.HealthDataMetrics.MONITORING_STARTUP_TIMESTAMP;
import static ee.ria.xroad.opmonitordaemon.HealthDataMetrics.STATISTICS_PERIOD_SECONDS;
import static ee.ria.xroad.opmonitordaemon.HealthDataMetricsUtil.*;

/**
 * Query handler for health data requests.
 */
@Slf4j
@RequiredArgsConstructor
public class HealthDataRequestHandler extends QueryRequestHandler {

    private static final int SERVICE_ID_NUM_PARTS = 6;
    private static final int SERVICE_ID_VERSION_PART = 5;
    private static final int SERVICE_ID_CODE_PART = 4;
    private static final int SERVICE_ID_SUBSYSTEM_PART = 3;

    private static final String IDENTIFIER_SEPARATOR = "/";

    /** The registry of health data. */
    private final MetricRegistry healthMetricRegistry;

    @Override
    public void handle(SoapMessageImpl requestSoap, OutputStream out, Consumer<String> contentTypeCallback)
            throws Exception {
        log.trace("handle()");

        ClientId clientId = requestSoap.getClient();
        GetSecurityServerHealthDataType requestData = getRequestData(requestSoap,
                GetSecurityServerHealthDataType.class);

        Optional<ClientId> provider = Optional.ofNullable(requestData.getFilterCriteria())
                .map(FilterCriteriaType::getClient);

        log.debug("Handle getSecurityServerHealthData: clientId: {}, " + "filterCriteria.client: {}", clientId,
                provider.orElse(null));

        SoapMessageImpl response = createResponse(requestSoap, buildHealthDataResponse(provider));

        contentTypeCallback.accept(response.getContentType());
        out.write(response.getBytes());
    }

    @SuppressWarnings("unchecked")
    private JAXBElement<?> buildHealthDataResponse(Optional<ClientId> provider) throws Exception {
        GetSecurityServerHealthDataResponseType healthDataResponse = OBJECT_FACTORY
                .createGetSecurityServerHealthDataResponseType();

        Optional<Gauge<Long>> timestamp = Optional
                .ofNullable(findGauge(healthMetricRegistry, MONITORING_STARTUP_TIMESTAMP));

        healthDataResponse.setMonitoringStartupTimestamp(timestamp.orElseThrow(this::missingTimestamp).getValue());
        Optional<Gauge<Integer>> statisticsPeriodSeconds = Optional
                .ofNullable(findGauge(healthMetricRegistry, STATISTICS_PERIOD_SECONDS));
        healthDataResponse
                .setStatisticsPeriodSeconds(statisticsPeriodSeconds.orElseThrow(this::missingPeriod).getValue());

        healthDataResponse.setServicesEvents(buildServicesEvents(provider));

        return OBJECT_FACTORY.createGetSecurityServerHealthDataResponse(healthDataResponse);
    }

    private Exception missingTimestamp() {
        return new IllegalStateException(
                "Monitoring startup timestamp is" + " missing in health metrics registry!");
    }

    private Exception missingPeriod() {
        return new IllegalStateException("Health statistics period is missing" + " in health metrics registry!");
    }

    private Stream<ServiceId> servicesWithAvailableMetrics() {
        return healthMetricRegistry.getMetrics().entrySet().stream()
                .map(HealthDataRequestHandler::extractIdentifier).filter(Objects::nonNull).distinct()
                .map(HealthDataRequestHandler::convertIdentifier);
    }

    private ServicesEventsType buildServicesEvents(Optional<ClientId> provider) {
        ServicesEventsType servicesEvents = OBJECT_FACTORY.createServicesEventsType();

        servicesWithAvailableMetrics()
                // If a client ID was provided in the request then
                // only include service metrics for that provider
                .filter(id -> provider.map(id.getClientId()::equals).orElse(true))
                .forEach(service -> servicesEvents.getServiceEvents().add(buildServiceEvents(service)));

        return servicesEvents;
    }

    @SuppressWarnings("unchecked")
    private ServiceEventsType buildServiceEvents(ServiceId service) {
        ServiceEventsType serviceEvents = OBJECT_FACTORY.createServiceEventsType();

        serviceEvents.setService(service);

        Optional<Gauge<Long>> lastSuccessfulRequestTimestamp = Optional
                .ofNullable(findGauge(healthMetricRegistry, getLastRequestTimestampGaugeName(service, true)));
        lastSuccessfulRequestTimestamp
                .ifPresent(g -> serviceEvents.setLastSuccessfulRequestTimestamp(g.getValue()));

        Optional<Gauge<Long>> lastUnsuccessfulRequestTimestamp = Optional
                .ofNullable(findGauge(healthMetricRegistry, getLastRequestTimestampGaugeName(service, false)));
        lastUnsuccessfulRequestTimestamp
                .ifPresent(g -> serviceEvents.setLastUnsuccessfulRequestTimestamp(g.getValue()));

        serviceEvents.setLastPeriodStatistics(buildLastPeriodStats(service));

        return serviceEvents;
    }

    private LastPeriodStatisticsType buildLastPeriodStats(ServiceId service) {
        LastPeriodStatisticsType lastPeriodStats = OBJECT_FACTORY.createLastPeriodStatisticsType();

        lastPeriodStats.setSuccessfulRequestCount(0);
        Optional<Counter> successfulEventCount = Optional
                .ofNullable(findCounter(healthMetricRegistry, getRequestCounterName(service, true)));
        successfulEventCount.ifPresent(c -> lastPeriodStats.setSuccessfulRequestCount((int) c.getCount()));

        lastPeriodStats.setUnsuccessfulRequestCount(0);
        Optional<Counter> unsuccessfulEventCount = Optional
                .ofNullable(findCounter(healthMetricRegistry, getRequestCounterName(service, false)));
        unsuccessfulEventCount.ifPresent(c -> lastPeriodStats.setUnsuccessfulRequestCount((int) c.getCount()));

        if (lastPeriodStats.getSuccessfulRequestCount() > 0) {
            Optional<Histogram> requestDuration = Optional
                    .ofNullable(findHistogram(healthMetricRegistry, getRequestDurationName(service)));
            requestDuration.ifPresent(h -> {
                lastPeriodStats.setRequestMinDuration(h.getSnapshot().getMin());
                lastPeriodStats.setRequestAverageDuration(h.getSnapshot().getMean());
                lastPeriodStats.setRequestMaxDuration(h.getSnapshot().getMax());
                lastPeriodStats.setRequestDurationStdDev(h.getSnapshot().getStdDev());
            });

            Optional<Histogram> requestSoapSize = Optional
                    .ofNullable(findHistogram(healthMetricRegistry, getRequestSoapSizeName(service)));
            requestSoapSize.ifPresent(h -> {
                lastPeriodStats.setRequestMinSoapSize(h.getSnapshot().getMin());
                lastPeriodStats.setRequestAverageSoapSize(h.getSnapshot().getMean());
                lastPeriodStats.setRequestMaxSoapSize(h.getSnapshot().getMax());
                lastPeriodStats.setRequestSoapSizeStdDev(h.getSnapshot().getStdDev());
            });

            Optional<Histogram> responseSoapSize = Optional
                    .ofNullable(findHistogram(healthMetricRegistry, getResponseSoapSizeName(service)));
            responseSoapSize.ifPresent(h -> {
                lastPeriodStats.setResponseMinSoapSize(h.getSnapshot().getMin());
                lastPeriodStats.setResponseAverageSoapSize(h.getSnapshot().getMean());
                lastPeriodStats.setResponseMaxSoapSize(h.getSnapshot().getMax());
                lastPeriodStats.setResponseSoapSizeStdDev(h.getSnapshot().getStdDev());
            });
        }

        return lastPeriodStats;
    }

    private static String extractIdentifier(Entry<String, Metric> metric) {
        String serviceName = null;
        String metricName = metric.getKey();

        if (metricName.contains("(")) {
            serviceName = metricName.substring(metricName.indexOf('(') + 1, metricName.lastIndexOf(')'));
        }

        return serviceName;
    }

    private static ServiceId convertIdentifier(String id) {
        // Construct a valid service identifier so we can compare it's
        // provider to the optionally provided exchange partner's client ID
        String[] idParts = id.split(IDENTIFIER_SEPARATOR, SERVICE_ID_NUM_PARTS);

        for (int i = 0; i < idParts.length; i++) {
            idParts[i] = StringEscapeUtils.unescapeHtml4(idParts[i]);

            if (StringUtils.isBlank(idParts[i])) {
                idParts[i] = null;
            }
        }

        return idParts.length > SERVICE_ID_NUM_PARTS - 1
                ? ServiceId.create(idParts[0], idParts[1], idParts[2], idParts[SERVICE_ID_SUBSYSTEM_PART],
                        idParts[SERVICE_ID_CODE_PART], idParts[SERVICE_ID_VERSION_PART])
                : ServiceId.create(idParts[0], idParts[1], idParts[2], idParts[SERVICE_ID_SUBSYSTEM_PART],
                        idParts[SERVICE_ID_CODE_PART]);
    }
}