content: /*
   * Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License").
   * You may not use this file except in compliance with the License.
   * A copy of the License is located at
   *
   *  http://aws.amazon.com/apache2.0
   *
   * or in the "license" file accompanying this file. This file 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.amazon.janusgraph.diskstorage.dynamodb;
  
  import org.janusgraph.diskstorage.BackendException;
  import org.janusgraph.diskstorage.TemporaryBackendException;
  
  import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
  import com.amazonaws.services.dynamodbv2.model.DeleteItemResult;
  import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
  import com.amazonaws.services.dynamodbv2.model.GetItemResult;
  import com.amazonaws.services.dynamodbv2.model.QueryRequest;
  import com.amazonaws.services.dynamodbv2.model.QueryResult;
  import com.amazonaws.services.dynamodbv2.model.ScanRequest;
  import com.amazonaws.services.dynamodbv2.model.ScanResult;
  import com.amazonaws.services.dynamodbv2.model.UpdateItemRequest;
  import com.amazonaws.services.dynamodbv2.model.UpdateItemResult;
  
  /**
   * A wrapper for a client-side exponential backoff retry strategy for DynamoDB API calls
   * @author Alexander Patrikalakis
   *
   * @param <R> the type of AWS request that is input
   * @param <A> the type of AWS request that is returned
   *
   */
  public abstract class ExponentialBackoff<R, A> {
      private static final String RETRIES = "Retries";
      static final String SCAN_RETRIES = DynamoDbDelegate.SCAN + RETRIES;
      static final String QUERY_RETRIES = DynamoDbDelegate.QUERY + RETRIES;
      static final String UPDATE_ITEM_RETRIES = DynamoDbDelegate.UPDATE_ITEM + RETRIES;
      static final String DELETE_ITEM_RETRIES = DynamoDbDelegate.DELETE_ITEM + RETRIES;
      static final String GET_ITEM_RETRIES = DynamoDbDelegate.GET_ITEM + RETRIES;
  
      public static final class Scan extends ExponentialBackoff<ScanRequest, ScanResult> {
          private final int permits;
          public Scan(final ScanRequest request, final DynamoDbDelegate delegate, final int permits) {
              super(request, delegate, SCAN_RETRIES);
              this.permits = permits;
          }
          @Override
          protected ScanResult call() throws BackendException {
              return delegate.scan(request, permits);
          }
          @Override
          protected String getTableName() {
              return request.getTableName();
          }
  
      }
  
      public static final class Query extends ExponentialBackoff<QueryRequest, QueryResult> {
          private final int permits;
          public Query(final QueryRequest request, final DynamoDbDelegate delegate, final int permits) {
              super(request, delegate, QUERY_RETRIES);
              this.permits = permits;
          }
          @Override
          protected QueryResult call() throws BackendException {
              return delegate.query(request, permits);
          }
          @Override
          protected String getTableName() {
              return request.getTableName();
          }
  
      }
  
      public static final class UpdateItem extends ExponentialBackoff<UpdateItemRequest, UpdateItemResult> {
          public UpdateItem(final UpdateItemRequest request, final DynamoDbDelegate delegate) {
              super(request, delegate, UPDATE_ITEM_RETRIES);
          }
          @Override
          protected UpdateItemResult call() throws BackendException {
              return delegate.updateItem(request);
          }
          @Override
          protected String getTableName() {
              return request.getTableName();
          }
  
      }
  
      public static final class DeleteItem extends ExponentialBackoff<DeleteItemRequest, DeleteItemResult> {
          public DeleteItem(final DeleteItemRequest request, final DynamoDbDelegate delegate) {
              super(request, delegate, DELETE_ITEM_RETRIES);
          }
          @Override
          protected DeleteItemResult call() throws BackendException {
              return delegate.deleteItem(request);
          }
          @Override
          protected String getTableName() {
              return request.getTableName();
          }
  
      }
  
      public static final class GetItem extends ExponentialBackoff<GetItemRequest, GetItemResult> {
          public GetItem(final GetItemRequest request, final DynamoDbDelegate delegate) {
              super(request, delegate, GET_ITEM_RETRIES);
          }
          @Override
          protected GetItemResult call() throws BackendException {
              return delegate.getItem(request);
          }
          @Override
          protected String getTableName() {
              return request.getTableName();
          }
  
      }
  
      private long exponentialBackoffTime;
      private long tries;
      private final String apiNameRetries;
      protected final R request; //CHECKSTYLE:SUPPRESS - needs to be protected
      protected A result; //CHECKSTYLE:SUPPRESS - needs to be protected
      protected final DynamoDbDelegate delegate;
      ExponentialBackoff(final R requestType, final DynamoDbDelegate delegate, final String apiNameTries) {
          this.request = requestType;
          this.delegate = delegate;
          this.exponentialBackoffTime = delegate.getRetryMillis();
          this.result = null;
          this.tries = 0;
          this.apiNameRetries = apiNameTries;
      }
      protected abstract A call() throws BackendException;
      protected abstract String getTableName();
  
      public A runWithBackoff() throws BackendException {
          boolean interrupted = false;
          try {
              do {
                  interrupted = runWithBackoffOnce();
              } while (result == null);
              return result;
          } finally {
              //meter tries
              delegate.getMeter(delegate.getMeterName(apiNameRetries, getTableName())).mark(tries - 1);
  
              if (interrupted) {
                  Thread.currentThread().interrupt();
                  throw new BackendRuntimeException("exponential backoff was interrupted");
              }
          }
      }
  
      private boolean runWithBackoffOnce() throws BackendException {
          boolean interrupted = false;
          tries++;
          try {
              result = call();
          } catch (TemporaryBackendException e) { //retriable
              if (tries > delegate.getMaxRetries()) {
                  throw new TemporaryBackendException("Max tries exceeded.", e);
              }
              try {
                  Thread.sleep(exponentialBackoffTime);
              } catch (InterruptedException ie) {
                  interrupted = true;
              } finally {
                  exponentialBackoffTime *= 2;
              }
          }
          return interrupted;
      }
  }
  