com.carrotgarden.nexus.aws.s3.publish.amazon.AmazonServiceProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.carrotgarden.nexus.aws.s3.publish.amazon.AmazonServiceProvider.java

Source

/**
 * Copyright (C) 2010-2012 Andrei Pozolotin <Andrei.Pozolotin@gmail.com>
 *
 * All rights reserved. Licensed under the OSI BSD License.
 *
 * http://www.opensource.org/licenses/bsd-license.php
 */
package com.carrotgarden.nexus.aws.s3.publish.amazon;

import static com.carrotgarden.nexus.aws.s3.publish.util.PathHelp.*;

import java.io.File;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.nexus.threads.NexusThreadFactory;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.GetBucketLocationRequest;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.carrotgarden.nexus.aws.s3.publish.config.ConfigBean;
import com.carrotgarden.nexus.aws.s3.publish.config.ConfigEntry;
import com.carrotgarden.nexus.aws.s3.publish.mailer.CarrotMailer;
import com.carrotgarden.nexus.aws.s3.publish.mailer.Report;
import com.carrotgarden.nexus.aws.s3.publish.metrics.AmazonReporter;
import com.carrotgarden.nexus.aws.s3.publish.metrics.Reporter;
import com.carrotgarden.nexus.aws.s3.publish.util.ConfigHelp;
import com.google.inject.assistedinject.Assisted;
import com.yammer.metrics.core.Gauge;

/**
 */
@Named(AmazonServiceProvider.NAME)
public class AmazonServiceProvider implements AmazonService, AmazonManager {

    public static final String NAME = "carrot.amazon.provider";

    private static final NexusThreadFactory threadFactory = //
            new NexusThreadFactory("carrot", NAME);

    private static final ScheduledExecutorService scheduler = //
            Executors.newScheduledThreadPool(1, threadFactory);

    private final AmazonReporter reporter;
    private final CarrotMailer mailer;
    private final ConfigEntry entry;

    @Inject
    public AmazonServiceProvider( //
            final CarrotMailer mailer, //
            final AmazonReporter reporter, //
            @Assisted final ConfigEntry entry //

    ) {

        this.entry = entry;
        this.mailer = mailer;
        this.reporter = reporter;

        reporter.newGauge("provider failure", new Gauge<String>() {
            @Override
            public String value() {
                return "" + failure;
            }
        });
        reporter.newGauge("provider available", new Gauge<Boolean>() {
            @Override
            public Boolean value() {
                return isAvailable();
            }
        });

    }

    private long checkCount;

    private volatile AmazonS3Client client;

    private volatile ConfigBean configBean;

    private volatile ScheduledFuture<?> healthFuture;

    private final Runnable healtTask = new Runnable() {
        @Override
        public void run() {
            checkAvailable();
        }
    };

    private boolean isAvailable;

    protected final Logger log = LoggerFactory.getLogger(getClass());

    private long amazonThrottleExpiration() {
        return ConfigHelp.reference().getMilliseconds("amazon-provider.throttle-expiration");
    }

    private boolean amazonThrottleExceptions() {
        return ConfigHelp.reference().getBoolean("amazon-provider.throttle-exceptions");
    }

    // private final Cache<String, String> exceptionCache = CacheBuilder
    // .newBuilder()
    // .maximumSize(1000)
    // .expireAfterWrite(amazonThrottleExpiration(), TimeUnit.MILLISECONDS)
    // .build();

    private synchronized void checkAvailable() {

        reporter.requestCheckCount.inc();
        reporter.requestTotalCount.inc();

        try {

            final GetBucketLocationRequest request = //
                    new GetBucketLocationRequest(mavenBucket());

            final String result = client.getBucketLocation(request);

            setAvailable(true, null);

        } catch (final Exception e) {

            setAvailable(false, e);

        }

        mailer.sendAmazonReport(Report.AMAZON_HEALTH_REPORT, entry, this);

        checkCount++;

    }

    @Override
    public synchronized void configure(final ConfigBean config) {
        this.configBean = config;
    }

    private class PluginCredentialsProvider implements AWSCredentialsProvider {
        public AWSCredentials getCredentials() {
            final String username = configBean.awsAccess();
            final String password = configBean.awsSecret();

            if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) {
                return new BasicAWSCredentials(username, password);
            } else {
                return new InstanceProfileCredentialsProvider().getCredentials();
            }
        }

        public void refresh() {
        }
    }

    @Override
    public boolean isAvailable() {
        return isAvailable;
    }

    private boolean isFirstScheck() {
        return checkCount == 0;
    }

    private String mavenBucket() {
        return configBean.bucket();
    }

    private String mavenRepoKey(final String path) {
        return configBean.prefix() + rootFullPath(path);
    }

    @Override
    public boolean kill(final String path) {

        reporter.requestKillCount.inc();
        reporter.requestTotalCount.inc();

        try {

            final DeleteObjectRequest request = //
                    new DeleteObjectRequest(mavenBucket(), mavenRepoKey(path));

            client.deleteObject(request);

            setAvailable(true, null);

            return true;

        } catch (final Exception e) {

            setAvailable(true, e);

            return false;

        }

    }

    @Override
    public boolean load(final String path, final File file) {

        reporter.requestLoadCount.inc();
        reporter.requestTotalCount.inc();

        try {

            final GetObjectRequest request = //
                    new GetObjectRequest(mavenBucket(), mavenRepoKey(path));

            final ObjectMetadata result = client.getObject(request, file);

            reporter.fileLoadCount.inc();
            reporter.fileLoadSize.inc(file.length());
            reporter.fileLoadWatch.add(file);

            setAvailable(true, null);

            return true;

        } catch (final AmazonS3Exception e) {

            switch (e.getStatusCode()) {
            case 404:
                log.error("path={} code={}", path, e.getErrorCode());
                break;
            default:
                setAvailable(true, e);
                break;
            }
            return false;

        } catch (final Exception e) {

            setAvailable(false, e);

            return false;

        }

    }

    protected void processFailure(final Throwable e) {

        failure = e;

        reporter.requestFailedCount.inc();

        final String message = "" + e.getMessage();

        // if (amazonThrottleExceptions()) {
        // if (exceptionCache.getIfPresent(message) != null) {
        // return;
        // } else {
        // exceptionCache.put(message, "");
        // }
        // }

        log.error("amazon falilure", e);

    }

    @Override
    public boolean save(final String path, final File file) {

        reporter.requestSaveCount.inc();
        reporter.requestTotalCount.inc();

        try {

            final PutObjectRequest request = //
                    new PutObjectRequest(mavenBucket(), mavenRepoKey(path), file);

            final PutObjectResult result = client.putObject(request);

            reporter.fileSaveCount.inc();
            reporter.fileSaveSize.inc(file.length());
            reporter.fileSaveWatch.add(file);

            setAvailable(true, null);

            return true;

        } catch (final Exception e) {

            setAvailable(false, e);

            return false;

        }

    }

    private String amazonId() {
        return "[" + entry.configId() + "]";
    }

    protected void setAvailable(final boolean nextAvailable, final Throwable e) {

        failure = e;

        final boolean pastAvailable = isAvailable;

        final boolean isChanged = pastAvailable ^ nextAvailable;

        isAvailable = nextAvailable;

        if (isChanged && isAvailable) {

            log.warn("amazon became available : {}", amazonId());

            mailer.sendAmazonReport(Report.AMAZON_AVAILABLE, entry, this);

        }

        if (isChanged && !isAvailable) {

            log.error("amazon became unavailable : {}", amazonId());

            mailer.sendAmazonReport(Report.AMAZON_UNAVAILABLE, entry, this);

        }

    }

    @Override
    public synchronized void ensure() {

        if (isRunning) {
            stop();
            start();
        } else {
            start();
        }

    }

    private boolean isRunning;

    @Override
    public synchronized void start() {

        if (configBean == null) {
            throw new IllegalStateException("config is missing");
        }

        if (client == null) {

            client = new AmazonS3Client(new PluginCredentialsProvider());
            client.setEndpoint(configBean.endpoint());
        } else {
            // throw new IllegalStateException("client is present");
        }

        if (healthFuture == null) {
            final int period = configBean.healthPeriod();
            healthFuture = scheduler.scheduleAtFixedRate( //
                    healtTask, 0, period, TimeUnit.SECONDS);
        } else {
            // throw new IllegalStateException("future is present");
        }

        checkCount = 0;

        isRunning = true;

        log.info("\n\t ### start");

    }

    @Override
    public synchronized void stop() {

        if (healthFuture == null) {
            // throw new IllegalStateException("future is missing");
        } else {
            healthFuture.cancel(true);
            healthFuture = null;
        }

        if (client == null) {
            // throw new IllegalStateException("client is missing");
        } else {
            client.shutdown();
            client = null;
        }

        isRunning = false;

        log.info("\n\t ### stop");

    }

    @Override
    public Reporter reporter() {
        return reporter;
    }

    private Throwable failure;

    @Override
    public Throwable failure() {
        return failure;
    }

}