Skip to content

Commit b48e44f

Browse files
committed
Adding user defined key/value attributes to tenant
1 parent ccfc3d8 commit b48e44f

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

services/tenant-service/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ limitations under the License.
8686
<groupId>junit</groupId>
8787
<artifactId>junit</artifactId>
8888
</dependency>
89+
<dependency>
90+
<groupId>org.slf4j</groupId>
91+
<artifactId>slf4j-nop</artifactId>
92+
</dependency>
8993
<dependency>
9094
<groupId>com.amazon.aws.partners.saasfactory.saasboost</groupId>
9195
<artifactId>Utils</artifactId>

services/tenant-service/src/main/java/com/amazon/aws/partners/saasfactory/saasboost/Tenant.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ public class Tenant {
2828
private UUID id;
2929
private LocalDateTime created;
3030
private LocalDateTime modified;
31-
private Boolean active = Boolean.FALSE;
31+
private Boolean active;
3232
private String tier;
3333
private String onboardingStatus;
3434
private String name;
3535
private String subdomain;
3636
private String hostname;
3737
private String billingPlan;
38+
private Map<String, String> attributes = new HashMap<>();
3839
private Map<String, Resource> resources = new HashMap<>();
3940

4041
public Tenant() {
@@ -70,7 +71,7 @@ public void setModified(LocalDateTime modified) {
7071
}
7172

7273
public Boolean getActive() {
73-
return active;
74+
return Boolean.TRUE.equals(active);
7475
}
7576

7677
public void setActive(Boolean active) {
@@ -125,12 +126,20 @@ public void setBillingPlan(String billingPlan) {
125126
this.billingPlan = billingPlan;
126127
}
127128

129+
public Map<String, String> getAttributes() {
130+
return attributes;
131+
}
132+
133+
public void setAttributes(Map<String, String> attributes) {
134+
this.attributes = attributes != null ? attributes : new HashMap<>();
135+
}
136+
128137
public Map<String, Resource> getResources() {
129138
return resources;
130139
}
131140

132141
public void setResources(Map<String, Resource> resources) {
133-
this.resources = resources;
142+
this.resources = resources != null ? resources : new HashMap<>();
134143
}
135144

136145
public boolean equals(Object obj) {
@@ -145,6 +154,19 @@ public boolean equals(Object obj) {
145154
}
146155
final Tenant other = (Tenant) obj;
147156

157+
boolean attributesEqual = attributes != null && other.attributes != null;
158+
if (attributesEqual) {
159+
attributesEqual = attributes.size() == other.attributes.size();
160+
if (attributesEqual) {
161+
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
162+
attributesEqual = attribute.getValue().equals(other.attributes.get(attribute.getKey()));
163+
if (!attributesEqual) {
164+
break;
165+
}
166+
}
167+
}
168+
}
169+
148170
boolean resourcesEqual = resources != null && other.resources != null;
149171
if (resourcesEqual) {
150172
resourcesEqual = resources.size() == other.resources.size();
@@ -168,12 +190,15 @@ public boolean equals(Object obj) {
168190
&& ((subdomain == null && other.subdomain == null) || (subdomain != null && subdomain.equals(other.subdomain)))
169191
&& ((hostname == null && other.hostname == null) || (hostname != null && hostname.equals(other.hostname)))
170192
&& ((billingPlan == null && other.billingPlan == null) || (billingPlan != null && billingPlan.equals(other.billingPlan)))
193+
&& ((attributes == null && other.attributes == null) || attributesEqual)
171194
&& ((resources == null && other.resources == null) || resourcesEqual));
172195
}
173196

174197
@Override
175198
public int hashCode() {
176199
return Objects.hash(id, created, modified, active, tier, onboardingStatus, name, subdomain, hostname, billingPlan)
200+
+ Arrays.hashCode(attributes != null ? attributes.keySet().toArray(new String[0]) : null)
201+
+ Arrays.hashCode(attributes != null ? attributes.values().toArray(new Object[0]) : null)
177202
+ Arrays.hashCode(resources != null ? resources.keySet().toArray(new String[0]) : null)
178203
+ Arrays.hashCode(resources != null ? resources.values().toArray(new Resource[0]) : null);
179204
}

services/tenant-service/src/main/java/com/amazon/aws/partners/saasfactory/saasboost/TenantServiceDAL.java

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public Tenant updateTenant(Tenant tenant) {
167167
// object was persisted
168168
tenant.setModified(LocalDateTime.now());
169169
Map<String, AttributeValue> item = toAttributeValueMap(tenant);
170-
PutItemResponse response = ddb.putItem(request -> request.tableName(TENANTS_TABLE).item(item));
170+
ddb.putItem(request -> request.tableName(TENANTS_TABLE).item(item));
171171
} catch (DynamoDbException e) {
172172
LOGGER.error("TenantServiceDAL::updateTenant " + Utils.getFullStackTrace(e));
173173
throw new RuntimeException(e);
@@ -288,14 +288,13 @@ public void deleteTenant(String tenantId) {
288288
try {
289289
Map<String, AttributeValue> key = new HashMap<>();
290290
key.put("id", AttributeValue.builder().s(tenantId).build());
291-
DeleteItemResponse response = ddb.deleteItem(request -> request.tableName(TENANTS_TABLE).key(key));
291+
ddb.deleteItem(request -> request.tableName(TENANTS_TABLE).key(key));
292292
} catch (DynamoDbException e) {
293293
LOGGER.error("TenantServiceDAL::deleteTenant " + Utils.getFullStackTrace(e));
294294
throw new RuntimeException(e);
295295
}
296296
long totalTimeMillis = System.currentTimeMillis() - startTimeMillis;
297297
LOGGER.info("TenantServiceDAL::deleteTenant exec " + totalTimeMillis);
298-
return;
299298
}
300299

301300
public static Map<String, AttributeValue> toAttributeValueMap(Tenant tenant) {
@@ -311,19 +310,32 @@ public static Map<String, AttributeValue> toAttributeValueMap(Tenant tenant) {
311310
item.put("active", AttributeValue.builder().bool(tenant.getActive()).build());
312311
}
313312
if (Utils.isNotBlank(tenant.getOnboardingStatus())) {
314-
item.put("onboarding", AttributeValue.builder().s(tenant.getOnboardingStatus()).build());
313+
item.put("onboardingStatus", AttributeValue.builder().s(tenant.getOnboardingStatus()).build());
315314
}
316315
if (Utils.isNotBlank(tenant.getName())) {
317316
item.put("name", AttributeValue.builder().s(tenant.getName()).build());
318317
}
318+
if (Utils.isNotBlank(tenant.getHostname())) {
319+
item.put("hostname", AttributeValue.builder().s(tenant.getHostname()).build());
320+
}
319321
if (Utils.isNotBlank(tenant.getSubdomain())) {
320322
item.put("subdomain", AttributeValue.builder().s(tenant.getSubdomain()).build());
321323
}
322324
if (Utils.isNotBlank(tenant.getTier())) {
323325
item.put("tier", AttributeValue.builder().s(tenant.getTier()).build());
324326
}
325327
if (Utils.isNotBlank(tenant.getBillingPlan())) {
326-
item.put("planId", AttributeValue.builder().s(tenant.getBillingPlan()).build());
328+
item.put("billingPlan", AttributeValue.builder().s(tenant.getBillingPlan()).build());
329+
}
330+
if (tenant.getAttributes() != null) {
331+
item.put("attributes", AttributeValue.builder().m(tenant.getAttributes().entrySet()
332+
.stream()
333+
.collect(Collectors.toMap(
334+
entry -> entry.getKey(),
335+
entry -> AttributeValue.builder().s(entry.getValue()).build()
336+
))
337+
).build()
338+
);
327339
}
328340
if (tenant.getResources() != null) {
329341
item.put("resources", AttributeValue.builder().m(tenant.getResources().entrySet()
@@ -379,17 +391,34 @@ public static Tenant fromAttributeValueMap(Map<String, AttributeValue> item) {
379391
if (item.containsKey("tier")) {
380392
tenant.setTier(item.get("tier").s());
381393
}
382-
if (item.containsKey("onboarding")) {
383-
tenant.setOnboardingStatus(item.get("onboarding").s());
394+
if (item.containsKey("onboardingStatus")) {
395+
tenant.setOnboardingStatus(item.get("onboardingStatus").s());
384396
}
385397
if (item.containsKey("name")) {
386398
tenant.setName(item.get("name").s());
387399
}
400+
if (item.containsKey("hostname")) {
401+
tenant.setHostname(item.get("hostname").s());
402+
}
388403
if (item.containsKey("subdomain")) {
389404
tenant.setSubdomain(item.get("subdomain").s());
390405
}
391-
if (item.containsKey("planId")) {
392-
tenant.setBillingPlan(item.get("planId").s());
406+
if (item.containsKey("billingPlan")) {
407+
tenant.setBillingPlan(item.get("billingPlan").s());
408+
}
409+
if (item.containsKey("attributes")) {
410+
try {
411+
tenant.setAttributes(item.get("attributes").m().entrySet()
412+
.stream()
413+
.collect(Collectors.toMap(
414+
entry -> entry.getKey(),
415+
entry -> entry.getValue().s()
416+
))
417+
);
418+
} catch (Exception e) {
419+
LOGGER.error("Failed to parse resources from database: {}", item.get("resources").m());
420+
LOGGER.error(Utils.getFullStackTrace(e));
421+
}
393422
}
394423
if (item.containsKey("resources")) {
395424
try {

services/tenant-service/src/test/java/com/amazon/aws/partners/saasfactory/saasboost/TenantServiceDALTest.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616

1717
package com.amazon.aws.partners.saasfactory.saasboost;
1818

19+
import com.fasterxml.jackson.annotation.JsonProperty;
1920
import org.junit.BeforeClass;
2021
import org.junit.Test;
2122
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
2223

24+
import java.beans.IntrospectionException;
25+
import java.beans.Introspector;
26+
import java.beans.PropertyDescriptor;
27+
import java.lang.annotation.Annotation;
28+
import java.lang.reflect.Method;
2329
import java.time.LocalDateTime;
2430
import java.time.format.DateTimeFormatter;
2531
import java.util.*;
@@ -30,12 +36,16 @@
3036
public class TenantServiceDALTest {
3137

3238
private static UUID tenantId;
39+
private static HashMap<String, String> attributes;
3340
private static HashMap<String, Tenant.Resource> resources;
3441

3542
@BeforeClass
3643
public static void setup() throws Exception {
3744
tenantId = UUID.fromString("d1c1e3cc-962f-4f03-b4a8-d8a7c1f986c3");
3845

46+
attributes = new HashMap<>();
47+
attributes.put("User Defined Key", "User Defined Value");
48+
3949
resources = new HashMap<>();
4050
resources.put("VPC", new Tenant.Resource("vpc-0f28a79bbbcce70bb",
4151
"arn:aws:ec2:us-east-1:111111111:vpc/vpc-0f28a79bbbcce70bb",
@@ -62,7 +72,9 @@ public void testToAttributeValueMap() {
6272
tenant.setName("Test Tenant");
6373
tenant.setOnboardingStatus("succeeded");
6474
tenant.setBillingPlan("Billing Plan");
75+
tenant.setHostname("test-tenant.saas-example.com");
6576
tenant.setSubdomain("test-tenant");
77+
tenant.setAttributes(attributes);
6678
tenant.setResources(resources);
6779

6880
Map<String, AttributeValue> expected = new HashMap<>();
@@ -72,9 +84,19 @@ public void testToAttributeValueMap() {
7284
expected.put("modified", AttributeValue.builder().s(modified.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)).build());
7385
expected.put("tier", AttributeValue.builder().s("default").build());
7486
expected.put("name", AttributeValue.builder().s("Test Tenant").build());
75-
expected.put("onboarding", AttributeValue.builder().s("succeeded").build());
87+
expected.put("onboardingStatus", AttributeValue.builder().s("succeeded").build());
88+
expected.put("hostname", AttributeValue.builder().s("test-tenant.saas-example.com").build());
7689
expected.put("subdomain", AttributeValue.builder().s("test-tenant").build());
77-
expected.put("planId", AttributeValue.builder().s("Billing Plan").build());
90+
expected.put("billingPlan", AttributeValue.builder().s("Billing Plan").build());
91+
expected.put("attributes", AttributeValue.builder().m(attributes.entrySet()
92+
.stream()
93+
.collect(Collectors.toMap(
94+
entry -> entry.getKey(),
95+
entry -> AttributeValue.builder().s(
96+
String.valueOf(entry.getValue())
97+
).build()
98+
))
99+
).build());
78100
expected.put("resources", AttributeValue.builder().m(resources.entrySet()
79101
.stream()
80102
.collect(Collectors.toMap(
@@ -85,13 +107,38 @@ public void testToAttributeValueMap() {
85107
"arn", AttributeValue.builder().s(entry.getValue().getArn()).build(),
86108
"consoleUrl", AttributeValue.builder().s(entry.getValue().getConsoleUrl()).build()
87109
)).build()
88-
))).build());
110+
))
111+
).build());
89112

90113
Map<String, AttributeValue> actual = TenantServiceDAL.toAttributeValueMap(tenant);
91114

115+
// DynamoDB marshalling
92116
assertEquals("Size unequal", expected.size(), actual.size());
93-
expected.keySet().stream().forEach((key) -> {
117+
expected.keySet().stream().forEach(key -> {
94118
assertEquals("Value mismatch for '" + key + "'", expected.get(key), actual.get(key));
95119
});
120+
121+
// Ignore read only properties from JSON serialization
122+
Collection<String> ignoreProperties = new HashSet<>();
123+
try {
124+
for (PropertyDescriptor reflection : Introspector.getBeanInfo(Tenant.class).getPropertyDescriptors()) {
125+
Method getter = reflection.getReadMethod();
126+
if (getter != null) {
127+
JsonProperty jsonProperty = getter.getDeclaredAnnotation(JsonProperty.class);
128+
if (jsonProperty != null && jsonProperty.access() == JsonProperty.Access.READ_ONLY) {
129+
ignoreProperties.add(reflection.getName());
130+
}
131+
}
132+
}
133+
} catch (IntrospectionException ie) {
134+
System.err.println(Utils.getFullStackTrace(ie));
135+
}
136+
// Have we reflected all class properties we serialize for API calls in DynamoDB?
137+
Map<String, Object> json = Utils.fromJson(Utils.toJson(tenant), LinkedHashMap.class);
138+
json.keySet().stream()
139+
.filter(key -> !ignoreProperties.contains(key))
140+
.forEach(key -> {
141+
assertTrue("Class property '" + key + "' does not exist in DynamoDB attribute map", actual.containsKey(key));
142+
});
96143
}
97144
}

0 commit comments

Comments
 (0)