Skip to content

Commit

Permalink
refactor: removed unnecessary dependency
Browse files Browse the repository at this point in the history
Signed-off-by: Maximillian Arruda <dearrudam@gmail.com>
  • Loading branch information
dearrudam committed Mar 12, 2024
1 parent c03128d commit e7ecf0f
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 209 deletions.
5 changes: 0 additions & 5 deletions jnosql-dynamodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@
<artifactId>dynamodb</artifactId>
<version>${dynamodb.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced</artifactId>
<version>${dynamodb.version}</version>
</dependency>
<dependency>
<groupId>net.datafaker</groupId>
<artifactId>datafaker</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
import org.eclipse.jnosql.communication.driver.JsonbSupplier;
import org.eclipse.jnosql.communication.driver.ValueUtil;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

import java.util.*;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.stream.StreamSupport;
Expand All @@ -41,25 +46,6 @@ class DocumentEntityConverter {
private DocumentEntityConverter() {
}

static DocumentEntity toDocumentEntity(UnaryOperator<String> entityNameResolver, EnhancedDocument enhancedDocument) {
if (enhancedDocument == null) {
return null;
}
if (enhancedDocument.toMap().isEmpty()) {
return null;
}
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
String entityAttribute = resolver.apply(ENTITY);
Map<String, AttributeValue> map = enhancedDocument.toMap();
var entityName = map.containsKey(entityAttribute) ? map.get(entityAttribute).s() : entityAttribute;
List<Document> documents = map.entrySet()
.stream()
.filter(entry -> !Objects.equals(entityAttribute, entry.getKey()))
.map(entry -> Document.of(entry.getKey(), convertValue(entry.getValue())))
.toList();
return DocumentEntity.of(entityName, documents);
}

private static Object convertValue(Object value) {
if (value instanceof AttributeValue attributeValue) {
switch (attributeValue.type()) {
Expand Down Expand Up @@ -91,14 +77,6 @@ private static Object convertValue(Object value) {
return value;
}

static EnhancedDocument toEnhancedDocument(UnaryOperator<String> entityNameResolver, DocumentEntity documentEntity) {
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
Map<String, Object> documentAsMap = getMap(resolver, documentEntity);
return EnhancedDocument.builder()
.json(JSONB.toJson(documentAsMap))
.build();
}

static Map<String, Object> getMap(UnaryOperator<String> entityNameResolver, DocumentEntity entity) {
var nameResolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
Map<String, Object> jsonObject = new HashMap<>();
Expand Down Expand Up @@ -141,4 +119,64 @@ private static boolean isSudDocumentList(Object value) {
return value instanceof Iterable && StreamSupport.stream(Iterable.class.cast(value).spliterator(), false).
allMatch(d -> d instanceof Iterable && isSudDocument(d));
}

public static Map<String, AttributeValue> toItem(UnaryOperator<String> entityNameResolver, DocumentEntity documentEntity) {
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
Map<String, Object> documentAttributes = getMap(resolver, documentEntity);
return toItem(documentAttributes);
}

private static Map<String, AttributeValue> toItem(Map<String, Object> documentAttributes) {
HashMap<String, AttributeValue> result = new HashMap<>();
documentAttributes.forEach((attribute, value) -> result.put(attribute, toAttributeValue(value)));
return result;
}

public static AttributeValue toAttributeValue(Object value) {
if (value == null)
return AttributeValue.builder().nul(true).build();
if (value instanceof String str)
return AttributeValue.builder().s(str).build();
if (value instanceof Number number)
return AttributeValue.builder().n(String.valueOf(number)).build();
if (value instanceof Boolean bool)
return AttributeValue.builder().bool(bool).build();
if (value instanceof List<?> list)
return AttributeValue.builder().l(list.stream().filter(Objects::nonNull)
.map(DocumentEntityConverter::toAttributeValue).toList()).build();
if (value instanceof Map<?, ?> mapValue) {
HashMap<String, AttributeValue> values = new HashMap<>();
mapValue.forEach((k, v) -> values.put(String.valueOf(k), toAttributeValue(v)));
return AttributeValue.builder().m(values).build();
}
if (value instanceof byte[] data) {
return AttributeValue.builder().b(SdkBytes.fromByteArray(data)).build();
}
if (value instanceof ByteBuffer byteBuffer) {
return AttributeValue.builder().b(SdkBytes.fromByteBuffer(byteBuffer)).build();
}
if (value instanceof InputStream input) {
return AttributeValue.builder().b(SdkBytes.fromInputStream(input)).build();
}
return AttributeValue.builder().s(String.valueOf(value)).build();
}


public static DocumentEntity toDocumentEntity(UnaryOperator<String> entityNameResolver, Map<String, AttributeValue> item) {
if (item == null) {
return null;
}
if (item.isEmpty()) {
return null;
}
UnaryOperator<String> resolver = Optional.ofNullable(entityNameResolver).orElse(UnaryOperator.identity());
String entityAttribute = resolver.apply(ENTITY);
var entityName = item.containsKey(entityAttribute) ? item.get(entityAttribute).s() : entityAttribute;
List<Document> documents = item.entrySet()
.stream()
.filter(entry -> !Objects.equals(entityAttribute, entry.getKey()))
.map(entry -> Document.of(entry.getKey(), convertValue(entry.getValue())))
.toList();
return DocumentEntity.of(entityName, documents);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,29 @@
import org.eclipse.jnosql.communication.document.DocumentEntity;
import org.eclipse.jnosql.communication.document.DocumentManager;
import org.eclipse.jnosql.communication.document.DocumentQuery;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DescribeTimeToLiveRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTimeToLiveResponse;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.StreamSpecification;
import software.amazon.awssdk.services.dynamodb.model.TimeToLiveStatus;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
Expand All @@ -44,7 +51,7 @@
import java.util.stream.StreamSupport;

import static java.util.Objects.requireNonNull;
import static org.eclipse.jnosql.databases.dynamodb.communication.DocumentEntityConverter.toEnhancedDocument;
import static org.eclipse.jnosql.databases.dynamodb.communication.DocumentEntityConverter.toItem;

public class DynamoDBDocumentManager implements DocumentManager {

Expand All @@ -54,18 +61,14 @@ public class DynamoDBDocumentManager implements DocumentManager {

private final DynamoDbClient dynamoDbClient;

private final DynamoDbEnhancedClient dynamoDbEnhancedClient;

private final UnaryOperator<String> entityNameAttributeNameResolver;

private final ConcurrentHashMap<String, DynamoDbTable<EnhancedDocument>> tables = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Supplier<String>> ttlAttributeNamesByTable = new ConcurrentHashMap<>();

public DynamoDBDocumentManager(String database, DynamoDbClient dynamoDbClient, Settings settings) {
this.settings = settings;
this.database = database;
this.dynamoDbClient = dynamoDbClient;
this.dynamoDbEnhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
this.entityNameAttributeNameResolver = this::resolveEntityNameAttributeName;
}

Expand All @@ -77,10 +80,6 @@ public DynamoDbClient getDynamoDbClient() {
return dynamoDbClient;
}

public DynamoDbEnhancedClient getDynamoDbEnhancedClient() {
return dynamoDbEnhancedClient;
}

@Override
public String name() {
return database;
Expand All @@ -93,57 +92,87 @@ UnaryOperator<String> entityNameAttributeNameResolver() {
@Override
public DocumentEntity insert(DocumentEntity documentEntity) {
requireNonNull(documentEntity, "documentEntity is required");
var enhancedDocument = convertToEnhancedDocument(documentEntity);
createIfNeeded(getTableFor(documentEntity.name()))
.putItem(enhancedDocument);
getDynamoDbClient().putItem(PutItemRequest.builder()
.tableName(createIfNeeded(documentEntity.name()).table().tableName())
.item(asItem(documentEntity))
.build());
return documentEntity;
}

private EnhancedDocument convertToEnhancedDocument(DocumentEntity documentEntity) {
return toEnhancedDocument(entityNameAttributeNameResolver(), documentEntity);
}

private DynamoDbTable<EnhancedDocument> getTableFor(String name) {
return this.tables.computeIfAbsent(name, this::buildTable);
private Map<String, AttributeValue> asItem(DocumentEntity documentEntity) {
return toItem(entityNameAttributeNameResolver(), documentEntity);
}

private Supplier<String> getTTLAttributeNameFor(String tableName) {
return this.ttlAttributeNamesByTable.computeIfAbsent(tableName, this::getTTLAttributeNameSupplierFromTable);
}

private Supplier<String> getTTLAttributeNameSupplierFromTable(String tableName) {
DynamoDbTable<EnhancedDocument> table = buildTable(tableName);
createIfNeeded(tableName);
DescribeTimeToLiveResponse describeTimeToLiveResponse = getDynamoDbClient().describeTimeToLive(DescribeTimeToLiveRequest.builder()
.tableName(table.tableName()).build());
.tableName(tableName).build());
if (TimeToLiveStatus.ENABLED.equals(describeTimeToLiveResponse.timeToLiveDescription().timeToLiveStatus())) {
var ttlAttributeName = describeTimeToLiveResponse.timeToLiveDescription().attributeName();
return () -> ttlAttributeName;
}
return unsupportedTTLSupplierFor(table.tableName());
return unsupportedTTLSupplierFor(tableName);
}

private Supplier<String> unsupportedTTLSupplierFor(String tableName) {
return () -> tableName + " don't support TTL operations. Check if TTL support is enabled for this table.";
}

private DynamoDbTable<EnhancedDocument> buildTable(String nameKey) {
return dynamoDbEnhancedClient
.table(nameKey, TableSchema.documentSchemaBuilder()
.addIndexPartitionKey(TableMetadata.primaryIndexName(), getEntityNameAttributeName(), AttributeValueType.S)
.addIndexSortKey(TableMetadata.primaryIndexName(), DocumentEntityConverter.ID, AttributeValueType.S)
.attributeConverterProviders(AttributeConverterProvider.defaultProvider())
.build());
}

private DynamoDbTable<EnhancedDocument> createIfNeeded(DynamoDbTable<EnhancedDocument> table) {
private DescribeTableResponse createIfNeeded(String tableName) {
if (shouldCreateTables()) {
try {
table.describeTable();
return getDynamoDbClient().describeTable(DescribeTableRequest.builder()
.tableName(tableName)
.build());
} catch (ResourceNotFoundException ex) {
table.createTable();
return createTable(tableName);
}
}
return table;
return getDynamoDbClient().describeTable(DescribeTableRequest.builder()
.tableName(tableName)
.build());
}

private DescribeTableResponse createTable(String tableName) {
try (var waiter = getDynamoDbClient().waiter()) {
getDynamoDbClient().createTable(CreateTableRequest.builder()
.tableName(tableName)
.keySchema(defaultKeySchemaFor(tableName))
.attributeDefinitions(defaultAttributeDefinitionsFor(tableName))
.provisionedThroughput(defaultProvisionedThroughputFor(tableName))
.streamSpecification(defaultStreamSpecificationFor(tableName))
.build());

var tableRequest = DescribeTableRequest.builder().tableName(tableName).build();
var waiterResponse = waiter.waitUntilTableExists(tableRequest);
return waiterResponse.matched().response().orElseThrow();
}
}

private StreamSpecification defaultStreamSpecificationFor(String tableName) {
return null;
}

private ProvisionedThroughput defaultProvisionedThroughputFor(String tableName) {
return DynamoTableUtils.createProvisionedThroughput(null, null);
}

private Collection<AttributeDefinition> defaultAttributeDefinitionsFor(String tableName) {
return List.of(
AttributeDefinition.builder().attributeName(getEntityNameAttributeName()).attributeType(ScalarAttributeType.S).build(),
AttributeDefinition.builder().attributeName(DocumentEntityConverter.ID).attributeType(ScalarAttributeType.S).build()
);
}

private Collection<KeySchemaElement> defaultKeySchemaFor(String tableName) {
return List.of(
KeySchemaElement.builder().attributeName(getEntityNameAttributeName()).keyType(KeyType.HASH).build(),
KeySchemaElement.builder().attributeName(DocumentEntityConverter.ID).keyType(KeyType.RANGE).build()
);
}

private boolean shouldCreateTables() {
Expand All @@ -160,11 +189,8 @@ private String getEntityNameAttributeName() {
public DocumentEntity insert(DocumentEntity documentEntity, Duration ttl) {
requireNonNull(documentEntity, "documentEntity is required");
requireNonNull(ttl, "ttl is required");
DynamoDbTable<EnhancedDocument> tableFor = createIfNeeded(getTableFor(documentEntity.name()));
documentEntity.add(getTTLAttributeNameFor(tableFor.tableName()).get(), Instant.now().plus(ttl).truncatedTo(ChronoUnit.SECONDS));
var enhancedDocument = convertToEnhancedDocument(documentEntity);
tableFor.putItem(enhancedDocument);
return documentEntity;
documentEntity.add(getTTLAttributeNameFor(documentEntity.name()).get(), Instant.now().plus(ttl).truncatedTo(ChronoUnit.SECONDS));
return insert(documentEntity);
}

@Override
Expand Down Expand Up @@ -210,9 +236,11 @@ public Stream<DocumentEntity> select(DocumentQuery documentQuery) {
@Override
public long count(String tableName) {
Objects.requireNonNull(tableName, "tableName is required");
DynamoDbTable<EnhancedDocument> table = getTableFor(tableName);
try {
return table.describeTable().table().itemCount();
return getDynamoDbClient()
.describeTable(DescribeTableRequest.builder().tableName(tableName).build())
.table()
.itemCount();
} catch (ResourceNotFoundException ex) {
return 0;
}
Expand Down

0 comments on commit e7ecf0f

Please sign in to comment.