package org.togglz.s3; import com.amazonaws.AmazonClientException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.S3Object; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.togglz.core.Feature; import org.togglz.core.repository.FeatureState; import org.togglz.core.repository.StateRepository; import org.togglz.core.util.FeatureStateStorageWrapper; import java.io.IOException; /** * A state repository that uses Amazon S3. * <p> * The repository is configured using the {@link Builder} * <p> * You must already have a bucket provisioned before you create this repository. * * @author Mark Richardson * @date 12/27/16 */ public class S3StateRepository implements StateRepository { // http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html private static final String ERR_NO_SUCH_KEY = "NoSuchKey"; private final ObjectMapper objectMapper = new ObjectMapper(); private final AmazonS3 client; private final String bucketName; private final String keyPrefix; private S3StateRepository(Builder builder) { this.client = builder.client; this.bucketName = builder.bucketName; this.keyPrefix = builder.keyPrefix; } @Override public FeatureState getFeatureState(Feature feature) { try (S3Object object = client.getObject(bucketName, keyPrefix + feature.name())) { if (object != null) { FeatureStateStorageWrapper wrapper = objectMapper.reader() .forType(FeatureStateStorageWrapper.class) .readValue(object.getObjectContent()); return FeatureStateStorageWrapper.featureStateForWrapper(feature, wrapper); } } catch (AmazonS3Exception ae) { if (ERR_NO_SUCH_KEY.equals(ae.getErrorCode())) { return null; } throw ae; } catch (IOException e) { throw new RuntimeException("Failed to set the feature state", e); } return null; } @Override public void setFeatureState(FeatureState featureState) { try { FeatureStateStorageWrapper storageWrapper = FeatureStateStorageWrapper.wrapperForFeatureState(featureState); String json = objectMapper.writeValueAsString(storageWrapper); client.putObject(bucketName, keyPrefix + featureState.getFeature().name(), json); } catch (AmazonClientException | JsonProcessingException e) { throw new RuntimeException("Failed to set the feature state", e); } } /** * Creates a new builder for creating a {@link S3StateRepository}. * * @param client the client instance to use for connecting to amazon s3 * @param bucketName The name of the bucket to use * @return a Builder */ public static Builder newBuilder(AmazonS3 client, String bucketName) { return new Builder(client, bucketName); } /** * Builder for a {@link S3StateRepository}. */ public static class Builder { private final AmazonS3 client; private final String bucketName; private String keyPrefix = "togglz/"; /** * Creates a new builder for a {@link S3StateRepository}. * * @param client the client instance to use for connecting to amazon s3 * @param bucketName The name of the bucket to use */ public Builder(AmazonS3 client, String bucketName) { this.client = client; this.bucketName = bucketName; } /** * Optional prefixes to prepend on to each key * * @param keyPrefix The prefix to use * @return */ public Builder prefix(String keyPrefix) { this.keyPrefix = keyPrefix == null ? "" : keyPrefix; return this; } /** * Creates a new {@link S3StateRepository} using the current settings. */ public S3StateRepository build() { return new S3StateRepository(this); } } }