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

io.mantisrx.extensions.dynamodb.DynamoDBClientSingleton Maven / Gradle / Ivy

/*
 * Copyright 2024 Netflix, Inc.
 *
 * 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 io.mantisrx.extensions.dynamodb;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClientOptions;
import io.mantisrx.server.core.utils.ConfigUtils;
import java.lang.reflect.Method;
import java.net.URI;
import java.time.Duration;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.swing.SingleSelectionModel;
import org.skife.config.Config;
import org.skife.config.ConfigurationObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
import software.amazon.awssdk.services.sts.model.Credentials;

public class DynamoDBClientSingleton {
    public static final String DYNAMO_DB_PROPERTIES_KEY = "mantis.ext.dynamodb.properties.file";

    private static final Logger logger = LoggerFactory.getLogger(SingleSelectionModel.class);
    private static AmazonDynamoDBLockClient instanceLockClient;
    private static DynamoDbClient instanceClient;
    private static String partitionKey;
    private static DynamoDBConfig conf;

    private DynamoDBClientSingleton() {
        // Private constructor to prevent instantiation
    }

    public static synchronized AmazonDynamoDBLockClient getLockClient() {
        if (instanceLockClient == null) {
            final DynamoDBConfig conf = getDynamoDBConf();
            instanceLockClient = new AmazonDynamoDBLockClient(
                AmazonDynamoDBLockClientOptions.builder(getDynamoDBClient(), conf.getDynamoDBLeaderTable())
                    .withLeaseDuration(Duration.parse(conf.getDynamoDBLeaderLeaseDuration()).toMillis())
                    .withHeartbeatPeriod(Duration.parse(conf.getDynamoDBLeaderHeartbeatDuration()).toMillis())
                    .withCreateHeartbeatBackgroundThread(conf.getDynamoDBLeaderHeartbeatInBackground())
                    .withTimeUnit(TimeUnit.MILLISECONDS)
                    .build());
        }
        return instanceLockClient;
    }

    public static synchronized String getPartitionKey() {
        if(partitionKey == null) {
            partitionKey = getDynamoDBConf().getDynamoDBLeaderKey();

            if (partitionKey == null || partitionKey.isEmpty()) {
                throw new IllegalArgumentException("mantis.ext.dynamodb.leader.key is null or empty and must be set");
            }
        }
        return partitionKey;
    }

    public static synchronized DynamoDbClient getDynamoDBClient() {
        if (instanceClient == null) {
            final DynamoDBConfig conf = getDynamoDBConf();
            final DynamoDbClientBuilder builder = DynamoDbClient.builder();
            if (conf.getDynamoDBRegion() != null && !conf.getDynamoDBRegion().isEmpty()) {
                builder.region(Region.of(conf.getDynamoDBRegion()));
            }
            if (conf.getDynamoDBEndpointOverride() != null && !conf.getDynamoDBEndpointOverride().isEmpty()) {
                final URI uri = URI.create(conf.getDynamoDBEndpointOverride());
                builder.endpointOverride(uri);
                logger.warn("using endpoint override of {}", uri);
            }
            useAssumeRole(conf).ifPresent((c) -> builder.credentialsProvider(StaticCredentialsProvider.create(c)));
            if (conf.getDynamoDBUseLocal()) {
                final AwsBasicCredentials credentials = AwsBasicCredentials
                    .create("fakeAccessKeyId", "fakeSecretAccessKey");
                builder.credentialsProvider(StaticCredentialsProvider.create(credentials));
                logger.warn("using local dynamodb this should not be used in production");
            }
            instanceClient = builder.build();
        }
        return instanceClient;
    }

    public static synchronized DynamoDBConfig getDynamoDBConf() {
        if (conf == null) {
            final String propFile = System.getProperty(DYNAMO_DB_PROPERTIES_KEY, "dynamodb.properties");
            // This checks for conventional env variable overrides
            final Properties dynamodbProps = ConfigUtils.loadProperties(propFile);
            // Make sure the provided class is an interface
            if (!DynamoDBConfig.class.isInterface()) {
                throw new IllegalArgumentException("The class must be an interface.");
            }
            // Iterate over the methods of the interface
            for (Method method : DynamoDBConfig.class.getDeclaredMethods()) {
                // Check if the method is annotated with @Config
                if (method.isAnnotationPresent(Config.class)) {
                    Config config = method.getAnnotation(Config.class);
                    // Override any value that is in java system properties
                    for (String key: config.value()) {
                        final String value = System.getProperty(key);
                        if (value != null && !value.isEmpty()) {
                            dynamodbProps.setProperty(key, System.getProperty(key));
                        }
                    }
                }
            }
            conf = new ConfigurationObjectFactory(dynamodbProps).build(DynamoDBConfig.class);
        }
        return conf;
    }

    private static Optional useAssumeRole(DynamoDBConfig config) {
        final String roleARN = config.getDynamoDBAssumeRoleARN();
        final String roleSessionName = config.getDynamoDBAssumeRoleSessionName();
        if (roleARN == null && roleSessionName == null) {
            return Optional.empty();
        }
        if (roleARN == null || roleARN.isEmpty() || roleSessionName == null || roleSessionName.isEmpty()) {
            throw new IllegalArgumentException(String.format("received invalid assume role configuration ARN [%s] Session Name [%s]", roleARN, roleSessionName));
        }
        StsClient stsClient = StsClient.builder().region(Region.AWS_GLOBAL).build();
        AssumeRoleRequest assumeRoleRequest = AssumeRoleRequest.builder()
            .roleArn(roleARN)
            .roleSessionName(roleSessionName)
            .build();
        AssumeRoleResponse assumeRoleResponse = stsClient.assumeRole(assumeRoleRequest);

        Credentials temporaryCredentials = assumeRoleResponse.credentials();

        return Optional.of(
            AwsSessionCredentials.create(
                temporaryCredentials.accessKeyId(),
                temporaryCredentials.secretAccessKey(),
                temporaryCredentials.sessionToken()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy