Java SDK

This page documents the usage of the Amazon Yojaka Java SDK. The Java SDK provides in-built support for handling the authentication of your requests using the AWS Sig V4 signing algorithm. It also provides utility classes that handle the automatic refresh of the LWA access token upon the token’s expiry.

AmazonYojaka Client

The main entry point class of the Java SDK is the com.amazon.yojaka.AmazonYojaka class.

Creating a client

To create a client, use the client builder. You can obtain an instance of the builder via a static factory method located on the client interface.

import com.amazon.yojaka.AmazonYojaka;

AmazonYojakaClientBuilder builder = AmazonYojaka.builder();
AmazonYojaka amazonYojaka = builder.build();

The builder exposes many fluent configuration methods that can be chained to configure a service client. Here’s a simple example that sets a few optional configuration options and then builds the service client.

import com.amazon.yojaka.AmazonYojaka;
import com.amazonaws.opensdk.config.ConnectionConfiguration;
import com.amazonaws.opensdk.config.TimeoutConfiguration;

AmazonYojaka amazonYojaka = AmazonYojaka.builder()
    .connectionConfiguration(new ConnectionConfiguration()
            .maxConnections(100)
            .connectionMaxIdleMillis(1000))
    .timeoutConfiguration(new TimeoutConfiguration()
            .httpRequestTimeout(3000)
            .totalExecutionTimeout(10000)
            .socketTimeout(2000))
    .build();

Endpoint Configuration

By default, a client created by the SDK will connect to the Amazon Yojaka sandbox running in the eu-west-1 region. You can override the endpoint that the SDK invokes by using the com.amazon.yojaka.util.Endpoints class, and specifying the endpoint to use while creating the AmazonYojaka client instance.

To connect to the productio instance of Amazon Yojaka in the eu-west-1 region, use the following snippet while creating the AmazonYojaka client instance.

import com.amazon.yojaka.AmazonYojaka;
import com.amazon.yojaka.util.Endpoints;

AmazonYojaka amazonYojaka = AmazonYojaka.builder()
    .endpoint(Endpoints.url(Endpoints.STAGE_PRODUCTION, Endpoints.REGON_EU_WEST_1))
    .build();

Client Lifecycle

Each client that you create has its own connection pool. It is recommended to treat the client instances as long-lived objects. Clients are immutable and thread safe. Clients clean up their resources when garbage collected but if you want to explicitly shut down the client you can do the following:

import com.amazon.yojaka.AmazonYojaka;

AmazonYojaka amazonYojaka = AmazonYojaka.builder().build();
// Use the AmazonYojaka client

amazonYojaka.shutdown();
// AmazonYojaka client is now unusable

Authentication

The AmazonYojaka client natively supports AWS Sig V4 based authentication using AWS IAM credentials. To configure the AWS credentials used by the client, use the iamCredentials fluent setter.

import com.amazon.yojaka.AmazonYojaka;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider;
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;


// Create an AWS STS client using the AWS IAM user credentials
AWSSecurityTokenService awsSecurityTokenService = AWSSecurityTokenServiceClientBuilder.standard()
    .withRegion(Regions.EU_WEST_1)
    // Use the access key and secret access key of the AWS IAM user that you have on-boarded with
    // Amazon Yojaka
    .withCredentials(new AWSStaticCredentialsProvider(
        new BasicAWSCredentials("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY")))
    .build();

// Create an AWS STS assume role credentials provider that will assume the role assigned to your connector
STSAssumeRoleSessionCredentialsProvider stsAssumeRoleSessionCredentialsProvider =
    // Use the ARN of the AWS IAM role created by the Amazon Yojaka team as part of on-boarding
    new STSAssumeRoleSessionCredentialsProvider.Builder("AWS_IAM_ROLE_ARN", "ConnectorInstanceName")
        // Use the AWS STS client created above
        .withStsClient(awsSecurityTokenService)
        // The maximum duration of the assume role credentials is 4 hours
        // This credentials provider class will automatically refresh the session credentials upon expiry
        .withRoleSessionDurationSeconds(4 * 60 * 60)
        .build();

AmazonYojaka amazonYojaka = AmazonYojaka.builder()
    // Use the STS assume role credentials provider created above
    .iamCredentials(stsAssumeRoleSessionCredentialsProvider)
    .build();

Authorization

The Amazon Yojaka Java SDK provides a utility class to handle the automatic refresh of the LWA access token when it expires. This class assumes that you have performed all the steps documented in the Authorization section and already have the LWA refresh token for every seller your connector works with.

The LWA utility class is com.amazon.yojaka.lwa.LWAHandler. It is recommended to create one instance of this class for your application and use it for the duration of your application running. This class is thread-safe.

Initialization

Initialize the LWAHandler class with your application’s LWA client_id and client_secret.

import com.amazon.yojaka.lwa.LWAHandler;

// Use your application's client_id and client_secret
LWAHandler lwaHandler = LWAHandler.builder()
        .clientId("amzn1.application-oa2-client.10a...")
        .clientSecret("f477...")
        .build();

Usage

To use the LWAHandler class, first register a seller identifier and the LWA refresh token that is associated with the seller. Note that the seller identifier can be any unique identifier in your connector application that you use to uniquely identify every seller that your connector works with.

lwaHandler.registerSellerRefreshToken("sellerId123",
    "Atzr|IwEBIPiK4h...");

At the time of making an Amazon Yojaka API call, you will need to fetch an LWA access token for the seller on whose behalf the API call is being invoked. This method will return a cached LWA access token if it is unexpired, and will automatically fetch and return a new LWA access token, if the currently cached access token is nearing expiry.

String amzAccessToken = lwaHandler.getAccessToken("sellerId123");

Note

The LWAHandler class provided by this Java SDK uses a lazy initialization behaviour. This means that a new LWA access token is fetched only when needed. This behaviour is used both when the LWA access token is requested the first time or when the LWA access token is requested the first time after it has expired.

The LWAHandler marks LWA access tokens as expired 55 minutes after they are fetched.

API Requests

After a client is configured and created, you can make a request to the service. A method on the AmazonYojaka interface is available for all actions (resource + method) in the Amazon Yojaka API.

For each API method, classes are available that represent the request and response of that API. The request class has fluent setters for any path parameters, query parameters, headers, and payload model that are defined in the API. The response class exposes getters for any modeled headers and for the modeled payload.

Example Usage

Below are some usage examples.

UpdateInventory

import com.amazon.yojaka.AmazonYojaka;
import com.amazon.yojaka.model.UpdateInventoryRequest;
import com.amazon.yojaka.model.UpdateInventoryResult;

// This snippet assumes that an AmazonYojaka and LWAHandler instance are available
// Create an UpdateInventoryRequest object and invoke the method
UpdateInventoryRequest updateInventoryRequest = new UpdateInventoryRequest()
        .amzAccessToken(lwaHandler.getAccessToken("sellerId123"))
        .inventoryUpdateSequence("100")
        .locationId("896e68a8-990f-4d5a-994d-f9877a1ef46d")
        .quantity("25000")
        .skuId("TestSku1");
UpdateInventoryResult updateInventoryResult = amazonYojaka.updateInventory(updateInventoryRequest);

ListOrders

import com.amazon.yojaka.model.ListOrdersRequest;
import com.amazon.yojaka.model.ListOrdersResult;
import com.amazon.yojaka.model.Order;
import com.amazon.yojaka.model.Status;

import java.time.Duration;
import java.time.Instant;
import java.util.List;

// This snippet assumes that an AmazonYojaka and LWAHandler instance are available
// Create a ListOrdersRequest object and invoke the method
ListOrdersRequest listOrdersRequest = new ListOrdersRequest()
        .amzAccessToken(lwaHandler.getAccessToken("sellerId123"))
        // List all orders received in the past hour
        .fromTimestamp(String.valueOf(Instant.now().minus(Duration.ofMinutes(60)).toEpochMilli()))
        .locationId("896e68a8-990f-4d5a-9944-f9877a7ef36d")
        // List only 10 orders
        .maxResults(String.valueOf(10))
        // List only those orders which are in the ACCEPTED state
        .status(Status.ACCEPTED.toString());
ListOrdersResult listOrdersResult = amazonYojaka.listOrders(listOrdersRequest);

// The list of orders fetched
List<Order> orders = listOrdersResult.getListOrdersOutput().getOrders();

Request Configuration

In addition to client-level configuration that you can specify when creating the AmazonYojaka client, each request class exposes configuration methods that are scoped to that request alone. Request level configuration takes precedence over client level configuration.

import com.amazon.yojaka.model.ConfirmOrderRequest;
import com.amazonaws.opensdk.SdkRequestConfig;

amazonYojaka.confirmOrder(new ConfirmOrderRequest()
        .amzAccessToken(lwaHandler.getAccessToken("sellerId123"))
        .id("123-abc")
        .sdkRequestConfig(SdkRequestConfig.builder()
            // Specify request specific timeouts
            .httpRequestTimeout(1500)
            .totalExecutionTimeout(5000)
            .build()));

Response Metadata

In addition to the API modeled data present in result objects, the SDK exposes access to additional HTTP metadata. This metadata is typically used for debugging issues.

import com.amazon.yojaka.model.ShipOrderRequest;
import com.amazon.yojaka.model.ShipOrderResult;

ShipOrderResult shipOrderResult = amazonYojaka.shipOrder(new ShipOrderRequest()
        .amzAccessToken(accessToken)
        .id("123-abc"));

// Access the requestId, the underlying HTTP status code or a HTTP header in the response
System.out.println(shipOrderResult.sdkResponseMetadata().requestId());
System.out.println(shipOrderResult.sdkResponseMetadata().httpStatusCode());
shipOrderResult.sdkResponseMetadata().header("Content-Length").ifPresent(System.out::println);

Exception Handling

Your connector application must handle all exceptions raised by the AmazonYojaka Java SDK. Service and client exceptions can be handled separately.

All exceptions that can be thrown by the SDK are a subtype of com.amazonaws.SdkBaseException which is a RuntimeException. To handle both service and client exceptions in the same way, you can directly catch this exception.

import com.amazonaws.SdkBaseException;

try {
    amazonYojaka.confirmOrder(...);
} catch (SdkBaseException e) {
    // All exceptions thrown from the client will be a subtype of SdkBaseException.
}

Service Exceptions

All exceptions modeled in the Amazon Yojaka API will be a sub-type of com.amazon.yojaka.model.AmazonYojakaException. All service exceptions expose metadata about the HTTP response for logging or debugging purposes.

try {
    amazonYojaka.createPackages(...);
} catch (AmazonYojakaException e) {
    int statusCode = e.sdkHttpMetadata().httpStatusCode();
    String requestId = e.sdkHttpMetadata().requestId();
    Optional<String> contentLength = e.sdkHttpMetadata().header("Content-Length");
    ByteBuffer responseContent = e.sdkHttpMetadata().responseContent();
}

The following sub-classes of the AmazonYojakaException are available in the com.amazon.yojaka.model package. These exception classes also provide the exception error code and whether the API is retryable or not as defined in the Error Handling section.

  • BadRequestException

  • ConflictException

  • ForbiddenException

  • InternalServerErrorException

  • NotFoundException

  • ServiceUnavailableException

  • UnauthorizedException

Client Exceptions

Client exceptions are modeled as sub-classes of the com.amazonaws.SdkClientException class. This provides greater granularity to deal with client-side exceptions.

import com.amazonaws.AboredException;
import com.amazonaws.SdkClientException;
import com.amazonaws.http.timers.client.ClientExecutionTimeoutException;

try {
    amazonYojaka.getInventory(...);
} catch (ClientExecutionTimeoutException e) {
    // Specific client exception thrown when the totalExecutionTimeout is triggered.
} catch (AbortedException e) {
    // Thrown when the client thread is interrupted while making a request.
} catch (SdkClientException e) {
    // All other exceptions can be handled here.
}

Encryption & Decryption

The SDK provides a utility class to simplify the process of encryption and decryption of any secure data that you exchange with the Amazon Yojaka APIs. The utility class is com.amazon.yojaka.crypto.CryptoUtils.

This class has methods to decrypt data from the com.amazon.yojaka.model.EncryptedData object returned by an Amazon Yojaka API response. Similarly, it has methods to encrypt data into a com.amazon.yojaka.model.EncryptedData object which you can then use in a request to the Amazon Yojaka API.

Tip

Instances of the com.amazon.yojaka.crypto.CryptoUtils class are thread-safe and a single instance can be used throughout the lifetime of your application.

The methods exposed by this class are:

  • decrypt - Decrypts encrypted data and returns a byte[]

  • decryptToString - Decrypts encrypted data and returns a String

  • encrypt - Encrypts a byte[]

  • encryptFromString - Encrypts a String

Note

Note that the encryptFromString and decryptToString methods in the com.amazon.yojaka.crypto.CryptoUtils class assume that the data being encrypted or decrypted is textual data in the UTF-8 character encoding. To decrypt binary data such as an invoice or a ship-label, use the plain decrypt method that returns a byte[].

Examples

Below are some usage examples.

Decrypting an Invoice

import com.amazon.yojaka.crypto.CryptoUtils;
import com.amazon.yojaka.model.GenerateInvoiceRequest;
import com.amazon.yojaka.model.Invoice;

// This snippet assumes that an STAAssumeRoleSessionCredentialsProvider, AmazonYojaka and LWAHandler instance are available
// Create a GenerateInvoiceRequest object and invoke the method
Invoice invoice = amazonYojaka.generateInvoice(new GenerateInvoiceRequest()
            .amzAccessToken(accessToken)
            .id("123-abc"))
        .getInvoice();

// Use the AWS Key Management Service Customer Managed Key ARN that you would have received for every seller that
// you work with to decrypt the invoice data
CryptoUtils cryptoUtils = CryptoUtils.builder()
        .awsCredentialsProvider(stsAssumeRoleSessionCredentialsProvider)
        .build();
byte[] invoiceData = cryptoUtils.decrypt(sellerKeyArn, invoice.getFileData().getEncryptedContent());

Encrypting a Serial Number

import com.amazon.yojaka.crypto.CryptoUtils;
import com.amazon.yojaka.model.Context;
import com.amazon.yojaka.model.CreatePackagesRequest;
import com.amazon.yojaka.model.Dimension;
import com.amazon.yojaka.model.LineItem;
import com.amazon.yojaka.model.Package;
import com.amazon.yojaka.model.PackagedLineItemsElement;
import com.amazon.yojaka.model.Weight;

// This snippet assumes that an STAAssumeRoleSessionCredentialsProvider, AmazonYojaka and LWAHandler instance are available
// Use the AWS Key Management Service Customer Managed Key ARN that you would have received for every seller that
// you work with to decrypt the invoice data
CryptoUtils cryptoUtils = CryptoUtils.builder()
        .awsCredentialsProvider(stsAssumeRoleSessionCredentialsProvider)
        .build();
EncryptedData encryptedSerialNumber = cryptoUtils.encryptFromString("SERIAL-NUM-1", sellerKeyArn,
        new EncryptionInfo()
                .context(Context.SerialNumber)
                .type(Type.AWS_KMS));

// Create a CreatePackagesRequest object and set the encrypted serial number within that
amazonYojaka.createPackages(new CreatePackagesRequest()
        .amzAccessToken(accessToken)
        .createPackagesInput(new CreatePackagesInput()
                .packages(new Package()
                        .id("123")
                        .length(new Dimension()
                                .value(10.0)
                                .dimensionUnit(DimensionUnit.CM))
                        .width(new Dimension()
                                .value(20.0)
                                .dimensionUnit(DimensionUnit.CM))
                        .height(new Dimension()
                                .value(30.0)
                                .dimensionUnit(DimensionUnit.CM))
                        .weight(new Weight()
                                .value(1.0)
                                .weightUnit(WeightUnit.Kilograms))
                        .packagedLineItems(new PackagedLineItemsElement()
                                .lineItem(new LineItem()
                                        .id(order.getLineItems().get(0).getId()))
                                        .quantity(order.getLineItems().get(0).getNumberOfUnits())
                                        .serialNumbers(encryptedSerialNumber)))));

Retries

Out of the box, the AmazonYojaka client retries on throttling errors (HTTP status code 429) and connection exceptions. If a different retry policy is desired, a custom one can be set via the client builder.

The easiest way to create a custom retry policy is to use the com.amazonaws.opensdk.retry.RetryPolicyBuilder. It provides a declarative API to specify when to retry.

import com.amazonaws.opensdk.retry.RetryPolicyBuilder;
import com.amazon.yojaka.model.InternalServerErrorException;

import java.net.SocketTimeoutException;

/**
 * The policy below will retry if the cause of the failed request matches any of the exceptions
 * given OR if the HTTP response from the service has one of the provided status codes.
 */
AmazonYojaka amazonYojaka = AmazonYojaka.builder()
    .retryPolicy(RetryPolicyBuilder.standard()
            .retryOnExceptions(InternalServerErrorException.class, SocketTimeoutException.class)
            .retryOnStatusCodes(429, 503)
            .maxNumberOfRetries(10)
            .fixedBackoff(100)
            .build())
    .build();

You can also directly implement the com.amazonaws.retry.v2.RetryPolicy interface to define your own implementation. com.amazonaws.retry.v2.RetryPolicyContext contains useful metadata about the state of the failed request that can be used to drive dynamic retry decisions or compute backoff delays.

import com.amazonaws.retry.v2.RetryPolicy;
import com.amazonaws.retry.v2.RetryPolicyContext;

/**
 * Simple implementation of com.amazonaws.retry.v2.RetryPolicy
 */
public static class CustomRetryPolicy implements RetryPolicy {
    @Override
    public long computeDelayBeforeNextRetry(RetryPolicyContext context) {
        return 100;
    }

    @Override
    public boolean shouldRetry(RetryPolicyContext context) {
        return context.retriesAttempted() < 3;
    }
}

// Using a custom retry policy via the builder
AmazonYojaka amazonYojaka = AmazonYojaka.builder()
        .retryPolicy(new CustomRetryPolicy())
        .build();

On this page