]> git.basschouten.com Git - openhab-addons.git/blob
75f22e42588b52fe1c2db64650798844384de879
[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("Specify either 1) accessKey and secretKey; or 2) profilesConfigFile and "
97                             + "profile for providing AWS credentials");
98                     return null;
99                 }
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();
104
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);
110                                 }
111                             }
112                             LOGGER.warn(
113                                     "Unknown retry_mode '{}' in profile. Ignoring and using SDK default retry mode.",
114                                     retry_mode);
115                             return Optional.empty();
116
117                         });
118                 LOGGER.debug("Retry mode {}", retryMode);
119             }
120
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;
132                 } else {
133                     // Explicit value for tablePrefix, user certainly prefers LEGACY
134                     tableRevision = ExpectedTableSchema.LEGACY;
135                 }
136             } else {
137                 tableRevision = ExpectedTableSchema.NEW;
138                 tablePrefixLegacy = DEFAULT_TABLE_PREFIX;
139             }
140
141             final long readCapacityUnits;
142             String readCapacityUnitsParam = (String) config.get("readCapacityUnits");
143             if (readCapacityUnitsParam == null || readCapacityUnitsParam.isBlank()) {
144                 readCapacityUnits = DEFAULT_READ_CAPACITY_UNITS;
145             } else {
146                 readCapacityUnits = Long.parseLong(readCapacityUnitsParam);
147             }
148
149             final long writeCapacityUnits;
150             String writeCapacityUnitsParam = (String) config.get("writeCapacityUnits");
151             if (writeCapacityUnitsParam == null || writeCapacityUnitsParam.isBlank()) {
152                 writeCapacityUnits = DEFAULT_WRITE_CAPACITY_UNITS;
153             } else {
154                 writeCapacityUnits = Long.parseLong(writeCapacityUnitsParam);
155             }
156
157             final @Nullable Integer expireDays;
158             String expireDaysString = (String) config.get("expireDays");
159             if (expireDaysString == null || expireDaysString.isBlank()) {
160                 expireDays = null;
161             } else {
162                 expireDays = Integer.parseInt(expireDaysString);
163                 if (expireDays <= 0) {
164                     LOGGER.error("expireDays should be positive integer or null");
165                     return null;
166                 }
167             }
168
169             switch (tableRevision) {
170                 case NEW:
171                     LOGGER.debug("Using new DynamoDB table schema");
172                     return DynamoDBConfig.newSchema(region, credentials, retryMode.map(AwsRetryPolicy::forRetryMode),
173                             table, readCapacityUnits, writeCapacityUnits, expireDays);
174                 case LEGACY:
175                     LOGGER.warn(
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);
179                 case MAYBE_LEGACY:
180                     LOGGER.debug(
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);
185                 default:
186                     throw new IllegalStateException("Unhandled enum. Bug");
187             }
188         } catch (Exception e) {
189             LOGGER.error("Error with configuration: {} {}", e.getClass().getSimpleName(), e.getMessage());
190             return null;
191         }
192     }
193
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);
199     }
200
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);
206     }
207
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);
213     }
214
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;
221         this.table = table;
222         this.tablePrefixLegacy = tablePrefixLegacy;
223         this.tableRevision = tableRevision;
224         this.readCapacityUnits = readCapacityUnits;
225         this.writeCapacityUnits = writeCapacityUnits;
226         this.expireDays = expireDays;
227     }
228
229     public AwsCredentials getCredentials() {
230         return credentials;
231     }
232
233     public String getTablePrefixLegacy() {
234         return tablePrefixLegacy;
235     }
236
237     public String getTable() {
238         return table;
239     }
240
241     public ExpectedTableSchema getTableRevision() {
242         return tableRevision;
243     }
244
245     public Region getRegion() {
246         return region;
247     }
248
249     public long getReadCapacityUnits() {
250         return readCapacityUnits;
251     }
252
253     public long getWriteCapacityUnits() {
254         return writeCapacityUnits;
255     }
256
257     public Optional<RetryPolicy> getRetryPolicy() {
258         return retryPolicy;
259     }
260
261     public @Nullable Integer getExpireDays() {
262         return expireDays;
263     }
264 }