]> git.basschouten.com Git - openhab-addons.git/blob
dd186e2b9f5feded1f05f52e0fb105952caca0bc
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.dynamodb.internal;
14
15 import java.nio.file.Path;
16 import java.util.Map;
17 import java.util.Optional;
18 import java.util.stream.Collectors;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
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;
35
36 /**
37  * Configuration for DynamoDB connections
38  *
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.
42  *
43  *
44  * @author Sami Salonen - Initial contribution
45  */
46 @NonNullByDefault
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);
53
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;
60     private String table;
61     private String tablePrefixLegacy;
62     private @Nullable Integer expireDays;
63
64     /**
65      *
66      * @param config persistence service configuration
67      * @return DynamoDB configuration. Returns null in case of configuration errors
68      */
69     public static @Nullable DynamoDBConfig fromConfig(Map<String, Object> config) {
70         ExpectedTableSchema tableRevision;
71         try {
72             String regionName = (String) config.get("region");
73             if (regionName == null) {
74                 return null;
75             }
76             final Region region;
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(", ")));
80             }
81             region = Region.of(regionName);
82
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);
90             } else {
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("""
97                             Specify either 1) accessKey and secretKey; or 2) profilesConfigFile and \
98                             profile for providing AWS credentials\
99                             """);
100                     return null;
101                 }
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();
106
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);
112                                 }
113                             }
114                             LOGGER.warn(
115                                     "Unknown retry_mode '{}' in profile. Ignoring and using SDK default retry mode.",
116                                     retry_mode);
117                             return Optional.empty();
118
119                         });
120                 LOGGER.debug("Retry mode {}", retryMode);
121             }
122
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;
134                 } else {
135                     // Explicit value for tablePrefix, user certainly prefers LEGACY
136                     tableRevision = ExpectedTableSchema.LEGACY;
137                 }
138             } else {
139                 tableRevision = ExpectedTableSchema.NEW;
140                 tablePrefixLegacy = DEFAULT_TABLE_PREFIX;
141             }
142
143             final long readCapacityUnits;
144             String readCapacityUnitsParam = (String) config.get("readCapacityUnits");
145             if (readCapacityUnitsParam == null || readCapacityUnitsParam.isBlank()) {
146                 readCapacityUnits = DEFAULT_READ_CAPACITY_UNITS;
147             } else {
148                 readCapacityUnits = Long.parseLong(readCapacityUnitsParam);
149             }
150
151             final long writeCapacityUnits;
152             String writeCapacityUnitsParam = (String) config.get("writeCapacityUnits");
153             if (writeCapacityUnitsParam == null || writeCapacityUnitsParam.isBlank()) {
154                 writeCapacityUnits = DEFAULT_WRITE_CAPACITY_UNITS;
155             } else {
156                 writeCapacityUnits = Long.parseLong(writeCapacityUnitsParam);
157             }
158
159             final @Nullable Integer expireDays;
160             String expireDaysString = (String) config.get("expireDays");
161             if (expireDaysString == null || expireDaysString.isBlank()) {
162                 expireDays = null;
163             } else {
164                 expireDays = Integer.parseInt(expireDaysString);
165                 if (expireDays <= 0) {
166                     LOGGER.error("expireDays should be positive integer or null");
167                     return null;
168                 }
169             }
170
171             switch (tableRevision) {
172                 case NEW:
173                     LOGGER.debug("Using new DynamoDB table schema");
174                     return DynamoDBConfig.newSchema(region, credentials, retryMode.map(AwsRetryPolicy::forRetryMode),
175                             table, readCapacityUnits, writeCapacityUnits, expireDays);
176                 case LEGACY:
177                     LOGGER.warn(
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);
181                 case MAYBE_LEGACY:
182                     LOGGER.debug(
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);
187                 default:
188                     throw new IllegalStateException("Unhandled enum. Bug");
189             }
190         } catch (Exception e) {
191             LOGGER.error("Error with configuration: {} {}", e.getClass().getSimpleName(), e.getMessage());
192             return null;
193         }
194     }
195
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);
201     }
202
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);
208     }
209
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);
215     }
216
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;
223         this.table = table;
224         this.tablePrefixLegacy = tablePrefixLegacy;
225         this.tableRevision = tableRevision;
226         this.readCapacityUnits = readCapacityUnits;
227         this.writeCapacityUnits = writeCapacityUnits;
228         this.expireDays = expireDays;
229     }
230
231     public AwsCredentials getCredentials() {
232         return credentials;
233     }
234
235     public String getTablePrefixLegacy() {
236         return tablePrefixLegacy;
237     }
238
239     public String getTable() {
240         return table;
241     }
242
243     public ExpectedTableSchema getTableRevision() {
244         return tableRevision;
245     }
246
247     public Region getRegion() {
248         return region;
249     }
250
251     public long getReadCapacityUnits() {
252         return readCapacityUnits;
253     }
254
255     public long getWriteCapacityUnits() {
256         return writeCapacityUnits;
257     }
258
259     public Optional<RetryPolicy> getRetryPolicy() {
260         return retryPolicy;
261     }
262
263     public @Nullable Integer getExpireDays() {
264         return expireDays;
265     }
266 }