2 * Copyright (c) 2010-2021 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 public static final RetryMode DEFAULT_RETRY_MODE = RetryMode.STANDARD;
53 private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBConfig.class);
55 private long readCapacityUnits;
56 private long writeCapacityUnits;
57 private Region region;
58 private AwsCredentials credentials;
59 private RetryPolicy retryPolicy;
60 private ExpectedTableSchema tableRevision;
62 private String tablePrefixLegacy;
63 private @Nullable Integer expireDays;
67 * @param config persistence service configuration
68 * @return DynamoDB configuration. Returns null in case of configuration errors
70 public static @Nullable DynamoDBConfig fromConfig(Map<String, Object> config) {
71 ExpectedTableSchema tableRevision;
73 String regionName = (String) config.get("region");
74 if (regionName == null) {
78 if (Region.regions().stream().noneMatch(r -> r.toString().equals(regionName))) {
79 LOGGER.warn("Region {} is not matching known regions: {}. The region might not be supported.",
80 regionName, Region.regions().stream().map(r -> r.toString()).collect(Collectors.joining(", ")));
82 region = Region.of(regionName);
84 RetryMode retryMode = RetryMode.STANDARD;
85 AwsCredentials credentials;
86 String accessKey = (String) config.get("accessKey");
87 String secretKey = (String) config.get("secretKey");
88 if (accessKey != null && !accessKey.isBlank() && secretKey != null && !secretKey.isBlank()) {
89 LOGGER.debug("accessKey and secretKey specified. Using those.");
90 credentials = AwsBasicCredentials.create(accessKey, secretKey);
92 LOGGER.debug("accessKey and/or secretKey blank. Checking profilesConfigFile and profile.");
93 String profilesConfigFile = (String) config.get("profilesConfigFile");
94 String profile = (String) config.get("profile");
95 if (profilesConfigFile == null || profilesConfigFile.isBlank() || profile == null
96 || profile.isBlank()) {
97 LOGGER.error("Specify either 1) accessKey and secretKey; or 2) profilesConfigFile and "
98 + "profile for providing AWS credentials");
101 ProfileFile profileFile = ProfileFile.builder().content(Path.of(profilesConfigFile))
102 .type(Type.CREDENTIALS).build();
103 credentials = ProfileCredentialsProvider.builder().profileFile(profileFile).profileName(profile).build()
104 .resolveCredentials();
106 retryMode = profileFile.profile(profile).flatMap(p -> p.property(ProfileProperty.RETRY_MODE))
107 .flatMap(retry_mode -> {
108 for (RetryMode value : RetryMode.values()) {
109 if (retry_mode.equalsIgnoreCase(value.name())) {
110 return Optional.of(value);
113 LOGGER.warn("Unknown retry_mode '{}' in profile. Ignoring and using default {} retry mode.",
114 retry_mode, DEFAULT_RETRY_MODE);
115 return Optional.empty();
117 }).orElse(DEFAULT_RETRY_MODE);
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, AwsRetryPolicy.forRetryMode(retryMode), table,
173 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, AwsRetryPolicy.forRetryMode(retryMode),
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, AwsRetryPolicy.forRetryMode(retryMode),
183 table, tablePrefixLegacy, readCapacityUnits, writeCapacityUnits, expireDays);
185 throw new IllegalStateException("Unhandled enum. Bug");
187 } catch (Exception e) {
188 LOGGER.error("Error with configuration: {} {}", e.getClass().getSimpleName(), e.getMessage());
193 private static DynamoDBConfig newSchema(Region region, AwsCredentials credentials, RetryPolicy retryPolicy,
194 String table, long readCapacityUnits, long writeCapacityUnits, @Nullable Integer expireDays) {
195 return new DynamoDBConfig(region, credentials, retryPolicy, table, "", ExpectedTableSchema.NEW,
196 readCapacityUnits, writeCapacityUnits, expireDays);
199 private static DynamoDBConfig legacySchema(Region region, AwsCredentials credentials, RetryPolicy retryPolicy,
200 String tablePrefixLegacy, long readCapacityUnits, long writeCapacityUnits) {
201 return new DynamoDBConfig(region, credentials, retryPolicy, "", tablePrefixLegacy, ExpectedTableSchema.LEGACY,
202 readCapacityUnits, writeCapacityUnits, null);
205 private static DynamoDBConfig maybeLegacySchema(Region region, AwsCredentials credentials, RetryPolicy retryPolicy,
206 String table, String tablePrefixLegacy, long readCapacityUnits, long writeCapacityUnits,
207 @Nullable Integer expireDays) {
208 return new DynamoDBConfig(region, credentials, retryPolicy, table, tablePrefixLegacy,
209 ExpectedTableSchema.MAYBE_LEGACY, readCapacityUnits, writeCapacityUnits, expireDays);
212 private DynamoDBConfig(Region region, AwsCredentials credentials, RetryPolicy retryPolicy, String table,
213 String tablePrefixLegacy, ExpectedTableSchema tableRevision, long readCapacityUnits,
214 long writeCapacityUnits, @Nullable Integer expireDays) {
215 this.region = region;
216 this.credentials = credentials;
217 this.retryPolicy = retryPolicy;
219 this.tablePrefixLegacy = tablePrefixLegacy;
220 this.tableRevision = tableRevision;
221 this.readCapacityUnits = readCapacityUnits;
222 this.writeCapacityUnits = writeCapacityUnits;
223 this.expireDays = expireDays;
226 public AwsCredentials getCredentials() {
230 public String getTablePrefixLegacy() {
231 return tablePrefixLegacy;
234 public String getTable() {
238 public ExpectedTableSchema getTableRevision() {
239 return tableRevision;
242 public Region getRegion() {
246 public long getReadCapacityUnits() {
247 return readCapacityUnits;
250 public long getWriteCapacityUnits() {
251 return writeCapacityUnits;
254 public RetryPolicy getRetryPolicy() {
258 public @Nullable Integer getExpireDays() {