controllers.ExpressionController.java Source code

Java tutorial

Introduction

Here is the source code for controllers.ExpressionController.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 controllers;

import com.arpnetworking.commons.jackson.databind.ObjectMapperFactory;
import com.arpnetworking.metrics.portal.expressions.ExpressionRepository;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import com.google.inject.Inject;
import models.internal.Expression;
import models.internal.ExpressionQuery;
import models.internal.QueryResult;
import models.internal.impl.DefaultExpression;
import models.view.PagedContainer;
import models.view.Pagination;
import play.Configuration;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Result;

import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.inject.Singleton;

/**
 * Metrics portal expression controller. Exposes APIs to query and manipulate expressions.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 */
@Singleton
public class ExpressionController extends Controller {

    /**
     * Public constructor.
     *
     * @param configuration Instance of Play's <code>Configuration</code>.
     * @param expressionRepository Instance of <code>ExpressionRepository</code>.
     */
    @Inject
    public ExpressionController(final Configuration configuration,
            final ExpressionRepository expressionRepository) {
        this(configuration.getInt("expression.limit", DEFAULT_MAX_LIMIT), expressionRepository);
    }

    /**
     * Adds an expression in the expression repository.
     *
     * @return Ok if the alert was created or updated successfully, a failure HTTP status code otherwise.
     */
    public Result addOrUpdate() {
        final Expression expression;
        try {
            final models.view.Expression viewExpression = buildViewExpression(request().body());
            expression = convertToInternalExpression(viewExpression);
        } catch (final IOException e) {
            LOGGER.error().setMessage("Failed to build an expression.").setThrowable(e).log();
            return badRequest("Invalid request body.");
        }

        try {
            _expressionRepository.addOrUpdateExpression(expression);
            // CHECKSTYLE.OFF: IllegalCatch - Convert any exception to 500
        } catch (final Exception e) {
            // CHECKSTYLE.ON: IllegalCatch
            LOGGER.error().setMessage("Failed to add an expression.").setThrowable(e).log();
            return internalServerError();
        }
        return ok();
    }

    /**
     * Query for expressions.
     *
     * * @param contains The text to search for. Optional.
     * @param cluster The cluster of the statistic to evaluate as part of the exression. Optional.
     * @param service The service of the statistic to evaluate as part of the expression. Optional.
     * @param limit The maximum number of results to return. Optional.
     * @param offset The number of results to skip. Optional.
     * @return <code>Result</code> paginated matching expressions.
     */
    // CHECKSTYLE.OFF: ParameterNameCheck - Names must match query parameters.
    public Result query(final String contains, final String cluster, final String service, final Integer limit,
            final Integer offset) {
        // CHECKSTYLE.ON: ParameterNameCheck

        // Convert and validate parameters
        final Optional<String> argContains = Optional.ofNullable(contains);
        final Optional<String> argCluster = Optional.ofNullable(cluster);
        final Optional<String> argService = Optional.ofNullable(service);
        final Optional<Integer> argOffset = Optional.ofNullable(offset);
        final int argLimit = Math.min(_maxLimit, Optional.of(MoreObjects.firstNonNull(limit, _maxLimit)).get());
        if (argLimit < 0) {
            return badRequest("Invalid limit; must be greater than or equal to 0");
        }
        if (argOffset.isPresent() && argOffset.get() < 0) {
            return badRequest("Invalid offset; must be greater than or equal to 0");
        }

        // Build conditions map
        final Map<String, String> conditions = Maps.newHashMap();
        if (argContains.isPresent()) {
            conditions.put("contains", argContains.get());
        }
        if (argCluster.isPresent()) {
            conditions.put("cluster", argCluster.get());
        }
        if (argService.isPresent()) {
            conditions.put("service", argService.get());
        }

        // Build a host repository query
        final ExpressionQuery query = _expressionRepository.createQuery().contains(argContains).service(argService)
                .cluster(argCluster).limit(argLimit).offset(argOffset);

        // Execute the query
        final QueryResult<Expression> result;
        try {
            result = query.execute();
            // CHECKSTYLE.OFF: IllegalCatch - Convert any exception to 500
        } catch (final Exception e) {
            // CHECKSTYLE.ON: IllegalCatch
            LOGGER.error().setMessage("Expression query failed").setThrowable(e).log();
            return internalServerError();
        }

        // Wrap the query results and return as JSON
        if (result.etag().isPresent()) {
            response().setHeader(HttpHeaders.ETAG, result.etag().get());
        }
        return ok(Json.toJson(new PagedContainer<>(
                result.values().stream().map(this::internalModelToViewModel).collect(Collectors.toList()),
                new Pagination(request().path(), result.total(), result.values().size(), argLimit, argOffset,
                        conditions))));
    }

    private models.view.Expression buildViewExpression(final Http.RequestBody body) throws IOException {
        final JsonNode jsonBody = body.asJson();
        if (jsonBody == null) {
            throw new IOException();
        }
        return OBJECT_MAPPER.readValue(jsonBody.toString(), models.view.Expression.class);
    }

    private Expression convertToInternalExpression(final models.view.Expression viewExpression) throws IOException {
        try {
            return new DefaultExpression.Builder()
                    .setId(viewExpression.getId() == null ? null : UUID.fromString(viewExpression.getId()))
                    .setCluster(viewExpression.getCluster()).setMetric(viewExpression.getMetric())
                    .setService(viewExpression.getService()).setScript(viewExpression.getScript()).build();
            // CHECKSTYLE.OFF: IllegalCatch - Translate any failure to bad input.
        } catch (final RuntimeException e) {
            // CHECKSTYLE.ON: IllegalCatch
            throw new IOException(e);
        }
    }

    private models.view.Expression internalModelToViewModel(final Expression expression) {
        final models.view.Expression viewExpression = new models.view.Expression();
        viewExpression.setCluster(expression.getCluster());
        viewExpression.setId(expression.getId().toString());
        viewExpression.setMetric(expression.getMetric());
        viewExpression.setScript(expression.getScript());
        viewExpression.setService(expression.getService());
        return viewExpression;
    }

    /**
     * Get specific expression.
     *
     * @param id The identifier of the expression.
     * @return Matching expression.
     */
    public Result get(final String id) {
        final UUID identifier = UUID.fromString(id);
        final Optional<Expression> result = _expressionRepository.get(identifier);
        if (!result.isPresent()) {
            return notFound();
        }
        // Return as JSON
        return ok(Json.toJson(result.get()));
    }

    private ExpressionController(final int maxLimit, final ExpressionRepository expressionRepository) {
        _maxLimit = maxLimit;
        _expressionRepository = expressionRepository;
    }

    private final int _maxLimit;
    private final ExpressionRepository _expressionRepository;

    private static final int DEFAULT_MAX_LIMIT = 1000;
    private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionController.class);
    private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getInstance();
}