2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.dynamodb.internal;
15 import java.nio.file.Path;
17 import java.util.Optional;
18 import java.util.stream.Collectors;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
25 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
26 import software.amazon.awssdk.auth.credentials.AwsCredentials;
27 import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
28 import software.amazon.awssdk.awscore.retry.AwsRetryPolicy;
29 import software.amazon.awssdk.core.retry.RetryMode;
30 import software.amazon.awssdk.core.retry.RetryPolicy;
31 import software.amazon.awssdk.profiles.ProfileFile;
32 import software.amazon.awssdk.profiles.ProfileFile.Type;
33 import software.amazon.awssdk.profiles.ProfileProperty;
34 import software.amazon.awssdk.regions.Region;
37 * Configuration for DynamoDB connections
39 * If table parameter is specified and is not blank, we use new table schema (ExpectedTableRevision.NEW).
40 * If tablePrefix parameter is specified and is not blank, we use legacy table schema (ExpectedTableRevision.LEGACY).
41 * Other cases conservatively set ExpectedTableRevision.MAYBE_LEGACY, detecting the right schema during runtime.
44 * @author Sami Salonen - Initial contribution
47 public class DynamoDBConfig {
48 public static final String DEFAULT_TABLE_PREFIX = "openhab-";
49 public static final String DEFAULT_TABLE_NAME = "openhab";
50 public static final long DEFAULT_READ_CAPACITY_UNITS = 1;
51 public static final long DEFAULT_WRITE_CAPACITY_UNITS = 1;
52 private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBConfig.class);
54 private long readCapacityUnits;
55 private long writeCapacityUnits;
56 private Region region;
57 private AwsCredentials credentials;
58 private Optional<RetryPolicy> retryPolicy;
59 private ExpectedTableSchema tableRevision;
61 private String tablePrefixLegacy;
62 private @Nullable Integer expireDays;
66 * @param config persistence service configuration
67 * @return DynamoDB configuration. Returns null in case of configuration errors
69 public static @Nullable DynamoDBConfig fromConfig(Map<String, Object> config) {
70 ExpectedTableSchema tableRevision;
72 String regionName = (String) config.get("region");
73 if (regionName == null) {
77 if (Region.regions().stream().noneMatch(r -> r.toString().equals(regionName))) {
78 LOGGER.warn("Region {} is not matching known regions: {}. The region might not be supported.",
79 regionName, Region.regions().stream().map(r -> r.toString()).collect(Collectors.joining(", ")));
81 region = Region.of(regionName);
83 Optional<RetryMode> retryMode = Optional.empty();
84 AwsCredentials credentials;
85 String accessKey = (String) config.get("accessKey");
86 String secretKey = (String) config.get("secretKey");
87 if (accessKey != null && !accessKey.isBlank() && secretKey != null && !secretKey.isBlank()) {
88 LOGGER.debug("accessKey and secretKey specified. Using those.");
89 credentials = AwsBasicCredentials.create(accessKey, secretKey);
91 LOGGER.debug("accessKey and/or secretKey blank. Checking profilesConfigFile and profile.");
92 String profilesConfigFile = (String) config.get("profilesConfigFile");
93 String profile = (String) config.get("profile");
94 if (profilesConfigFile == null || profilesConfigFile.isBlank() || profile == null
95 || profile.isBlank()) {
97 Specify either 1) accessKey and secretKey; or 2) profilesConfigFile and \
98 profile for providing AWS credentials\
102 ProfileFile profileFile = ProfileFile.builder().content(Path.of(profilesConfigFile))
103 .type(Type.CREDENTIALS).build();
104 credentials = ProfileCredentialsProvider.builder().profileFile(profileFile).profileName(profile).build()
105 .resolveCredentials();
107 retryMode = profileFile.profile(profile).flatMap(p -> p.property(ProfileProperty.RETRY_MODE))
108 .flatMap(retry_mode -> {
109 for (RetryMode value : RetryMode.values()) {
110 if (retry_mode.equalsIgnoreCase(value.name())) {
111 return Optional.of(value);
115 "Unknown retry_mode '{}' in profile. Ignoring and using SDK default retry mode.",
117 return Optional.empty();
120 LOGGER.debug("Retry mode {}", retryMode);
123 String table = (String) config.get("table");
124 String tablePrefixLegacy;
125 if (table == null || table.isBlank()) {
126 // the new parameter 'table' has not been set. Check whether the legacy parameter 'tablePrefix' is set
127 table = DEFAULT_TABLE_NAME;
128 tablePrefixLegacy = (String) config.get("tablePrefix");
129 if (tablePrefixLegacy == null || tablePrefixLegacy.isBlank()) {
130 LOGGER.debug("Using default table prefix {}", DEFAULT_TABLE_PREFIX);
131 // No explicit value has been specified for tablePrefix, user could be still using the legacy setup
132 tableRevision = ExpectedTableSchema.MAYBE_LEGACY;
133 tablePrefixLegacy = DEFAULT_TABLE_PREFIX;
135 // Explicit value for tablePrefix, user certainly prefers LEGACY
136 tableRevision = ExpectedTableSchema.LEGACY;
139 tableRevision = ExpectedTableSchema.NEW;
140 tablePrefixLegacy = DEFAULT_TABLE_PREFIX;
143 final long readCapacityUnits;
144 String readCapacityUnitsParam = (String) config.get("readCapacityUnits");
145 if (readCapacityUnitsParam == null || readCapacityUnitsParam.isBlank()) {
146 readCapacityUnits = DEFAULT_READ_CAPACITY_UNITS;
148 readCapacityUnits = Long.parseLong(readCapacityUnitsParam);
151 final long writeCapacityUnits;
152 String writeCapacityUnitsParam = (String) config.get("writeCapacityUnits");
153 if (writeCapacityUnitsParam == null || writeCapacityUnitsParam.isBlank()) {
154 writeCapacityUnits = DEFAULT_WRITE_CAPACITY_UNITS;
156 writeCapacityUnits = Long.parseLong(writeCapacityUnitsParam);
159 final @Nullable Integer expireDays;
160 String expireDaysString = (String) config.get("expireDays");
161 if (expireDaysString == null || expireDaysString.isBlank()) {
164 expireDays = Integer.parseInt(expireDaysString);
165 if (expireDays <= 0) {
166 LOGGER.error("expireDays should be positive integer or null");
171 switch (tableRevision) {
173 LOGGER.debug("Using new DynamoDB table schema");
174 return DynamoDBConfig.newSchema(region, credentials, retryMode.map(AwsRetryPolicy::forRetryMode),
175 table, readCapacityUnits, writeCapacityUnits, expireDays);
178 "Using legacy DynamoDB table schema. It is recommended to transition to new schema by defining 'table' parameter and not configuring 'tablePrefix'");
179 return DynamoDBConfig.legacySchema(region, credentials, retryMode.map(AwsRetryPolicy::forRetryMode),
180 tablePrefixLegacy, readCapacityUnits, writeCapacityUnits);
183 "Unclear whether we should use new legacy DynamoDB table schema. It is recommended to explicitly define new 'table' parameter. The correct table schema will be detected at runtime.");
184 return DynamoDBConfig.maybeLegacySchema(region, credentials,
185 retryMode.map(AwsRetryPolicy::forRetryMode), table, tablePrefixLegacy, readCapacityUnits,
186 writeCapacityUnits, expireDays);
188 throw new IllegalStateException("Unhandled enum. Bug");
190 } catch (Exception e) {
191 LOGGER.error("Error with configuration: {} {}", e.getClass().getSimpleName(), e.getMessage());
196 private static DynamoDBConfig newSchema(Region region, AwsCredentials credentials,
197 Optional<RetryPolicy> retryPolicy, String table, long readCapacityUnits, long writeCapacityUnits,
198 @Nullable Integer expireDays) {
199 return new DynamoDBConfig(region, credentials, retryPolicy, table, "", ExpectedTableSchema.NEW,
200 readCapacityUnits, writeCapacityUnits, expireDays);
203 private static DynamoDBConfig legacySchema(Region region, AwsCredentials credentials,
204 Optional<RetryPolicy> retryPolicy, String tablePrefixLegacy, long readCapacityUnits,
205 long writeCapacityUnits) {
206 return new DynamoDBConfig(region, credentials, retryPolicy, "", tablePrefixLegacy, ExpectedTableSchema.LEGACY,
207 readCapacityUnits, writeCapacityUnits, null);
210 private static DynamoDBConfig maybeLegacySchema(Region region, AwsCredentials credentials,
211 Optional<RetryPolicy> retryPolicy, String table, String tablePrefixLegacy, long readCapacityUnits,
212 long writeCapacityUnits, @Nullable Integer expireDays) {
213 return new DynamoDBConfig(region, credentials, retryPolicy, table, tablePrefixLegacy,
214 ExpectedTableSchema.MAYBE_LEGACY, readCapacityUnits, writeCapacityUnits, expireDays);
217 private DynamoDBConfig(Region region, AwsCredentials credentials, Optional<RetryPolicy> retryPolicy, String table,
218 String tablePrefixLegacy, ExpectedTableSchema tableRevision, long readCapacityUnits,
219 long writeCapacityUnits, @Nullable Integer expireDays) {
220 this.region = region;
221 this.credentials = credentials;
222 this.retryPolicy = retryPolicy;
224 this.tablePrefixLegacy = tablePrefixLegacy;
225 this.tableRevision = tableRevision;
226 this.readCapacityUnits = readCapacityUnits;
227 this.writeCapacityUnits = writeCapacityUnits;
228 this.expireDays = expireDays;
231 public AwsCredentials getCredentials() {
235 public String getTablePrefixLegacy() {
236 return tablePrefixLegacy;
239 public String getTable() {
243 public ExpectedTableSchema getTableRevision() {
244 return tableRevision;
247 public Region getRegion() {
251 public long getReadCapacityUnits() {
252 return readCapacityUnits;
255 public long getWriteCapacityUnits() {
256 return writeCapacityUnits;
259 public Optional<RetryPolicy> getRetryPolicy() {
263 public @Nullable Integer getExpireDays() {