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()) {
96 LOGGER.error("Specify either 1) accessKey and secretKey; or 2) profilesConfigFile and "
97 + "profile for providing AWS credentials");
100 ProfileFile profileFile = ProfileFile.builder().content(Path.of(profilesConfigFile))
101 .type(Type.CREDENTIALS).build();
102 credentials = ProfileCredentialsProvider.builder().profileFile(profileFile).profileName(profile).build()
103 .resolveCredentials();
105 retryMode = profileFile.profile(profile).flatMap(p -> p.property(ProfileProperty.RETRY_MODE))
106 .flatMap(retry_mode -> {
107 for (RetryMode value : RetryMode.values()) {
108 if (retry_mode.equalsIgnoreCase(value.name())) {
109 return Optional.of(value);
113 "Unknown retry_mode '{}' in profile. Ignoring and using SDK default retry mode.",
115 return Optional.empty();
118 LOGGER.debug("Retry mode {}", retryMode);
121 String table = (String) config.get("table");
122 String tablePrefixLegacy;
123 if (table == null || table.isBlank()) {
124 // the new parameter 'table' has not been set. Check whether the legacy parameter 'tablePrefix' is set
125 table = DEFAULT_TABLE_NAME;
126 tablePrefixLegacy = (String) config.get("tablePrefix");
127 if (tablePrefixLegacy == null || tablePrefixLegacy.isBlank()) {
128 LOGGER.debug("Using default table prefix {}", DEFAULT_TABLE_PREFIX);
129 // No explicit value has been specified for tablePrefix, user could be still using the legacy setup
130 tableRevision = ExpectedTableSchema.MAYBE_LEGACY;
131 tablePrefixLegacy = DEFAULT_TABLE_PREFIX;
133 // Explicit value for tablePrefix, user certainly prefers LEGACY
134 tableRevision = ExpectedTableSchema.LEGACY;
137 tableRevision = ExpectedTableSchema.NEW;
138 tablePrefixLegacy = DEFAULT_TABLE_PREFIX;
141 final long readCapacityUnits;
142 String readCapacityUnitsParam = (String) config.get("readCapacityUnits");
143 if (readCapacityUnitsParam == null || readCapacityUnitsParam.isBlank()) {
144 readCapacityUnits = DEFAULT_READ_CAPACITY_UNITS;
146 readCapacityUnits = Long.parseLong(readCapacityUnitsParam);
149 final long writeCapacityUnits;
150 String writeCapacityUnitsParam = (String) config.get("writeCapacityUnits");
151 if (writeCapacityUnitsParam == null || writeCapacityUnitsParam.isBlank()) {
152 writeCapacityUnits = DEFAULT_WRITE_CAPACITY_UNITS;
154 writeCapacityUnits = Long.parseLong(writeCapacityUnitsParam);
157 final @Nullable Integer expireDays;
158 String expireDaysString = (String) config.get("expireDays");
159 if (expireDaysString == null || expireDaysString.isBlank()) {
162 expireDays = Integer.parseInt(expireDaysString);
163 if (expireDays <= 0) {
164 LOGGER.error("expireDays should be positive integer or null");
169 switch (tableRevision) {
171 LOGGER.debug("Using new DynamoDB table schema");
172 return DynamoDBConfig.newSchema(region, credentials, retryMode.map(AwsRetryPolicy::forRetryMode),
173 table, readCapacityUnits, writeCapacityUnits, expireDays);
176 "Using legacy DynamoDB table schema. It is recommended to transition to new schema by defining 'table' parameter and not configuring 'tablePrefix'");
177 return DynamoDBConfig.legacySchema(region, credentials, retryMode.map(AwsRetryPolicy::forRetryMode),
178 tablePrefixLegacy, readCapacityUnits, writeCapacityUnits);
181 "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.");
182 return DynamoDBConfig.maybeLegacySchema(region, credentials,
183 retryMode.map(AwsRetryPolicy::forRetryMode), table, tablePrefixLegacy, readCapacityUnits,
184 writeCapacityUnits, expireDays);
186 throw new IllegalStateException("Unhandled enum. Bug");
188 } catch (Exception e) {
189 LOGGER.error("Error with configuration: {} {}", e.getClass().getSimpleName(), e.getMessage());
194 private static DynamoDBConfig newSchema(Region region, AwsCredentials credentials,
195 Optional<RetryPolicy> retryPolicy, String table, long readCapacityUnits, long writeCapacityUnits,
196 @Nullable Integer expireDays) {
197 return new DynamoDBConfig(region, credentials, retryPolicy, table, "", ExpectedTableSchema.NEW,
198 readCapacityUnits, writeCapacityUnits, expireDays);
201 private static DynamoDBConfig legacySchema(Region region, AwsCredentials credentials,
202 Optional<RetryPolicy> retryPolicy, String tablePrefixLegacy, long readCapacityUnits,
203 long writeCapacityUnits) {
204 return new DynamoDBConfig(region, credentials, retryPolicy, "", tablePrefixLegacy, ExpectedTableSchema.LEGACY,
205 readCapacityUnits, writeCapacityUnits, null);
208 private static DynamoDBConfig maybeLegacySchema(Region region, AwsCredentials credentials,
209 Optional<RetryPolicy> retryPolicy, String table, String tablePrefixLegacy, long readCapacityUnits,
210 long writeCapacityUnits, @Nullable Integer expireDays) {
211 return new DynamoDBConfig(region, credentials, retryPolicy, table, tablePrefixLegacy,
212 ExpectedTableSchema.MAYBE_LEGACY, readCapacityUnits, writeCapacityUnits, expireDays);
215 private DynamoDBConfig(Region region, AwsCredentials credentials, Optional<RetryPolicy> retryPolicy, String table,
216 String tablePrefixLegacy, ExpectedTableSchema tableRevision, long readCapacityUnits,
217 long writeCapacityUnits, @Nullable Integer expireDays) {
218 this.region = region;
219 this.credentials = credentials;
220 this.retryPolicy = retryPolicy;
222 this.tablePrefixLegacy = tablePrefixLegacy;
223 this.tableRevision = tableRevision;
224 this.readCapacityUnits = readCapacityUnits;
225 this.writeCapacityUnits = writeCapacityUnits;
226 this.expireDays = expireDays;
229 public AwsCredentials getCredentials() {
233 public String getTablePrefixLegacy() {
234 return tablePrefixLegacy;
237 public String getTable() {
241 public ExpectedTableSchema getTableRevision() {
242 return tableRevision;
245 public Region getRegion() {
249 public long getReadCapacityUnits() {
250 return readCapacityUnits;
253 public long getWriteCapacityUnits() {
254 return writeCapacityUnits;
257 public Optional<RetryPolicy> getRetryPolicy() {
261 public @Nullable Integer getExpireDays() {