com.arpnetworking.metrics.portal.alerts.impl.DatabaseAlertRepository.java Source code

Java tutorial

Introduction

Here is the source code for com.arpnetworking.metrics.portal.alerts.impl.DatabaseAlertRepository.java

Source

/**
 * Copyright 2015 Groupon.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.arpnetworking.metrics.portal.alerts.impl;

import com.arpnetworking.metrics.portal.alerts.AlertRepository;
import com.arpnetworking.play.configuration.ConfigurationHelper;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.Junction;
import com.avaje.ebean.PagedList;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.Transaction;
import com.google.inject.Inject;
import models.ebean.NagiosExtension;
import models.internal.Alert;
import models.internal.AlertQuery;
import models.internal.QueryResult;
import models.internal.impl.DefaultAlert;
import models.internal.impl.DefaultAlertQuery;
import models.internal.impl.DefaultQuantity;
import models.internal.impl.DefaultQueryResult;
import org.joda.time.Period;
import play.Configuration;
import play.Environment;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.persistence.PersistenceException;

/**
 * Implementation of <code>AlertRepository</code> using SQL database.
 *
 * @author Deepika Misra (deepika at groupon dot com)
 */
public class DatabaseAlertRepository implements AlertRepository {

    /**
     * Public constructor.
     *
     * @param environment Play's <code>Environment</code> instance.
     * @param config Play's <code>Configuration</code> instance.
     * @throws Exception If the configuration is invalid.
     */
    @Inject
    public DatabaseAlertRepository(final Environment environment, final Configuration config) throws Exception {
        this(ConfigurationHelper
                .<AlertQueryGenerator>getType(environment, config, "alertRepository.alertQueryGenerator.type")
                .newInstance());
    }

    /**
     * Public constructor.
     *
     * @param alertQueryGenerator Instance of <code>AlertQueryGenerator</code>.
     */
    public DatabaseAlertRepository(final AlertQueryGenerator alertQueryGenerator) {
        _alertQueryGenerator = alertQueryGenerator;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void open() {
        assertIsOpen(false);
        LOGGER.debug().setMessage("Opening alert repository").log();
        _isOpen.set(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        assertIsOpen();
        LOGGER.debug().setMessage("Closing alert repository").log();
        _isOpen.set(false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<Alert> get(final UUID identifier) {
        assertIsOpen();
        LOGGER.debug().setMessage("Getting alert").addData("alertId", identifier).log();

        final models.ebean.Alert ebeanAlert = Ebean.find(models.ebean.Alert.class).where().eq("uuid", identifier)
                .findUnique();
        if (ebeanAlert == null) {
            return Optional.empty();
        }
        return Optional.of(convertFromEbeanAlert(ebeanAlert));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AlertQuery createQuery() {
        assertIsOpen();
        LOGGER.debug().setMessage("Preparing query").log();
        return new DefaultAlertQuery(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public QueryResult<Alert> query(final AlertQuery query) {
        assertIsOpen();
        LOGGER.debug().setMessage("Querying").addData("query", query).log();

        // Create the base query
        final PagedList<models.ebean.Alert> pagedAlerts = _alertQueryGenerator.createAlertQuery(query);

        // Compute the etag
        // TODO(deepika): Obfuscate the etag [ISSUE-7]
        final Long etag = _alertQueryGenerator.getEtag();

        final List<Alert> values = new ArrayList<>();
        pagedAlerts.getList().forEach(ebeanAlert -> values.add(convertFromEbeanAlert(ebeanAlert)));

        // Transform the results
        return new DefaultQueryResult<>(values, pagedAlerts.getTotalRowCount(), etag.toString());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getAlertCount() {
        assertIsOpen();
        return Ebean.find(models.ebean.Alert.class).findRowCount();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addOrUpdateAlert(final Alert alert) {
        assertIsOpen();
        LOGGER.debug().setMessage("Upserting alert").addData("alert", alert).log();

        try (Transaction transaction = Ebean.beginTransaction()) {
            models.ebean.Alert ebeanAlert = Ebean.find(models.ebean.Alert.class).where().eq("uuid", alert.getId())
                    .findUnique();
            boolean isNewAlert = false;
            if (ebeanAlert == null) {
                ebeanAlert = new models.ebean.Alert();
                isNewAlert = true;
            }

            ebeanAlert.setCluster(alert.getCluster());
            ebeanAlert.setUuid(alert.getId());
            ebeanAlert.setMetric(alert.getMetric());
            ebeanAlert.setContext(alert.getContext());
            ebeanAlert.setNagiosExtension(convertToEbeanNagiosExtension(alert.getNagiosExtension()));
            ebeanAlert.setName(alert.getName());
            ebeanAlert.setOperator(alert.getOperator());
            ebeanAlert.setPeriod(alert.getPeriod().toStandardSeconds().getSeconds());
            ebeanAlert.setQuantityValue(alert.getValue().getValue());
            ebeanAlert.setQuantityUnit(alert.getValue().getUnit().orNull());
            ebeanAlert.setStatistic(alert.getStatistic());
            ebeanAlert.setService(alert.getService());
            _alertQueryGenerator.saveAlert(ebeanAlert);
            transaction.commit();

            LOGGER.info().setMessage("Upserted alert").addData("alert", alert).addData("isCreated", isNewAlert)
                    .log();
            // CHECKSTYLE.OFF: IllegalCatchCheck
        } catch (final IOException | RuntimeException e) {
            // CHECKSTYLE.ON: IllegalCatchCheck
            LOGGER.error().setMessage("Failed to upsert alert").addData("alert", alert).setThrowable(e).log();
            throw new PersistenceException(e);
        }
    }

    private void assertIsOpen() {
        assertIsOpen(true);
    }

    private void assertIsOpen(final boolean expectedState) {
        if (_isOpen.get() != expectedState) {
            throw new IllegalStateException(
                    String.format("Alert repository is not %s", expectedState ? "open" : "closed"));
        }
    }

    private Alert convertFromEbeanAlert(final models.ebean.Alert ebeanAlert) {
        return new DefaultAlert.Builder().setCluster(ebeanAlert.getCluster()).setContext(ebeanAlert.getContext())
                .setId(ebeanAlert.getUuid()).setMetric(ebeanAlert.getMetric()).setName(ebeanAlert.getName())
                .setOperator(ebeanAlert.getOperator()).setPeriod(Period.seconds(ebeanAlert.getPeriod()))
                .setService(ebeanAlert.getService()).setStatistic(ebeanAlert.getStatistic())
                .setValue(new DefaultQuantity.Builder().setValue(ebeanAlert.getQuantityValue())
                        .setUnit(ebeanAlert.getQuantityUnit()).build())
                .setNagiosExtension(convertToInternalNagiosExtension(ebeanAlert.getNagiosExtension())).build();
    }

    private models.internal.NagiosExtension convertToInternalNagiosExtension(final NagiosExtension ebeanExtension) {
        if (ebeanExtension == null) {
            return null;
        }
        return new models.internal.NagiosExtension.Builder().setSeverity(ebeanExtension.getSeverity())
                .setNotify(ebeanExtension.getNotify()).setMaxCheckAttempts(ebeanExtension.getMaxCheckAttempts())
                .setFreshnessThresholdInSeconds(ebeanExtension.getFreshnessThreshold()).build();
    }

    private NagiosExtension convertToEbeanNagiosExtension(final models.internal.NagiosExtension internalExtension) {
        if (internalExtension == null) {
            return null;
        }
        final NagiosExtension extension = new NagiosExtension();
        extension.setSeverity(internalExtension.getSeverity());
        extension.setNotify(internalExtension.getNotify());
        extension.setMaxCheckAttempts(internalExtension.getMaxCheckAttempts());
        extension.setFreshnessThreshold(internalExtension.getFreshnessThreshold().getStandardSeconds());
        return extension;
    }

    private final AtomicBoolean _isOpen = new AtomicBoolean(false);
    private final AlertQueryGenerator _alertQueryGenerator;

    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseAlertRepository.class);

    /**
     * Inteface for database query generation.
     */
    public interface AlertQueryGenerator {

        /**
         * Translate the <code>AlertQuery</code> to an Ebean <code>Query</code>.
         *
         * @param query The repository agnostic <code>AlertQuery</code>.
         * @return The database specific <code>PagedList</code> query result.
         */
        PagedList<models.ebean.Alert> createAlertQuery(AlertQuery query);

        /**
         * Save the <code>Alert</code> to the database. This needs to be executed in a transaction.
         *
         * @param alert The <code>Alert</code> model instance to save.
         */
        void saveAlert(models.ebean.Alert alert);

        /**
         * Gets the etag for the alerts table.
         *
         * @return The etag for the table.
         */
        long getEtag();
    }

    /**
     * RDBMS agnostic query for alerts.
     */
    public static final class GenericQueryGenerator implements AlertQueryGenerator {

        /**
         * {@inheritDoc}
         */
        @Override
        public PagedList<models.ebean.Alert> createAlertQuery(final AlertQuery query) {
            ExpressionList<models.ebean.Alert> ebeanExpressionList = Ebean.find(models.ebean.Alert.class).where();
            if (query.getCluster().isPresent()) {
                ebeanExpressionList = ebeanExpressionList.eq("cluster", query.getCluster().get());
            }
            if (query.getContext().isPresent()) {
                ebeanExpressionList = ebeanExpressionList.eq("context", query.getContext().get().toString());
            }
            if (query.getService().isPresent()) {
                ebeanExpressionList = ebeanExpressionList.eq("service", query.getService().get());
            }

            //TODO(deepika): Add full text search [ISSUE-11]
            if (query.getContains().isPresent()) {
                final Junction<models.ebean.Alert> junction = ebeanExpressionList.disjunction();
                ebeanExpressionList = junction.contains("name", query.getContains().get());
                if (!query.getCluster().isPresent()) {
                    ebeanExpressionList = junction.contains("cluster", query.getContains().get());
                }
                if (!query.getService().isPresent()) {
                    ebeanExpressionList = junction.contains("service", query.getContains().get());
                }
                ebeanExpressionList = junction.contains("metric", query.getContains().get());
                ebeanExpressionList = junction.contains("statistic", query.getContains().get());
                ebeanExpressionList = junction.contains("operator", query.getContains().get());
                ebeanExpressionList = ebeanExpressionList.endJunction();
            }
            final Query<models.ebean.Alert> ebeanQuery = ebeanExpressionList.query();
            int pageOffset = 0;
            if (query.getOffset().isPresent()) {
                pageOffset = query.getOffset().get() / query.getLimit();
            }
            return ebeanQuery.findPagedList(pageOffset, query.getLimit());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void saveAlert(final models.ebean.Alert alert) {
            Ebean.save(alert);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public long getEtag() {
            final SqlQuery sqlQuery = Ebean.createSqlQuery("SELECT CURRVAL('portal.alerts_etag_seq') AS etag;");
            return sqlQuery.findUnique().getLong("etag");
        }
    }
}