]> git.basschouten.com Git - openhab-addons.git/blob
f2adcceafc5b60512fa74d9aad2eaac471e09925
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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     public static final RetryMode DEFAULT_RETRY_MODE = RetryMode.STANDARD;
53     private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBConfig.class);
54
55     private long readCapacityUnits;
56     private long writeCapacityUnits;
57     private Region region;
58     private AwsCredentials credentials;
59     private RetryPolicy retryPolicy;
60     private ExpectedTableSchema tableRevision;
61     private String table;
62     private String tablePrefixLegacy;
63     private @Nullable Integer expireDays;
64
65     /**
66      *
67      * @param config persistence service configuration
68      * @return DynamoDB configuration. Returns null in case of configuration errors
69      */
70     public static @Nullable DynamoDBConfig fromConfig(Map<String, Object> config) {
71         ExpectedTableSchema tableRevision;
72         try {
73             String regionName = (String) config.get("region");
74             if (regionName == null) {
75                 return null;
76             }
77             final Region region;
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(", ")));
81             }
82             region = Region.of(regionName);
83
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);
91             } else {
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");
99                     return null;
100                 }
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();
105
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);
111                                 }
112                             }
113                             LOGGER.warn("Unknown retry_mode '{}' in profile. Ignoring and using default {} retry mode.",
114                                     retry_mode, DEFAULT_RETRY_MODE);
115                             return Optional.empty();
116
117                         }).orElse(DEFAULT_RETRY_MODE);
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, AwsRetryPolicy.forRetryMode(retryMode), table,
173                             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, AwsRetryPolicy.forRetryMode(retryMode),
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, AwsRetryPolicy.forRetryMode(retryMode),
183                             table, tablePrefixLegacy, readCapacityUnits, writeCapacityUnits, expireDays);
184                 default:
185                     throw new IllegalStateException("Unhandled enum. Bug");
186             }
187         } catch (Exception e) {
188             LOGGER.error("Error with configuration: {} {}", e.getClass().getSimpleName(), e.getMessage());
189             return null;
190         }
191     }
192
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);
197     }
198
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);
203     }
204
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);
210     }
211
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;
218         this.table = table;
219         this.tablePrefixLegacy = tablePrefixLegacy;
220         this.tableRevision = tableRevision;
221         this.readCapacityUnits = readCapacityUnits;
222         this.writeCapacityUnits = writeCapacityUnits;
223         this.expireDays = expireDays;
224     }
225
226     public AwsCredentials getCredentials() {
227         return credentials;
228     }
229
230     public String getTablePrefixLegacy() {
231         return tablePrefixLegacy;
232     }
233
234     public String getTable() {
235         return table;
236     }
237
238     public ExpectedTableSchema getTableRevision() {
239         return tableRevision;
240     }
241
242     public Region getRegion() {
243         return region;
244     }
245
246     public long getReadCapacityUnits() {
247         return readCapacityUnits;
248     }
249
250     public long getWriteCapacityUnits() {
251         return writeCapacityUnits;
252     }
253
254     public RetryPolicy getRetryPolicy() {
255         return retryPolicy;
256     }
257
258     public @Nullable Integer getExpireDays() {
259         return expireDays;
260     }
261 }