All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arpnetworking.configuration.triggers.UriTrigger Maven / Gradle / Ivy

/*
 * 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.configuration.triggers;

import com.arpnetworking.commons.builder.OvalBuilder;
import com.arpnetworking.configuration.Trigger;
import com.arpnetworking.logback.annotations.LogValue;
import com.arpnetworking.steno.LogValueMapFactory;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import net.sf.oval.constraint.NotNull;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * {@link Trigger} implementation based on a uri's last modified date and
 * ETag. Either can trigger a reload; the last modified if is later than the
 * previous value or the ETag if it differs from the previous value. If
 * the uri is unavailable it is not considered changed to prevent flickering
 * caused by connectivity or server issues.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 */
public final class UriTrigger implements Trigger {

    @Override
    public boolean evaluateAndReset() {
        HttpGet request = null;
        try {
            LOGGER.debug()
                    .setMessage("Evaluating trigger")
                    .addData("uri", _uri)
                    .addData("headers", _headers)
                    .log();
            request = new HttpGet(_uri);
            if (!_headers.isEmpty()) {
                request.setHeaders(_headers.toArray(new Header[_headers.size()]));
            }
            if (_previousETag.isPresent()) {
                request.addHeader(HttpHeaders.IF_NONE_MATCH, _previousETag.get());
            }
            if (_previousLastModified.isPresent()) {
                request.addHeader(HttpHeaders.IF_MODIFIED_SINCE, DateUtils.formatDate(_previousLastModified.get()));
            }
            final HttpResponse response = CLIENT.execute(request);
            if (response.getStatusLine().getStatusCode() == 304) {
                LOGGER.debug()
                        .setMessage("Uri unmodified")
                        .addData("uri", _uri)
                        .addData("status", response.getStatusLine().getStatusCode())
                        .log();
                return false;
            }
            if (response.getStatusLine().getStatusCode() / 100 != 2) {
                LOGGER.warn()
                        .setMessage("Failed to retrieve url")
                        .addData("uri", _uri)
                        .addData("status", response.getStatusLine().getStatusCode())
                        .log();
                return false;
            }
            if (response.getFirstHeader(HttpHeaders.ETAG) == null
                    && response.getFirstHeader(HttpHeaders.LAST_MODIFIED) == null) {
                LOGGER.warn()
                        .setMessage("Untriggerable uri missing both etag and last modified")
                        .addData("uri", _uri)
                        .addData("headers", response.getAllHeaders())
                        .log();
                return false;
            }
            return isLastModifiedChanged(response) || isEtagChanged(response);
        } catch (final IOException e) {
            LOGGER.warn()
                    .setMessage("Failed to evaluate url trigger")
                    .addData("uri", _uri)
                    .setThrowable(e)
                    .log();
        } finally {
            if (request != null) {
                request.releaseConnection();
            }
        }

        return false;
    }

    private boolean isEtagChanged(final HttpResponse response) {
        final Header newEtagHeader = response.getFirstHeader(HttpHeaders.ETAG);
        if (newEtagHeader != null) {
            final String newETag = newEtagHeader.getValue();
            if (!_previousETag.isPresent() || !newETag.equals(_previousETag.get())) {
                LOGGER.debug()
                        .setMessage("Uri etag changed")
                        .addData("uri", _uri)
                        .addData("newETag", newETag)
                        .addData("previousETag", _previousETag)
                        .log();
                _previousETag = Optional.of(newETag);
                return true;
            }
        }
        return false;
    }

    private boolean isLastModifiedChanged(final HttpResponse response) {
        final Header newLastModifiedHeader = response.getFirstHeader(HttpHeaders.LAST_MODIFIED);
        if (newLastModifiedHeader != null) {
            final Date newLastModified = DateUtils.parseDate(newLastModifiedHeader.getValue());
            if (newLastModified == null) {
                throw new IllegalArgumentException("Invalid last modified date");
            }
            if (!_previousLastModified.isPresent() || newLastModified.after(_previousLastModified.get())) {
                LOGGER.debug()
                        .setMessage("Uri last modified changed")
                        .addData("uri", _uri)
                        .addData("newLastModified", newLastModified)
                        .addData("previousLastModified", _previousLastModified)
                        .log();
                _previousLastModified = Optional.of(newLastModified);
                return true;
            }
        }
        return false;
    }

    /**
     * Generate a Steno log compatible representation.
     *
     * @return Steno log compatible representation.
     */
    @LogValue
    public Object toLogValue() {
        return LogValueMapFactory.builder(this)
                .put("uri", _uri)
                .put("previousLastModified", _previousLastModified)
                .put("previousETag", _previousETag)
                .put("headers", _headers)
                .build();
    }

    @Override
    public String toString() {
        return toLogValue().toString();
    }

    private UriTrigger(final Builder builder) {
        // The uri trigger should always return true on the first successful
        // evaluation while on subsequent evaluations true should only be
        // returned if the content at the uri was changed since the previous
        // evaluation. To accomplish this a modified time of -1 and a null hash
        // is used.
        _uri = builder._uri;
        _previousLastModified = Optional.empty();
        _previousETag = Optional.empty();
        _headers = new ArrayList<>(builder._headers);
    }

    private final URI _uri;

    private Optional _previousLastModified;
    private Optional _previousETag;
    private List
_headers; private static final Logger LOGGER = LoggerFactory.getLogger(UriTrigger.class); private static final int CONNECTION_TIMEOUT_IN_MILLISECONDS = 3000; private static final HttpClientConnectionManager CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(); private static final HttpClient CLIENT = HttpClientBuilder.create() .setConnectionManager(CONNECTION_MANAGER) .setDefaultRequestConfig( RequestConfig.copy(RequestConfig.DEFAULT) .setConnectTimeout(CONNECTION_TIMEOUT_IN_MILLISECONDS) .build()) .build(); /** * {@link com.arpnetworking.commons.builder.Builder} implementation for * {@link UriTrigger}. */ public static final class Builder extends OvalBuilder { /** * Public constructor. */ public Builder() { super(UriTrigger::new); } /** * Set the source {@link URI}. * * @param value The source {@link URI}. * @return This {@link Builder} instance. */ public Builder setUri(final URI value) { _uri = value; return this; } /** * Add a {@link Header} to the uri. * * @param value A HTTP header. * @return This {@link Builder} instance. */ public Builder addHeader(final Header value) { _headers.add(value); return this; } /** * Add a {@link List} of {@link Header} to the uri. * * @param values A {@link List} of HTTP headers. * @return This {@link Builder} instance. */ public Builder addHeaders(final List
values) { _headers.addAll(values); return this; } /** * Overrides the existing headers with a {@link List} of {@link Header}. * * @param values A {@link List} of HTTP headers. * @return This {@link Builder} instance. */ public Builder setHeaders(final List
values) { _headers = new ArrayList<>(values); return this; } @NotNull private URI _uri; @NotNull private List
_headers = new ArrayList<>(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy