diff --git a/dynamodb/build.gradle.kts b/dynamodb/build.gradle.kts new file mode 100644 index 0000000000..5686e49056 --- /dev/null +++ b/dynamodb/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("io.micronaut.build.internal.aws-module") +} +dependencies { + annotationProcessor(mnValidation.micronaut.validation.processor) + api(platform(libs.boms.aws.java.sdk.v2)) + api(libs.awssdk.dynamodb) + implementation(mnValidation.micronaut.validation) + + testAnnotationProcessor(platform(mn.micronaut.core.bom)) + testAnnotationProcessor(mn.micronaut.inject.java) + + testImplementation(platform(mn.micronaut.core.bom)) + testImplementation(libs.junit.jupiter.api) + testImplementation(mnTest.micronaut.test.junit5) + testRuntimeOnly(libs.junit.jupiter.engine) + + testImplementation(projects.micronautAwsSdkV2) + + testAnnotationProcessor(mnSerde.micronaut.serde.processor) + testImplementation(mnSerde.micronaut.serde.jackson) + testImplementation(mn.micronaut.http.server.netty) + testImplementation(mn.micronaut.http.client) + testImplementation(platform(libs.testcontainers.bom)) + testImplementation(libs.testcontainers) + + testImplementation(libs.ksuid) +} +micronautBuild { + binaryCompatibility { + enabled.set(false) + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/CompositeKey.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/CompositeKey.java new file mode 100644 index 0000000000..39986a5b06 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/CompositeKey.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.NonNull; + +/** + * Composite Key for an Amazon DynamodDB Single table design. + * @author Sergio del Amo + * @since 4.0.0 + */ +public interface CompositeKey { + + /** + * + * @return Partition or Hash Key + */ + @NonNull + String getPartionKey(); + + /** + * + * @return Sort Key + */ + @NonNull + String getSortKey(); + + @NonNull + static CompositeKey of(@NonNull String hashKey, @NonNull String sortKey) { + return new CompositeKey() { + @Override + public String getPartionKey() { + return hashKey; + } + + @Override + public String getSortKey() { + return sortKey; + } + }; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DefaultDynamoDbConversionService.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DefaultDynamoDbConversionService.java new file mode 100644 index 0000000000..3bc2159cb0 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DefaultDynamoDbConversionService.java @@ -0,0 +1,120 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.beans.BeanIntrospection; +import io.micronaut.core.beans.BeanIntrospector; +import io.micronaut.core.beans.BeanProperty; +import io.micronaut.core.beans.BeanWrapper; +import io.micronaut.core.beans.exceptions.IntrospectionException; +import io.micronaut.core.convert.ConversionService; +import jakarta.inject.Singleton; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * {@link io.micronaut.context.annotation.DefaultImplementation} of {@link DynamoDbConversionService} which uses {@link ConversionService} to convert from and to {@link AttributeValue} map. + * + */ +@Singleton +public class DefaultDynamoDbConversionService implements DynamoDbConversionService { + + private final ConversionService conversionService; + + public DefaultDynamoDbConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Override + public Map convert(BeanWrapper wrapper) { + Map result = new HashMap<>(); + S bean = wrapper.getBean(); + for (BeanProperty beanProperty : wrapper.getBeanProperties()) { + + if (isStringSet(bean, beanProperty)) { + Set stringSet = new HashSet<>(); + for (Object item : (Set) beanProperty.get(bean)) { + stringSet.add(item.toString()); + } + result.put(beanProperty.getName(), AttributeValue.builder().ss(stringSet).build()); + } else { + Optional attributeValueOptional = conversionService.convert(beanProperty.get(bean), AttributeValue.class); + if (attributeValueOptional.isPresent()) { + AttributeValue attributeValue = attributeValueOptional.get(); + result.put(beanProperty.getName(), attributeValue); + } else { + + + try { + BeanWrapper valueWrapper = BeanWrapper.getWrapper(beanProperty.get(bean)); + Map valueWrapperMap = convert(valueWrapper); + AttributeValue attributeValue = AttributeValue.builder().m(valueWrapperMap).build(); + result.put(beanProperty.getName(), attributeValue); + } catch (IntrospectionException e) { + + } + } + } + } + return result; + } + + @Override + public T convert(Map item, Class targetType) { + final BeanIntrospection introspection = BeanIntrospection.getIntrospection(targetType); + Object[] arguments = new Object[introspection.getConstructorArguments().length]; + int counter = 0; + for (BeanProperty beanProperty : introspection.getBeanProperties()) { + if (item.containsKey(beanProperty.getName())) { + + AttributeValue attributeValue = item.get(beanProperty.getName()); + if (attributeValue.hasSs()) { + arguments[counter++] = conversionService.convert(attributeValue.ss(), beanProperty.getType()).orElse(null); + } else { + if (BeanIntrospector.SHARED.findIntrospection(beanProperty.getType()).isPresent()) { + Map m = attributeValue.m(); + if (m != null) { + arguments[counter++] = convert(m, beanProperty.getType()); + } + } else { + arguments[counter++] = conversionService.convert(attributeValue, beanProperty.getType()) + .orElse(null); + } + } + } else { + arguments[counter++] = null; + } + } + return introspection.instantiate(arguments); + } + + private boolean isStringSet(S bean, BeanProperty beanProperty) { + if (!Set.class.isAssignableFrom(beanProperty.getType())) { + return false; + } + for (Object setItem : (Set) beanProperty.get(bean)) { + if (!CharSequence.class.isAssignableFrom(setItem.getClass())) { + return false; + } + } + return true; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DynamoDbConversionService.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DynamoDbConversionService.java new file mode 100644 index 0000000000..0d5ccd4fc6 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DynamoDbConversionService.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.context.annotation.DefaultImplementation; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.beans.BeanWrapper; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import io.micronaut.core.beans.exceptions.IntrospectionException; +import java.util.Map; + +/** + * @author Sergio del Amo + * @since 4.0.0 + */ +@DefaultImplementation(DefaultDynamoDbConversionService.class) +public interface DynamoDbConversionService { + @NonNull + Map convert(@NonNull BeanWrapper wrapper); + + @NonNull + default Map convert(@NonNull Object object) throws IntrospectionException { + return convert(BeanWrapper.getWrapper(object)); + } + + @NonNull + T convert(@NonNull Map item, Class targetType); +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DynamoRepository.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DynamoRepository.java new file mode 100644 index 0000000000..2d1a5f4b61 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/DynamoRepository.java @@ -0,0 +1,414 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.aws.dynamodb.conf.DynamoConfiguration; +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import jakarta.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.Put; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.PutItemResponse; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem; +import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest; +import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsResponse; +import software.amazon.awssdk.services.dynamodb.model.Update; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Utility class to simplifies operations with a {@link DynamoDbClient} working with a DynamoDB table whose name is specified by the bean {@link DynamoConfiguration#getTableName()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Requires(beans = { DynamoDbClient.class, DynamoConfiguration.class, DynamoDbConversionService.class }) +@Singleton +public class DynamoRepository { + private static final Logger LOG = LoggerFactory.getLogger(DynamoRepository.class); + private final DynamoDbClient dynamoDbClient; + private final DynamoConfiguration dynamoConfiguration; + + private final DynamoDbConversionService dynamoDbConversionService; + + protected DynamoRepository(DynamoDbClient dynamoDbClient, + DynamoConfiguration dynamoConfiguration, + DynamoDbConversionService dynamoDbConversionService) { + this.dynamoDbClient = dynamoDbClient; + this.dynamoConfiguration = dynamoConfiguration; + this.dynamoDbConversionService = dynamoDbConversionService; + } + + /** + * @param item DynamoDB Item + * @return A PutItem Request builder with the table name populated via {@link DynamoConfiguration#getTableName()}. + */ + @NonNull + public PutItemRequest.Builder putItemRequest(@NonNull Map item) { + return PutItemRequest.builder() + .tableName(dynamoConfiguration.getTableName()) + .item(item); + } + + /** + * @param item DynamoDB Item + * @return A put Item Response + */ + @NonNull + public PutItemResponse putItem(@NonNull Map item) { + return putItem(item, null); + } + + /** + * @param item Item to be converted to {@literal Map} + * @param builderConsumer PutItem Request Builder consumer + * @return A put Item Response + */ + public PutItemResponse putItem(@NonNull Object item, + @Nullable Consumer builderConsumer) { + return putItem(dynamoDbConversionService.convert(item), builderConsumer); + + } + + /** + * @param item DynamoDB Item + * @param builderConsumer PutItem Request Builder consumer + * @return A put Item Response + */ + public PutItemResponse putItem(@NonNull Map item, @Nullable Consumer builderConsumer) { + if (LOG.isTraceEnabled()) { + LOG.trace("****************************"); + for (String k : item.keySet()) { + LOG.trace("{}: {}", k, item.get(k)); + } + LOG.trace("****************************"); + } + PutItemRequest.Builder builder = putItemRequest(item); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return putItem(builder.build()); + } + + /** + * @param putItemRequest PutItem REquest + * @return A PutItem Response + */ + @NonNull + public PutItemResponse putItem(@NonNull PutItemRequest putItemRequest) { + PutItemResponse itemResponse = dynamoDbClient.putItem(putItemRequest); + if (LOG.isTraceEnabled()) { + LOG.trace("{}", itemResponse); + } + return itemResponse; + } + + /** + * @return The DynamoDB Client + */ + public DynamoDbClient getDynamoDbClient() { + return dynamoDbClient; + } + + /** + * @return DynamoDB Configuration + */ + public DynamoConfiguration getDynamoConfiguration() { + return dynamoConfiguration; + } + + /** + * @return GetItemRequest Builder with the table name populated via {@link DynamoConfiguration#getTableName()}. + */ + @NonNull + public GetItemRequest.Builder getItemBuilder() { + return GetItemRequest.builder() + .tableName(dynamoConfiguration.getTableName()); + } + + /** + * + * @param key Table Key + * @param targetType Target Type class + * @return An Optional Instance of the Target class if found. + * @param Target Type + */ + public Optional getItem(CompositeKey key, Class targetType) { + Map keyMap = mapForKey(key); + return getItem(keyMap, targetType); + } + + /** + * @param key {@link GetItemRequest.Builder#key}. + * @param builderConsumer GetItemRequest Builder consumer + * @return Get Item Response + */ + public GetItemResponse getItem(CompositeKey key, @Nullable Consumer builderConsumer) { + GetItemRequest.Builder builder = getItemBuilder(); + builder.key(mapForKey(key)); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return dynamoDbClient.getItem(builder.build()); + } + + /** + * @param builderConsumer GetItemRequest Builder consumer + * @return Get Item Response + */ + public GetItemResponse getItem(@Nullable Consumer builderConsumer) { + GetItemRequest.Builder builder = getItemBuilder(); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return dynamoDbClient.getItem(builder.build()); + } + + /** + * @return QueryRequest Builder with the table name populated via {@link DynamoConfiguration#getTableName()}. + */ + @NonNull + public QueryRequest.Builder queryRequestBuilder() { + return QueryRequest.builder() + .tableName(dynamoConfiguration.getTableName()); + } + + /** + * @param builderConsumer Query Request Builder Consumer + * @return Query Request + */ + @NonNull + public QueryRequest queryRequestBuilder(@Nullable Consumer builderConsumer) { + QueryRequest.Builder builder = queryRequestBuilder(); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return builder.build(); + } + + /** + * @param builderConsumer Query Request Builder Consumer + * @return QueryResponse + */ + @NonNull + public QueryResponse query(@Nullable Consumer builderConsumer) { + return query(queryRequestBuilder(builderConsumer)); + } + + /** + * @param queryRequest Query Request + * @return QueryResponse + */ + @NonNull + public QueryResponse query(@NonNull QueryRequest queryRequest) { + return dynamoDbClient.query(queryRequest); + } + + /** + * @param putBuilderConsumers PutBuilderConsumers + * @return The Transaction write Item response + */ + @NonNull + public TransactWriteItemsResponse transactWriteItems(@NonNull List> putBuilderConsumers) { + return transactWriteItems(putBuilderConsumers.stream().toArray(Consumer[]::new)); + } + + /** + * @param putBuilderConsumers PutBuilderConsumers + * @return The Transaction write Item response + */ + @NonNull + public TransactWriteItemsResponse transactWriteItems(@NonNull Consumer... putBuilderConsumers) { + TransactWriteItem[] transactWriteItemArr = new TransactWriteItem[putBuilderConsumers.length]; + int count = 0; + for (Consumer putBuilderConsumer : putBuilderConsumers) { + transactWriteItemArr[count++] = putTransactWriteItem(putBuilderConsumer); + } + return dynamoDbClient.transactWriteItems(TransactWriteItemsRequest.builder() + .transactItems(transactWriteItemArr) + .build()); + } + + /** + * + * @param puBuilderConsumer PutBuilderConsumer + * @return Transaction write item + */ + public TransactWriteItem putTransactWriteItem(@Nullable Consumer puBuilderConsumer) { + Put.Builder putBuidler = Put.builder() + .tableName(getDynamoConfiguration().getTableName()); + if (puBuilderConsumer != null) { + puBuilderConsumer.accept(putBuidler); + } + return TransactWriteItem.builder() + .put(putBuidler.build()) + .build(); + } + + /** + * + * @param updateBuilderConsumer updateBuilderConsumer + * @return Transaction write item + */ + public TransactWriteItem updateTransactWriteItem(@Nullable Consumer updateBuilderConsumer) { + Update.Builder updateBuilder = Update.builder() + .tableName(getDynamoConfiguration().getTableName()); + if (updateBuilderConsumer != null) { + updateBuilderConsumer.accept(updateBuilder); + } + return TransactWriteItem.builder() + .update(updateBuilder.build()) + .build(); + } + + /** + * @param transactWriteItemArr items to write + * @return The Transaction write Item response + */ + @NonNull + public TransactWriteItemsResponse transactWriteItems(TransactWriteItem... transactWriteItemArr) { + return dynamoDbClient.transactWriteItems(TransactWriteItemsRequest.builder() + .transactItems(transactWriteItemArr) + .build()); + } + + /** + * + * @param key Key + * @return Key Map + */ + @NonNull + public Map mapForKey(@NonNull CompositeKey key) { + if (key instanceof GlobalSecondaryIndex1) { + return Map.of(dynamoConfiguration.getGlobalSecondaryIndex1HashKey(), AttributeValueUtils.s(key.getPartionKey()), + dynamoConfiguration.getGlobalSecondaryIndex1SortKey(), AttributeValueUtils.s(key.getSortKey())); + } + return Map.of(dynamoConfiguration.getHashKey(), AttributeValueUtils.s(key.getPartionKey()), + dynamoConfiguration.getSortKey(), AttributeValueUtils.s(key.getSortKey())); + } + + /** + * + * @param key Table Key + * @param targetType Target Type class + * @return An Optional Instance of the Target class if found. + * @param Target Type + */ + public Optional getItem(Map key, Class targetType) { + GetItemResponse response = getItem(builder -> builder.key(key)); + if (!response.hasItem()) { + return Optional.empty(); + } + Map item = response.item(); + if (item == null) { + return Optional.empty(); + } + return Optional.of(dynamoDbConversionService.convert(item, targetType)); + } + + /** + * + * @return DynamoDB Conversion service. + */ + public DynamoDbConversionService getDynamoDbConversionService() { + return dynamoDbConversionService; + } + + /** + * @param key compositeKey + * @param updateItemRequestBuilderConsumer Update Item Request Builder Consumer + * @return Update Item Response + */ + @NonNull + public UpdateItemResponse updateItem(@NonNull CompositeKey key, @NonNull Consumer updateItemRequestBuilderConsumer) { + return dynamoDbClient.updateItem(updateItemRequestBuilder(key, updateItemRequestBuilderConsumer).build()); + } + + /** + * + * @param updateItemRequestBuilderConsumer Update Item Request Builder Consumer + * @return Update Item Response + */ + @NonNull + public UpdateItemResponse updateItem(@NonNull Consumer updateItemRequestBuilderConsumer) { + return dynamoDbClient.updateItem(updateItemRequestBuilder(updateItemRequestBuilderConsumer).build()); + } + + + /** + * + * @param updateItemRequestBuilder Update Item Request Builder + * @return Update Item Response + */ + @NonNull + public UpdateItemResponse updateItem(@NonNull UpdateItemRequest.Builder updateItemRequestBuilder) { + return dynamoDbClient.updateItem(updateItemRequestBuilder.build()); + } + + /** + * Instantiates UpdateItem Request Builder, populates its table name with {@link DynamoConfiguration#getTableName()}. + * @return UpdateItem Request Builder + */ + @NonNull + public UpdateItemRequest.Builder updateItemRequestBuilder() { + return UpdateItemRequest.builder() + .tableName(dynamoConfiguration.getTableName()); + } + + /** + * Instantiates UpdateItem Request Builder, populates its table name with {@link DynamoConfiguration#getTableName()} and passes it to the consumer. + * @param builderConsumer UpdateItem Request Builder Consumer + * @return UpdateItem Request Builder + */ + @NonNull + public UpdateItemRequest.Builder updateItemRequestBuilder(@Nullable Consumer builderConsumer) { + UpdateItemRequest.Builder builder = updateItemRequestBuilder(); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return builder; + } + + /** + * Instantiates UpdateItem Request Builder, populates its table name with {@link DynamoConfiguration#getTableName()} and passes it to the consumer. + * @param key Composite Key + * @param builderConsumer UpdateItem Request Builder Consumer + * @return UpdateItem Request Builder + */ + @NonNull + public UpdateItemRequest.Builder updateItemRequestBuilder(@NonNull CompositeKey key, @Nullable Consumer builderConsumer) { + UpdateItemRequest.Builder builder = updateItemRequestBuilder(); + builder.key(mapForKey(key)); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return builder; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/GlobalSecondaryIndex1.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/GlobalSecondaryIndex1.java new file mode 100644 index 0000000000..eaa1d1b0e7 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/GlobalSecondaryIndex1.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.NonNull; + +/** + * Marker for a Global Secondary Index 1 composite key. + * @author Sergio del Amo + * @since 4.0.0 + */ +public interface GlobalSecondaryIndex1 extends CompositeKey { + @NonNull + static GlobalSecondaryIndex1 of(@NonNull String hashKey, @NonNull String sortKey) { + return new GlobalSecondaryIndex1() { + @Override + public String getPartionKey() { + return hashKey; + } + + @Override + public String getSortKey() { + return sortKey; + } + }; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRow.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRow.java new file mode 100644 index 0000000000..6767982afe --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRow.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import jakarta.validation.constraints.NotBlank; + +/** + * Base class to extend from in a DynamoDB Single table design. + */ +@Introspected +public class SingleTableRow { + @NonNull + @NotBlank + private final String pk; + + @NonNull + @NotBlank + private final String sk; + + @NonNull + @NotBlank + private final String className; + + /** + * + * @param pk Primary Key + * @param sk Sort Key + * @param className Class Name + */ + public SingleTableRow(String pk, + String sk, + String className) { + this.pk = pk; + this.sk = sk; + this.className = className; + } + + /** + * + * @return Partition Key + */ + @NonNull + public String getPk() { + return pk; + } + + /** + * + * @return Sort Key + */ + @NonNull + public String getSk() { + return sk; + } + + /** + * + * @return Class Name + */ + public String getClassName() { + return className; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithOneGlobalSecondaryIndex.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithOneGlobalSecondaryIndex.java new file mode 100644 index 0000000000..d929f22e0a --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithOneGlobalSecondaryIndex.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; + +/** + * Base class to extend from in a DynamoDB Single table design with a Global Secondary Index. + */ +@Introspected +public class SingleTableRowWithOneGlobalSecondaryIndex extends SingleTableRow { + private final String gsi1Pk; + + private final String gsi1Sk; + + /** + * + * @param pk Partition Key + * @param sk Sort Key + * @param className Class Name + * @param gsi1Pk Global Secondary Index 1 Partition Key + * @param gsi1Sk Global Secondary Index 1 Sort Key + */ + public SingleTableRowWithOneGlobalSecondaryIndex(String pk, + String sk, + String className, + String gsi1Pk, + String gsi1Sk) { + super(pk, sk, className); + this.gsi1Pk = gsi1Pk; + this.gsi1Sk = gsi1Sk; + } + + /** + * + * @return Global Secondary Index 1 Partition Key + */ + @Nullable + public String getGsi1Pk() { + return gsi1Pk; + } + + /** + * + * @return Global Secondary Index 1 Sort Key + */ + @Nullable + public String getGsi1Sk() { + return gsi1Sk; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithThreeGlobalSecondaryIndex.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithThreeGlobalSecondaryIndex.java new file mode 100644 index 0000000000..d81adedd86 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithThreeGlobalSecondaryIndex.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; + +/** + * Base class to extend from in a DynamoDB Single table design with a Global Secondary Index. + */ +@Introspected +public class SingleTableRowWithThreeGlobalSecondaryIndex extends SingleTableRowWithTwoGlobalSecondaryIndex { + private final String gsi3Pk; + + private final String gsi3Sk; + + /** + * + * @param pk Partition Key + * @param sk Sort Key* + * @param className Class Name + * @param gsi1Pk Global Secondary Index 1 Partition Key + * @param gsi1Sk Global Secondary Index 1 Sort Key + * @param gsi2Pk Global Secondary Index 2 Partition Key + * @param gsi2Sk Global Secondary Index 2 Sort Key + * @param gsi3Pk Global Secondary Index 3 Partition Key + * @param gsi3Sk Global Secondary Index 3 Sort Key + */ + public SingleTableRowWithThreeGlobalSecondaryIndex(String pk, + String sk, + String className, + String gsi1Pk, + String gsi1Sk, + String gsi2Pk, + String gsi2Sk, + String gsi3Pk, + String gsi3Sk) { + super(pk, sk, className, gsi1Pk, gsi1Sk, gsi2Pk, gsi2Sk); + this.gsi3Pk = gsi3Pk; + this.gsi3Sk = gsi3Sk; + } + + /** + * + * @return Global Secondary Index 3 Partition Key + */ + @Nullable + public String getGsi3Pk() { + return gsi3Pk; + } + + /** + * + * @return Global Secondary Index 3 Sort Key + */ + @Nullable + public String getGsi3Sk() { + return gsi3Sk; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithTwoGlobalSecondaryIndex.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithTwoGlobalSecondaryIndex.java new file mode 100644 index 0000000000..9fa14499b6 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/SingleTableRowWithTwoGlobalSecondaryIndex.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; + +/** + * Base class to extend from in a DynamoDB Single table design with a Global Secondary Index. + */ +@Introspected +public class SingleTableRowWithTwoGlobalSecondaryIndex extends SingleTableRowWithOneGlobalSecondaryIndex { + private final String gsi2Pk; + + private final String gsi2Sk; + + /** + * + * @param pk Partition Key + * @param sk Sort Key + * @param className Class Name + * @param gsi1Pk Global Secondary Index 1 Partition Key + * @param gsi1Sk Global Secondary Index 1 Sort Key + * @param gsi2Pk Global Secondary Index 2 Partition Key + * @param gsi2Sk Global Secondary Index 2 Sort Key + */ + public SingleTableRowWithTwoGlobalSecondaryIndex(String pk, + String sk, + String className, + String gsi1Pk, + String gsi1Sk, + String gsi2Pk, + String gsi2Sk) { + super(pk, sk, className, gsi1Pk, gsi1Sk); + this.gsi2Pk = gsi2Pk; + this.gsi2Sk = gsi2Sk; + } + + /** + * + * @return Global Secondary Index 2 Partition Key + */ + @Nullable + public String getGsi2Pk() { + return gsi2Pk; + } + + /** + * + * @return Global Secondary Index 2 Sort Key + */ + @Nullable + public String getGsi2Sk() { + return gsi2Sk; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/conf/DynamoConfiguration.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/conf/DynamoConfiguration.java new file mode 100644 index 0000000000..86b48d43c2 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/conf/DynamoConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.conf; + +import io.micronaut.core.annotation.NonNull; + +/** + * Configuration to define the DynamoDB table name. + * @author Sergio del Amo + * @since 4.0.0 + */ +public interface DynamoConfiguration { + /** + * + * @return The DynamoDB Table Name + */ + @NonNull + String getTableName(); + + /** + * + * @return The DynamoDB Table Hash Key name + */ + @NonNull + String getHashKey(); + + /** + * + * @return The DynamoDB Table Sort Key name + */ + @NonNull + String getSortKey(); + + /** + * + * @return The DynamoDB Table Hash Key name for the Global Secondary Index 1. + */ + @NonNull + String getGlobalSecondaryIndex1HashKey(); + + /** + * + * @return The DynamoDB Table Sort Key name for the Global Secondary Index 1. + */ + @NonNull + String getGlobalSecondaryIndex1SortKey(); +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/conf/DynamoConfigurationProperties.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/conf/DynamoConfigurationProperties.java new file mode 100644 index 0000000000..6e123b9379 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/conf/DynamoConfigurationProperties.java @@ -0,0 +1,123 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.conf; + +import io.micronaut.context.annotation.ConfigurationProperties; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import jakarta.validation.constraints.NotBlank; + +/** + * {@link ConfigurationProperties} implementation of {@link DynamoConfiguration}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Requires(property = "dynamodb.table-name") +@ConfigurationProperties("dynamodb") +public class DynamoConfigurationProperties implements DynamoConfiguration { + + public static final String DEFAULT_HASH_KEY = "pk"; + public static final String DEFAULT_SORT_KEY = "sk"; + public static final String DEFAULT_GSI1_HASH_KEY = "gsi1pk"; + public static final String DEFAULT_GSI1_SORT_KEY = "gsi1sk"; + + @NonNull + @NotBlank + private String tableName; + + @NonNull + @NotBlank + private String hashKey = DEFAULT_HASH_KEY; + + @NonNull + @NotBlank + private String sortKey = DEFAULT_SORT_KEY; + + @NonNull + @NotBlank + private String globalSecondaryIndex1HashKey = DEFAULT_GSI1_HASH_KEY; + + @NonNull + @NotBlank + private String globalSecondaryIndex1SortKey = DEFAULT_GSI1_SORT_KEY; + + /** + * + * @param tableName The DynamoDB table name + */ + public void setTableName(@NonNull String tableName) { + this.tableName = tableName; + } + + @Override + @NonNull + public String getTableName() { + return tableName; + } + + @Override + public String getHashKey() { + return hashKey; + } + + /** + * + * @param hashKey The Table hash Key name. Default value: {@value #DEFAULT_HASH_KEY}. + */ + public void setHashKey(String hashKey) { + this.hashKey = hashKey; + } + + @Override + public String getSortKey() { + return sortKey; + } + + /** + * + * @param sortKey The Table hash Key name. Default value: {@value #DEFAULT_SORT_KEY}. + */ + public void setSortKey(String sortKey) { + this.sortKey = sortKey; + } + + @Override + public String getGlobalSecondaryIndex1HashKey() { + return globalSecondaryIndex1HashKey; + } + + /** + * + * @param globalSecondaryIndex1HashKey Global Secondary Index 1 hash Key name. Default value: {@value #DEFAULT_GSI1_HASH_KEY}. + */ + public void setGlobalSecondaryIndex1HashKey(String globalSecondaryIndex1HashKey) { + this.globalSecondaryIndex1HashKey = globalSecondaryIndex1HashKey; + } + + @Override + public String getGlobalSecondaryIndex1SortKey() { + return globalSecondaryIndex1SortKey; + } + + /** + * + * @param globalSecondaryIndex1SortKey Global Secondary Index 1 hash Key name. Default value: {@value #DEFAULT_GSI1_SORT_KEY}. + */ + public void setGlobalSecondaryIndex1SortKey(String globalSecondaryIndex1SortKey) { + this.globalSecondaryIndex1SortKey = globalSecondaryIndex1SortKey; + } +} + diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicBooleanToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicBooleanToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..1313b2f6fa --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicBooleanToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * {@link TypeConverter} from {@link AtomicBoolean} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AtomicBooleanToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(AtomicBoolean object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + return Optional.of(AttributeValue.builder().bool(object.get()).build()); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicIntegerToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicIntegerToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..eb65b9ad50 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicIntegerToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * {@link TypeConverter} from {@link AtomicInteger} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AtomicIntegerToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(AtomicInteger object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicLongToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicLongToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..8fc756525a --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AtomicLongToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +/** + * {@link TypeConverter} from {@link AtomicLong} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AtomicLongToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(AtomicLong object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicBooleanTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicBooleanTypeConverter.java new file mode 100644 index 0000000000..0d1f8a8d71 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicBooleanTypeConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link AtomicBoolean}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToAtomicBooleanTypeConverter implements TypeConverter { + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + if (object.bool() == null) { + return Optional.empty(); + } + return Optional.of(new AtomicBoolean(object.bool())); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicIntegerTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicIntegerTypeConverter.java new file mode 100644 index 0000000000..55531b8ae5 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicIntegerTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link AtomicInteger}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToAtomicIntegerTypeConverter implements TypeConverter { + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(new AtomicInteger(Integer.parseInt(value))); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicLongTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicLongTypeConverter.java new file mode 100644 index 0000000000..93430577e9 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToAtomicLongTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link AtomicLong}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToAtomicLongTypeConverter implements TypeConverter { + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(new AtomicLong(Long.parseLong(value))); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBigDecimalTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBigDecimalTypeConverter.java new file mode 100644 index 0000000000..a509b1d1c0 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBigDecimalTypeConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.math.BigDecimal; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link BigDecimal} with {@link BigDecimal#BigDecimal(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToBigDecimalTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(new BigDecimal(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBigIntegerTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBigIntegerTypeConverter.java new file mode 100644 index 0000000000..85c651ed88 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBigIntegerTypeConverter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + + +import java.math.BigInteger; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link BigInteger} with {@link BigInteger#BigInteger(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToBigIntegerTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(new BigInteger(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBooleanTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBooleanTypeConverter.java new file mode 100644 index 0000000000..27905a78fb --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToBooleanTypeConverter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Boolean}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToBooleanTypeConverter implements TypeConverter { + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + if (object.bool() != null) { + return Optional.of(object.bool()); + } + return Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToCharSequenceTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToCharSequenceTypeConverter.java new file mode 100644 index 0000000000..e92bd8cce1 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToCharSequenceTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link CharSequence}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToCharSequenceTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + return Optional.of(object.s()); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToCharacterTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToCharacterTypeConverter.java new file mode 100644 index 0000000000..d422f9dbe9 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToCharacterTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Character}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToCharacterTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(value.charAt(0)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToDoubleTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToDoubleTypeConverter.java new file mode 100644 index 0000000000..69553f5732 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToDoubleTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Double} with {@link Double#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToDoubleTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(Double.valueOf(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToDurationTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToDurationTypeConverter.java new file mode 100644 index 0000000000..56bc14f56f --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToDurationTypeConverter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.time.Duration; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Duration}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToDurationTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(convertNumber(value)); + } + + private Duration convertNumber(String value) { + String[] splitOnDecimal = splitNumberOnDecimal(value); + + long seconds = Long.parseLong(splitOnDecimal[0]); + int nanoAdjustment = Integer.parseInt(splitOnDecimal[1]); + + if (seconds < 0) { + nanoAdjustment = -nanoAdjustment; + } + + return Duration.ofSeconds(seconds, nanoAdjustment); + } + + private static String[] splitNumberOnDecimal(String valueToSplit) { + int i = valueToSplit.indexOf('.'); + if (i == -1) { + return new String[] { valueToSplit, "0" }; + } else { + // Ends with '.' is not supported. + return new String[] { valueToSplit.substring(0, i), valueToSplit.substring(i + 1) }; + } + } + +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToEnumTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToEnumTypeConverter.java new file mode 100644 index 0000000000..6e323578ac --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToEnumTypeConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Enum}. + * @author Sergio del Amo + * @since 4.0.0 + * @param Enum Type + */ +@Prototype +public class AttributeValueToEnumTypeConverter> implements TypeConverter { + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(T.valueOf(targetType, value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToFloatTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToFloatTypeConverter.java new file mode 100644 index 0000000000..ffc453cadb --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToFloatTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Float} with {@link Float#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToFloatTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(Float.valueOf(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToInstantTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToInstantTypeConverter.java new file mode 100644 index 0000000000..de6ebcaf5b --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToInstantTypeConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Instant} using {@link Instant#parse(CharSequence)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToInstantTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToInstantTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(Instant.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to Instant", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToIntegerTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToIntegerTypeConverter.java new file mode 100644 index 0000000000..df652ef301 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToIntegerTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Integer}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToIntegerTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + return Optional.of(Integer.valueOf(object.n())); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalDateTimeTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalDateTimeTypeConverter.java new file mode 100644 index 0000000000..d7d613ac65 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalDateTimeTypeConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link LocalDateTime}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToLocalDateTimeTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToLocalDateTimeTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + try { + return Optional.of(LocalDateTime.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to LocalDateTime", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalDateTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalDateTypeConverter.java new file mode 100644 index 0000000000..c38cf03de9 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalDateTypeConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link LocalDate}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToLocalDateTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToLocalDateTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + try { + return Optional.of(LocalDate.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to LocalDate", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalTimeTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalTimeTypeConverter.java new file mode 100644 index 0000000000..3a7e25ac0a --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocalTimeTypeConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.DateTimeException; +import java.time.LocalTime; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link LocalTime} using {@link LocalTime#parse(CharSequence)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToLocalTimeTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToLocalTimeTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(LocalTime.parse(value)); + } catch (DateTimeException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to LocalTime", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocaleTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocaleTypeConverter.java new file mode 100644 index 0000000000..a9e4d8a15d --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLocaleTypeConverter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Locale; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Locale} with {@link Locale#forLanguageTag(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToLocaleTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToLocaleTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(Locale.forLanguageTag(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLongTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLongTypeConverter.java new file mode 100644 index 0000000000..2e88808a50 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToLongTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Long} with {@link Long#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToLongTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(Long.valueOf(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToMonthDayTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToMonthDayTypeConverter.java new file mode 100644 index 0000000000..5e0ddeea5b --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToMonthDayTypeConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.MonthDay; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link java.time.MonthDay} using {@link java.time.MonthDay#parse(CharSequence)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToMonthDayTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToMonthDayTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(MonthDay.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to MonthDay", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOffsetDateTimeTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOffsetDateTimeTypeConverter.java new file mode 100644 index 0000000000..7ffe74caa1 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOffsetDateTimeTypeConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link OffsetDateTime} using {@link OffsetDateTime#parse(CharSequence)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToOffsetDateTimeTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToOffsetDateTimeTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(OffsetDateTime.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to OffsetDateTime", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalDoubleTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalDoubleTypeConverter.java new file mode 100644 index 0000000000..4d885f5ac0 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalDoubleTypeConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link OptionalDouble} with {@link OptionalDouble#of(double)} and {@link Double#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToOptionalDoubleTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(OptionalDouble.of(Double.parseDouble(value))); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalIntTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalIntTypeConverter.java new file mode 100644 index 0000000000..5ab83bd4e4 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalIntTypeConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalInt; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link OptionalInt} with {@link OptionalInt#of(int)} and {@link Double#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToOptionalIntTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(OptionalInt.of(Integer.parseInt(value))); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalLongTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalLongTypeConverter.java new file mode 100644 index 0000000000..142d6ee998 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToOptionalLongTypeConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalLong; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link OptionalLong} with {@link OptionalLong#of(long)} and {@link Long#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToOptionalLongTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(OptionalLong.of(Long.valueOf(value))); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToPeriodTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToPeriodTypeConverter.java new file mode 100644 index 0000000000..4f7ae1d67e --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToPeriodTypeConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Period; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Period} using {@link Period#parse(CharSequence)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToPeriodTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToPeriodTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(Period.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to MonthDay", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToShortTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToShortTypeConverter.java new file mode 100644 index 0000000000..34ba3ee147 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToShortTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link Short} with {@link Short#valueOf(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToShortTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.n(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(Short.valueOf(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToStringBufferTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToStringBufferTypeConverter.java new file mode 100644 index 0000000000..ad70b91730 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToStringBufferTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link StringBuffer}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToStringBufferTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(new StringBuffer(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToStringBuilderTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToStringBuilderTypeConverter.java new file mode 100644 index 0000000000..9f5a5e0e17 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToStringBuilderTypeConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link StringBuilder}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToStringBuilderTypeConverter implements TypeConverter { + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + return Optional.of(new StringBuilder(value)); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToURITypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToURITypeConverter.java new file mode 100644 index 0000000000..f4de0ef5f1 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToURITypeConverter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.net.URI; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link URI} to {@link AttributeValue} with {@link URI#create(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToURITypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToURITypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(URI.create(value)); + } catch (IllegalArgumentException e) { + LOG.warn("Malformed URL {}", value); + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToURLTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToURLTypeConverter.java new file mode 100644 index 0000000000..a62ee4c406 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToURLTypeConverter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link URL} to {@link AttributeValue} with {@link URL#URL(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToURLTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToURLTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(new URL(value)); + } catch (MalformedURLException e) { + LOG.warn("Malformed URL {}", value); + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToUUIDTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToUUIDTypeConverter.java new file mode 100644 index 0000000000..1e56b0df0d --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToUUIDTypeConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.format.DateTimeParseException; +import java.util.Optional; +import java.util.UUID; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link java.util.UUID} using {@link UUID#fromString(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToUUIDTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToUUIDTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + try { + return Optional.of(UUID.fromString(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to UUID", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZoneIdTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZoneIdTypeConverter.java new file mode 100644 index 0000000000..1fc2255510 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZoneIdTypeConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.DateTimeException; +import java.time.ZoneId; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link ZoneId} using {@link ZoneId#of(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToZoneIdTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToZoneIdTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + try { + return Optional.of(ZoneId.of(value)); + } catch (DateTimeException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to ZoneId", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZoneOffsetTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZoneOffsetTypeConverter.java new file mode 100644 index 0000000000..53ebeea769 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZoneOffsetTypeConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.DateTimeException; +import java.time.ZoneOffset; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link ZoneOffset} using {@link ZoneOffset#of(String)}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToZoneOffsetTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToZoneOffsetTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + try { + return Optional.of(ZoneOffset.of(value)); + } catch (DateTimeException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to ZoneId", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZonedDateTimeTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZonedDateTimeTypeConverter.java new file mode 100644 index 0000000000..7177438bfc --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/AttributeValueToZonedDateTimeTypeConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link AttributeValue} to {@link ZonedDateTime}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class AttributeValueToZonedDateTimeTypeConverter implements TypeConverter { + private static final Logger LOG = LoggerFactory.getLogger(AttributeValueToZonedDateTimeTypeConverter.class); + + @Override + public Optional convert(AttributeValue object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + String value = object.s(); + try { + return Optional.of(ZonedDateTime.parse(value)); + } catch (DateTimeParseException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Could not parse {} to ZonedDateTime", value); + } + return Optional.empty(); + } + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BigDecimalToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BigDecimalToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..e541106f64 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BigDecimalToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.math.BigDecimal; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link BigDecimal} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class BigDecimalToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(BigDecimal object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BigIntegerToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BigIntegerToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..1abf6d16b8 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BigIntegerToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.math.BigInteger; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link BigInteger} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class BigIntegerToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(BigInteger object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BooleanToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BooleanToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..2329529a21 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/BooleanToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Boolean} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class BooleanToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Boolean object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + return Optional.of(AttributeValue.builder().bool(object).build()); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/CharSequenceToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/CharSequenceToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..687bdde345 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/CharSequenceToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import io.micronaut.core.util.StringUtils; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link CharSequence} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class CharSequenceToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(CharSequence object, Class targetType, ConversionContext context) { + return StringUtils.isNotEmpty(object) ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/CharacterToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/CharacterToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..38bf5fb6e1 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/CharacterToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Character} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class CharacterToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Character object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/DoubleToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/DoubleToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..a8cc9a7b28 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/DoubleToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Double} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class DoubleToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Double object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/DurationToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/DurationToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..1765daf67d --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/DurationToAttributeValueTypeConverter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Duration; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Duration} to {@link AttributeValue} using {@link Duration#toString()}. + * + *

+ * This stores and reads values in DynamoDB as a number, so that they can be sorted numerically as part of a sort key. + * + *

+ * Durations are stored in the format "[-]X[.YYYYYYYYY]", where X is the number of seconds in the duration, and Y is the number of + * nanoseconds in the duration, left padded with zeroes to a length of 9. The Y and decimal point may be excluded for durations + * that are of whole seconds. The duration may be preceded by a - to indicate a negative duration. + * + *

+ * Examples: + *

    + *
  • {@code Duration.ofDays(1)} is stored as {@code ItemAttributeValueMapper.fromNumber("86400")}
  • + *
  • {@code Duration.ofSeconds(9)} is stored as {@code ItemAttributeValueMapper.fromNumber("9")}
  • + *
  • {@code Duration.ofSeconds(-9)} is stored as {@code ItemAttributeValueMapper.fromNumber("-9")}
  • + *
  • {@code Duration.ofNanos(1_234_567_890)} is stored as {@code ItemAttributeValueMapper.fromNumber("1.234567890")}
  • + *
  • {@code Duration.ofMillis(1)} is stored as {@code ItemAttributeValueMapper.fromNumber("0.001000000")}
  • + *
  • {@code Duration.ofNanos(1)} is stored as {@code ItemAttributeValueMapper.fromNumber("0.000000001")}
  • + *
+ * + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class DurationToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Duration object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } + return Optional.of(AttributeValue.builder() + .n(object.getSeconds() + + (object.getNano() == 0 ? "" : "." + padLeft(9, object.getNano()))) + .build()); + } + + public static String padLeft(int paddingAmount, int valueToPad) { + String result; + String value = Integer.toString(valueToPad); + if (value.length() == paddingAmount) { + result = value; + } else { + int padding = paddingAmount - value.length(); + StringBuilder sb = new StringBuilder(paddingAmount); + for (int i = 0; i < padding; i++) { + sb.append('0'); + } + result = sb.append(value).toString(); + } + return result; + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/EnumToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/EnumToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..8fb32be1cb --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/EnumToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Enum} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + * @param Enum Type + */ +@Prototype +public class EnumToAttributeValueTypeConverter> implements TypeConverter { + @Override + public Optional convert(T object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/FloatToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/FloatToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..2b9dd26575 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/FloatToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Float} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class FloatToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Float object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/InstantToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/InstantToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..9423757ab0 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/InstantToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Instant} to {@link AttributeValue} using {@link Instant#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class InstantToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Instant object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/IntegerToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/IntegerToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..1c926c19b3 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/IntegerToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Integer} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class IntegerToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Integer object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalDateTimeToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalDateTimeToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..a99e0513cb --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalDateTimeToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link LocalDateTime} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class LocalDateTimeToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(LocalDateTime object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalDateToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalDateToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..9888947e3b --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalDateToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalDate; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link LocalDate} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class LocalDateToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(LocalDate object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalTimeToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalTimeToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..c5d83db015 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocalTimeToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalTime; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link LocalTime} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class LocalTimeToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(LocalTime object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocaleToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocaleToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..e18aec353c --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LocaleToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.util.Locale; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Locale} to {@link AttributeValue} with {@link java.util.Locale#toLanguageTag()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class LocaleToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Locale object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toLanguageTag())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LongToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LongToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..98c037dd34 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/LongToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Long} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class LongToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Long object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/MonthDayToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/MonthDayToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..39a25d66ed --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/MonthDayToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.time.MonthDay; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link MonthDay} to {@link AttributeValue} with {@link MonthDay#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class MonthDayToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(MonthDay object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OffsetDateTimeToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OffsetDateTimeToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..80c103bc5b --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OffsetDateTimeToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.OffsetDateTime; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link OffsetDateTime} to {@link AttributeValue} with {@link OffsetDateTime#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class OffsetDateTimeToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(OffsetDateTime object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalDoubleToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalDoubleToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..5e66f4b815 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalDoubleToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * {@link TypeConverter} from {@link OptionalDouble} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class OptionalDoubleToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(OptionalDouble object, Class targetType, ConversionContext context) { + return object != null && object.isPresent() ? + Optional.of(AttributeValueUtils.n("" + object.getAsDouble())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalIntToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalIntToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..00fe756ba7 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalIntToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalInt; + +/** + * {@link TypeConverter} from {@link OptionalInt} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class OptionalIntToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(OptionalInt object, Class targetType, ConversionContext context) { + return object != null && object.isPresent() ? + Optional.of(AttributeValueUtils.n("" + object.getAsInt())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalLongToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalLongToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..6815ed9b6b --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/OptionalLongToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalLong; + +/** + * {@link TypeConverter} from {@link OptionalLong} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class OptionalLongToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(OptionalLong object, Class targetType, ConversionContext context) { + return object != null && object.isPresent() ? + Optional.of(AttributeValueUtils.n("" + object.getAsLong())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/PeriodToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/PeriodToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..783ec4d03c --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/PeriodToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Period; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Period} to {@link AttributeValue} with {@link Period#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class PeriodToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Period object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ShortToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ShortToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..8c7530764f --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ShortToAttributeValueTypeConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link Short} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class ShortToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(Short object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.n(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/StringBufferToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/StringBufferToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..7befb10e06 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/StringBufferToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import io.micronaut.core.util.StringUtils; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link StringBuffer} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class StringBufferToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(StringBuffer object, Class targetType, ConversionContext context) { + return StringUtils.isNotEmpty(object) ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/StringBuilderToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/StringBuilderToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..f361b32502 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/StringBuilderToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import io.micronaut.core.util.StringUtils; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link StringBuilder} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class StringBuilderToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(StringBuilder object, Class targetType, ConversionContext context) { + return StringUtils.isNotEmpty(object) ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/URIToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/URIToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..caeaa699f1 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/URIToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.net.URI; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link URI} to {@link AttributeValue} with {@link URI#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class URIToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(URI object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/URLToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/URLToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..91c8cd38d9 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/URLToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.net.URL; + +/** + * {@link TypeConverter} from {@link URL} to {@link AttributeValue} with {@link URL#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class URLToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(URL object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/UUIDToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/UUIDToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..a10a991f78 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/UUIDToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.UUID; + +/** + * {@link TypeConverter} from {@link UUID} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class UUIDToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(UUID object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZoneIdToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZoneIdToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..99f3c415c7 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZoneIdToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.ZoneId; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link ZoneId} to {@link AttributeValue} using {@link ZoneId#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class ZoneIdToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(ZoneId object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZoneOffsetToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZoneOffsetToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..e5a4875a69 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZoneOffsetToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.ZoneOffset; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link ZoneOffset} to {@link AttributeValue} using {@link ZoneOffset#toString()}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class ZoneOffsetToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(ZoneOffset object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZonedDateTimeToAttributeValueTypeConverter.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZonedDateTimeToAttributeValueTypeConverter.java new file mode 100644 index 0000000000..49a444ff50 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/converters/ZonedDateTimeToAttributeValueTypeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.ZonedDateTime; +import java.util.Optional; + +/** + * {@link TypeConverter} from {@link ZonedDateTime} to {@link AttributeValue}. + * @author Sergio del Amo + * @since 4.0.0 + */ +@Prototype +public class ZonedDateTimeToAttributeValueTypeConverter implements TypeConverter { + @Override + public Optional convert(ZonedDateTime object, Class targetType, ConversionContext context) { + return object != null ? + Optional.of(AttributeValueUtils.s(object.toString())) : + Optional.empty(); + } +} diff --git a/dynamodb/src/main/java/io/micronaut/aws/dynamodb/utils/AttributeValueUtils.java b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/utils/AttributeValueUtils.java new file mode 100644 index 0000000000..af33adce93 --- /dev/null +++ b/dynamodb/src/main/java/io/micronaut/aws/dynamodb/utils/AttributeValueUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aws.dynamodb.utils; + +import io.micronaut.core.annotation.NonNull; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * Utility class to work with {@link AttributeValue} builders. + */ +public final class AttributeValueUtils { + + private AttributeValueUtils() { + + } + + /** + * + * @param value Value + * @return The Attribute value + */ + @NonNull + public static AttributeValue s(@NonNull String value) { + return AttributeValue.builder().s(value).build(); + } + + /** + * + * @param value Value + * @return The Attribute value + */ + @NonNull + public static AttributeValue n(@NonNull String value) { + return AttributeValue.builder().n(value).build(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/DynamoDbConversionServiceTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/DynamoDbConversionServiceTest.java new file mode 100644 index 0000000000..a3135b9e66 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/DynamoDbConversionServiceTest.java @@ -0,0 +1,172 @@ +package io.micronaut.aws.dynamodb; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.beans.exceptions.IntrospectionException; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class DynamoDbConversionServiceTest { + + @Test + void youCannotConvertFromAPojoWhichIsNotIntrospected(DynamoDbConversionService dbConversionService) { + assertNotNull(dbConversionService); + Executable e = () -> dbConversionService.convert(new User("WARRENBUFFETT", "Warren Buffet", "Admin")); + IntrospectionException thrown = assertThrows(IntrospectionException.class, e); + } + + @Test + void canConvertFromAnIntrospectedPojo(DynamoDbConversionService dbConversionService) { + assertNotNull(dbConversionService); + Map item = dbConversionService.convert(new UserItem("ORG#BERKSHIRE", "USER#WARRENTBUFFET", "WARRENBUFFETT", "Warren Buffet", "Admin")); + assertNotNull(item); + assertTrue(item.containsKey("pk")); + assertTrue(item.containsKey("sk")); + assertTrue(item.containsKey("id")); + assertTrue(item.containsKey("username")); + assertTrue(item.containsKey("role")); + assertEquals("ORG#BERKSHIRE", item.get("pk").s()); + assertEquals("USER#WARRENTBUFFET", item.get("sk").s()); + assertEquals("WARRENBUFFETT", item.get("id").s()); + assertEquals("Warren Buffet", item.get("username").s()); + assertEquals("Admin", item.get("role").s()); + + + UserItem userItem = (UserItem) dbConversionService.convert(item, UserItem.class); + assertNotNull(userItem); + assertEquals("ORG#BERKSHIRE", userItem.getPk()); + assertEquals("USER#WARRENTBUFFET", userItem.getSk()); + assertEquals("WARRENBUFFETT", userItem.getId()); + assertEquals("Warren Buffet", userItem.getUsername()); + assertEquals("Admin", userItem.getRole()); + } + + static class User { + private final String id; + private final String username; + private final String role; + + User(String id, String username, String role) { + this.id = id; + this.username = username; + this.role = role; + } + + public String getId() { + return id; + } + + String getUsername() { + return username; + } + + String getRole() { + return role; + } + } + + static class Organization { + private final String id; + private final String organizationName; + private final String subscriptionLevel; + + Organization(String id, String organizationName, String subscriptionLevel) { + this.id = id; + this.organizationName = organizationName; + this.subscriptionLevel = subscriptionLevel; + } + + String getId() { + return id; + } + + String getOrganizationName() { + return organizationName; + } + + String getSubscriptionLevel() { + return subscriptionLevel; + } + } + + @Introspected + static class OrganizationItem { + private final String pk; + private final String sk; + private final String id; + private final String organizationName; + private final String subscriptionLevel; + + OrganizationItem(String pk, String sk, String id, String organizationName, String subscriptionLevel) { + this.pk = pk; + this.sk = sk; + this.id = id; + this.organizationName = organizationName; + this.subscriptionLevel = subscriptionLevel; + } + + String getPk() { + return pk; + } + + String getSk() { + return sk; + } + + String getId() { + return id; + } + + String getOrganizationName() { + return organizationName; + } + + String getSubscriptionLevel() { + return subscriptionLevel; + } + } + + @Introspected + static class UserItem { + private final String pk; + private final String sk; + private final String id; + private final String username; + private final String role; + + UserItem(String pk, String sk, String id, String username, String role) { + this.pk = pk; + this.sk = sk; + this.id = id; + this.username = username; + this.role = role; + } + + String getPk() { + return pk; + } + + String getSk() { + return sk; + } + + String getId() { + return id; + } + + String getUsername() { + return username; + } + + String getRole() { + return role; + } + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/SessionStoreTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/SessionStoreTest.java new file mode 100644 index 0000000000..ce3fb244bb --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/SessionStoreTest.java @@ -0,0 +1,350 @@ +package io.micronaut.aws.dynamodb; + +import io.micronaut.aws.dynamodb.conf.DynamoConfiguration; +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.aws.dynamodb.utils.DynamoDbLocal; +import io.micronaut.aws.dynamodb.utils.TestDynamoRepository; +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.http.HttpHeaderValues; +import io.micronaut.http.HttpHeaders; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MutableHttpResponse; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Delete; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Header; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Status; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.serde.annotation.Serdeable; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.test.support.TestPropertyProvider; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.function.Executable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.BatchWriteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.BatchWriteItemResponse; +import software.amazon.awssdk.services.dynamodb.model.BillingMode; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.DeleteRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import software.amazon.awssdk.services.dynamodb.model.WriteRequest; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Property(name = "dynamodb.table-name", value = "sessions") +@Property(name = "spec.name", value = "SessionStoreTest") +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SessionStoreTest implements TestPropertyProvider { + @Override + public Map getProperties() { + return DynamoDbLocal.getProperties(); + } + + @Inject + @Client("/") + HttpClient httpClient; + + @Test + void sessionStore() { + BlockingHttpClient client = httpClient.toBlocking(); + HttpRequest createSessionRequest = HttpRequest.POST("/sessions", new Credentials("myuser", "Password1")); + HttpResponse sessionResponseHttpResponse = client.exchange(createSessionRequest, SessionResponse.class); + assertEquals(HttpStatus.CREATED, sessionResponseHttpResponse.getStatus()); + SessionResponse sessionResponse = sessionResponseHttpResponse.body(); + assertNotNull(sessionResponse); + assertNotNull(sessionResponse.getSessionId()); + + Executable e = () -> client.exchange(HttpRequest.GET("/sessions").bearerAuth("foobar")); + HttpClientResponseException thrown = assertThrows(HttpClientResponseException.class, e); + assertEquals(HttpStatus.UNAUTHORIZED, thrown.getStatus()); + + HttpResponse userHttpResponse = client.exchange(HttpRequest.GET("/sessions").bearerAuth(sessionResponse.getSessionId()), User.class); + assertEquals(HttpStatus.OK, userHttpResponse.getStatus()); + User user = userHttpResponse.body(); + assertNotNull(user); + assertNotNull(user.getUsername()); + assertEquals("myuser", user.getUsername()); + + HttpResponse deleteHttpResponse = client.exchange(HttpRequest.DELETE("/sessions").bearerAuth(sessionResponse.getSessionId())); + assertEquals(HttpStatus.NO_CONTENT, deleteHttpResponse.getStatus()); + + e = () -> client.exchange(HttpRequest.GET("/sessions").bearerAuth(sessionResponse.getSessionId())); + thrown = assertThrows(HttpClientResponseException.class, e); + assertEquals(HttpStatus.UNAUTHORIZED, thrown.getStatus()); + } + + @Requires(property = "spec.name", value = "SessionStoreTest") + @Singleton + static class BootStrap extends TestDynamoRepository { + public BootStrap(DynamoDbClient dynamoDbClient, + DynamoConfiguration dynamoConfiguration) { + super(dynamoDbClient, dynamoConfiguration); + } + + @Override + public CreateTableRequest createTableRequest(String tableName) { + return CreateTableRequest.builder() + .attributeDefinitions(attributeDefinition("sessionId", ScalarAttributeType.S), attributeDefinition("username", ScalarAttributeType.S)) + .keySchema(Collections.singletonList(keySchemaElement("sessionId", KeyType.HASH))) + .globalSecondaryIndexes(gsi("userIndex", "username", ProjectionType.KEYS_ONLY)) + .billingMode(BillingMode.PAY_PER_REQUEST) + .tableName(tableName) + .build(); + } + } + + @Requires(property = "spec.name", value = "SessionStoreTest") + @Singleton + static class SessionRepository { + private static final Logger LOG = LoggerFactory.getLogger(SessionRepository.class); + private final SessionGenerator sessionGenerator; + private final DynamoDbConversionService dynamoDbConversionService; + private final DynamoRepository dynamoRepository; + + SessionRepository(SessionGenerator sessionGenerator, + DynamoDbConversionService dynamoDbConversionService, + DynamoRepository dynamoRepository) { + this.sessionGenerator = sessionGenerator; + this.dynamoDbConversionService = dynamoDbConversionService; + this.dynamoRepository = dynamoRepository; + } + + @NonNull + Optional save(@NonNull @NotBlank String username) { + String sessionId = sessionGenerator.generate(); + LocalDateTime createdAt = LocalDateTime.now(); + LocalDateTime expiresAt = createdAt.plusWeeks(1); + ZoneId zoneId = ZoneId.systemDefault(); + long ttl = expiresAt.atZone(zoneId).toEpochSecond(); + SessionItem sessionItem = new SessionItem(sessionId, username, createdAt, expiresAt, ttl); + Map item = dynamoDbConversionService.convert(sessionItem); + try { + dynamoRepository.putItem(item, builder -> builder.conditionExpression("attribute_not_exists(sessionId)").build()); + return Optional.of(sessionId); + } catch (ConditionalCheckFailedException e) { + LOG.warn("Holy moley -- a UUID collision!"); + } + return Optional.empty(); + } + + @NonNull + public Optional findUsernameBySessionId(@NonNull @NotBlank String sessionId) { + return dynamoRepository.getItem(Collections.singletonMap("sessionId", AttributeValueUtils.s(sessionId)), User.class) + .map(User::getUsername); + } + + public void deleteSessionsByUsername(@NonNull @NotBlank String username) { + QueryRequest queryRequest = dynamoRepository.queryRequestBuilder() + .indexName("userIndex") + .keyConditionExpression("#username = :username") + .expressionAttributeNames(Collections.singletonMap("#username", "username")) + .expressionAttributeValues(Collections.singletonMap(":username", AttributeValueUtils.s(username))) + .build(); + QueryResponse response = dynamoRepository.query(queryRequest); + List deletes = response.items() + .stream() + .filter(item -> item.containsKey("sessionId")) + .map(item -> DeleteRequest.builder() + .key(Collections.singletonMap("sessionId", item.get("sessionId"))) + .build()) + .map(deleteRequest -> WriteRequest.builder().deleteRequest(deleteRequest).build()) + .toList(); + BatchWriteItemResponse batchWriteItemResponse = dynamoRepository.getDynamoDbClient() + .batchWriteItem(BatchWriteItemRequest.builder() + .requestItems(Collections.singletonMap("sessions", deletes)) + .build()); + } + } + + @Introspected + static class SessionItem { + @NonNull + @NotBlank + private final String sessionId; + @NonNull + @NotBlank + private final String username; + + @NonNull + @NotNull + private final LocalDateTime createdAt; + + @NonNull + @NotNull + private final LocalDateTime expiresAt; + + private final long ttl; + + SessionItem(String sessionId, String username, LocalDateTime createdAt, LocalDateTime expiresAt, long ttl) { + this.sessionId = sessionId; + this.username = username; + this.createdAt = createdAt; + this.expiresAt = expiresAt; + this.ttl = ttl; + } + + public String getSessionId() { + return sessionId; + } + + public String getUsername() { + return username; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getExpiresAt() { + return expiresAt; + } + + public long getTtl() { + return ttl; + } + } + + @Requires(property = "spec.name", value = "SessionStoreTest") + @Controller("/sessions") + static class SessionController { + + private final SessionRepository sessionRepository; + + SessionController(SessionRepository sessionRepository) { + this.sessionRepository = sessionRepository; + } + + @ExecuteOn(TaskExecutors.IO) + @Post + HttpResponse save(@NonNull @NotNull @Valid @Body Credentials credentials) { + return sessionRepository.save(credentials.getUsername()) + .map(sessionId -> HttpResponse.created(new SessionResponse(sessionId))) + .orElseGet(() -> { + MutableHttpResponse response = HttpResponse.serverError("could not create session token"); + return response; + }); + } + + @ExecuteOn(TaskExecutors.IO) + @Get + HttpResponse get(@NonNull @Header(HttpHeaders.AUTHORIZATION) String authorization) { + return sessionRepository.findUsernameBySessionId(sessionIdOfAuthorizationHeader(authorization)) + .map(User::new) + .map(HttpResponse::ok) + .orElseGet(HttpResponse::unauthorized); + } + + @ExecuteOn(TaskExecutors.IO) + @Delete + @Status(HttpStatus.NO_CONTENT) + void delete(@NonNull @Header(HttpHeaders.AUTHORIZATION) String authorization) { + sessionRepository.findUsernameBySessionId(sessionIdOfAuthorizationHeader(authorization)) + .ifPresent(username -> sessionRepository.deleteSessionsByUsername(username)); + } + + @NonNull + private String sessionIdOfAuthorizationHeader(@NonNull String authorization) { + return authorization.startsWith(HttpHeaderValues.AUTHORIZATION_PREFIX_BEARER + " ") ? + authorization.substring((HttpHeaderValues.AUTHORIZATION_PREFIX_BEARER + " ").length()) : + authorization; + } + } + + @Serdeable + static class User { + @NonNull + @NotBlank + private final String username; + + public User(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + } + + @Requires(property = "spec.name", value = "SessionStoreTest") + @Singleton + static class SessionGenerator { + String generate() { + return UUID.randomUUID().toString(); + } + } + + @Serdeable + static class SessionResponse { + private final String sessionId; + public SessionResponse(String sessionId) { + this.sessionId = sessionId; + } + public String getSessionId() { + return sessionId; + } + } + + @Serdeable + static class Credentials { + private final String username; + private final String password; + + public Credentials(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + } + + +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BigTimeDealsTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BigTimeDealsTest.java new file mode 100644 index 0000000000..14fbe1cbb2 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BigTimeDealsTest.java @@ -0,0 +1,63 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.aws.dynamodb.utils.DynamoDbLocal; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.test.support.TestPropertyProvider; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +@Property(name = "dynamodb.table-name", value = BigTimeDealsTest.TABLE_NAME) +@Property(name = "spec.name", value = "BigTimeDealsTest") +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class BigTimeDealsTest implements TestPropertyProvider { + + public static final String TABLE_NAME = "bigtimedeals"; + + @Override + public Map getProperties() { + return DynamoDbLocal.getProperties(); + } + + @Inject + @Client("/") + HttpClient httpClient; + + @Test + void bigTimeDealsTest() { + BlockingHttpClient client = httpClient.toBlocking(); + + String brand = "Nike"; + + HttpRequest createBrandRequest = HttpRequest.POST("/brands", new CreateBrand(brand, "https://static.nike.com/logo.png" )); + + HttpResponse createBrandResponse = client.exchange(createBrandRequest); + assertEquals(HttpStatus.CREATED, createBrandResponse.getStatus()); +// +// HttpRequest createDealRequest = HttpRequest.POST("/deals", new CreateDeal("8 day cruise!", "https://www.princess.com/...", new BigDecimal("1599"), "Travel", brand)); +// assertEquals(HttpStatus.CREATED, createBrandResponse.getStatus()); + } + + @Test + void category(BrandsRepository repository) { + repository.save(new CreateBrand("Bellroy", "http://Bellroy.com")); + repository.save(new CreateBrand("Apple", "https://apple.com")); + assertEquals(Set.of("Bellroy", "Apple"), repository.listBrands()); + } + + + +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BootStrap.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BootStrap.java new file mode 100644 index 0000000000..f48fa5881a --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BootStrap.java @@ -0,0 +1,58 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.aws.dynamodb.conf.DynamoConfiguration; +import io.micronaut.aws.dynamodb.utils.TestDynamoRepository; +import io.micronaut.context.annotation.Requires; +import jakarta.inject.Singleton; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.BillingMode; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; + +import java.util.Arrays; + +@Requires(property = "spec.name", value = "BigTimeDealsTest") +@Singleton +public class BootStrap extends TestDynamoRepository { + public static final String INDEX_GSI1 = "gsi1"; + public static final String INDEX_GSI1_PK = "gsi1Pk"; + public static final String INDEX_GSI1_SK = "gsi1Sk"; + + public static final String INDEX_GSI2 = "gsi2"; + public static final String INDEX_GSI2_PK = "gsi2Pk"; + public static final String INDEX_GSI2_SK = "gsi2Sk"; + + public static final String INDEX_GSI3 = "gsi3"; + public static final String INDEX_GSI3_PK = "gsi3Pk"; + public static final String INDEX_GSI3_SK = "gsi3Sk"; + + public BootStrap(DynamoDbClient dynamoDbClient, + DynamoConfiguration dynamoConfiguration) { + super(dynamoDbClient, dynamoConfiguration); + } + + @Override + public CreateTableRequest createTableRequest(String tableName) { + return CreateTableRequest.builder() + .attributeDefinitions( + attributeDefinition("pk", ScalarAttributeType.S), + attributeDefinition("sk", ScalarAttributeType.S), + attributeDefinition(INDEX_GSI1_PK, ScalarAttributeType.S), + attributeDefinition(INDEX_GSI1_SK, ScalarAttributeType.S), + attributeDefinition(INDEX_GSI2_PK, ScalarAttributeType.S), + attributeDefinition(INDEX_GSI2_SK, ScalarAttributeType.S), + attributeDefinition(INDEX_GSI3_PK, ScalarAttributeType.S), + attributeDefinition(INDEX_GSI3_SK, ScalarAttributeType.S) + ) + .keySchema(Arrays.asList(keySchemaElement("pk", KeyType.HASH), keySchemaElement("sk", KeyType.RANGE))) + .globalSecondaryIndexes( + gsi(INDEX_GSI1, INDEX_GSI1_PK, INDEX_GSI1_SK), + gsi(INDEX_GSI2, INDEX_GSI2_PK, INDEX_GSI2_SK), + gsi(INDEX_GSI3, INDEX_GSI3_PK, INDEX_GSI3_SK) + ) + .billingMode(BillingMode.PAY_PER_REQUEST) + .tableName(tableName) + .build(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BrandsController.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BrandsController.java new file mode 100644 index 0000000000..f835bf7c85 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BrandsController.java @@ -0,0 +1,25 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Status; + +@Requires(property = "spec.name", value = "BigTimeDealsTest") +@Controller("/brands") +public class BrandsController { + + private final BrandsRepository brandsRepository; + + public BrandsController(BrandsRepository brandsRepository) { + this.brandsRepository = brandsRepository; + } + + @Status(HttpStatus.CREATED) + @Post + void save(@Body CreateBrand brand) { + brandsRepository.save(brand); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BrandsRepository.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BrandsRepository.java new file mode 100644 index 0000000000..a25b4a84aa --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/BrandsRepository.java @@ -0,0 +1,55 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.DynamoRepository; +import io.micronaut.aws.dynamodb.bigtimedeals.rows.BrandContainer; +import io.micronaut.aws.dynamodb.bigtimedeals.rows.BrandRow; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import jakarta.inject.Singleton; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Requires(property = "spec.name", value = "BigTimeDealsTest") +@Singleton +public class BrandsRepository { + + private final DynamoRepository dynamoRepository; + + public BrandsRepository(DynamoRepository dynamoRepository) { + this.dynamoRepository = dynamoRepository; + } + + public void save(@NonNull @NotNull @Valid CreateBrand brand) { + + BrandRow brandRow = new BrandRow(BrandRow.key(brand.getName()), brand.getName(), brand.getLogoUrl()); + + Map brandRowMap = dynamoRepository.getDynamoDbConversionService().convert(brandRow); + dynamoRepository.transactWriteItems( + dynamoRepository.putTransactWriteItem(builder -> + builder.item(brandRowMap) + .conditionExpression("attribute_not_exists(pk)") + ) + , + dynamoRepository.updateTransactWriteItem(builder -> + builder.key(dynamoRepository.mapForKey(BrandContainer.KEY)) + .updateExpression("ADD #brands :brand") + .expressionAttributeNames(Collections.singletonMap("#brands", "brands")) + .expressionAttributeValues(Collections.singletonMap(":brand", AttributeValue.builder().ss(brand.getName()).build())) + ) + ); + } + + @NonNull + public Set listBrands() { + return dynamoRepository.getItem(BrandContainer.KEY, BrandContainer.class) + .map(BrandContainer::getBrands) + .orElseGet(Collections::emptySet); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/CreateBrand.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/CreateBrand.java new file mode 100644 index 0000000000..d0752ef567 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/CreateBrand.java @@ -0,0 +1,23 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public class CreateBrand { + private final String name; + private final String logoUrl; + + + public CreateBrand(String name, String logoUrl) { + this.name = name; + this.logoUrl = logoUrl; + } + + public String getName() { + return name; + } + + public String getLogoUrl() { + return logoUrl; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/CreateDeal.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/CreateDeal.java new file mode 100644 index 0000000000..1d88cf1c97 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/CreateDeal.java @@ -0,0 +1,43 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.serde.annotation.Serdeable; + +import java.math.BigDecimal; + +@Serdeable +public class CreateDeal { + + private final String title; + private final String link; + private final BigDecimal price; + private final String category; + private final String brand; + + public CreateDeal(String title, String link, BigDecimal price, String category, String brand) { + this.title = title; + this.link = link; + this.price = price; + this.category = category; + this.brand = brand; + } + + public String getTitle() { + return title; + } + + public String getLink() { + return link; + } + + public BigDecimal getPrice() { + return price; + } + + public String getCategory() { + return category; + } + + public String getBrand() { + return brand; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/DealsController.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/DealsController.java new file mode 100644 index 0000000000..de2d0c099e --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/DealsController.java @@ -0,0 +1,24 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Status; + +@Requires(property = "spec.name", value = "BigTimeDealsTest") +@Controller("/deals") +public class DealsController { + private final DealsRepository dealsRepository; + + public DealsController(DealsRepository dealsRepository) { + this.dealsRepository = dealsRepository; + } + + @Post + @Status(HttpStatus.CREATED) + void save(@Body CreateDeal deal) { + + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/DealsRepository.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/DealsRepository.java new file mode 100644 index 0000000000..498f698698 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/DealsRepository.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.aws.dynamodb.DynamoRepository; +import io.micronaut.aws.dynamodb.bigtimedeals.rows.DealRow; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import jakarta.inject.Singleton; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; + +@Requires(property = "spec.name", value = "BigTimeDealsTest") +@Singleton +public class DealsRepository { + private final IdGenerator idGenerator; + private final DynamoRepository dynamoRepository; + + public DealsRepository(IdGenerator idGenerator, + DynamoRepository dynamoRepository) { + this.idGenerator = idGenerator; + this.dynamoRepository = dynamoRepository; + } + + void save(@NonNull @NotNull @Valid CreateDeal deal) { + String dealId = idGenerator.generate(); + LocalDateTime createdAt = LocalDateTime.now(); + DealRow dealRow = new DealRow(dealId, + deal.getTitle(), + deal.getLink(), + deal.getPrice(), + deal.getCategory(), + deal.getBrand(), + createdAt); + dynamoRepository.putItem(dealRow, builder -> builder.conditionExpression("attribute_not_exists(pk)")); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/IdGenerator.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/IdGenerator.java new file mode 100644 index 0000000000..a8823de02f --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/IdGenerator.java @@ -0,0 +1,9 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import io.micronaut.core.annotation.NonNull; + +public interface IdGenerator { + + @NonNull + String generate(); +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/KsuidGenerator.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/KsuidGenerator.java new file mode 100644 index 0000000000..e2866f1360 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/KsuidGenerator.java @@ -0,0 +1,12 @@ +package io.micronaut.aws.dynamodb.bigtimedeals; + +import com.github.ksuid.Ksuid; +import jakarta.inject.Singleton; + +@Singleton +public class KsuidGenerator implements IdGenerator { + @Override + public String generate() { + return Ksuid.newKsuid().toString(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandContainer.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandContainer.java new file mode 100644 index 0000000000..ecc01f1c6d --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandContainer.java @@ -0,0 +1,31 @@ +package io.micronaut.aws.dynamodb.bigtimedeals.rows; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.SingleTableRow; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; + +import java.util.Collections; +import java.util.Set; + +@Introspected +public class BrandContainer extends SingleTableRow { + public static final CompositeKey KEY = CompositeKey.of("BRANDS", "BRANDS"); + private final Set brands; + + @Creator + public BrandContainer(String pk, String sk, @Nullable String className, Set brands) { + super(pk, sk, className); + this.brands = brands; + } + + public BrandContainer() { + this(KEY.getPartionKey(), KEY.getSortKey(), BrandContainer.class.getName(), Collections.emptySet()); + } + + public Set getBrands() { + return brands; + } + +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandContainerTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandContainerTest.java new file mode 100644 index 0000000000..e424154fc9 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandContainerTest.java @@ -0,0 +1,41 @@ +package io.micronaut.aws.dynamodb.bigtimedeals.rows; + +import io.micronaut.aws.dynamodb.DynamoDbConversionService; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class BrandContainerTest { + + @Test + void conversionServiceHandlesSet(DynamoDbConversionService dynamoDbConversionService) { + BrandContainer brandContainer = new BrandContainer(BrandContainer.KEY.getPartionKey(), + BrandContainer.KEY.getSortKey(), + BrandContainer.class.getName(), + Collections.singleton("Nike")); + Map m = dynamoDbConversionService.convert(brandContainer); + assertNotNull(m); + assertTrue(m.containsKey("pk")); + assertEquals("BRANDS", m.get("pk").s()); + assertTrue(m.containsKey("sk")); + assertEquals("BRANDS", m.get("pk").s()); + assertTrue(m.containsKey("className")); + assertEquals("io.micronaut.aws.dynamodb.bigtimedeals.rows.BrandContainer", m.get("className").s()); + assertTrue(m.containsKey("brands")); + assertEquals(Collections.singletonList("Nike"), m.get("brands").ss()); + + BrandContainer result = dynamoDbConversionService.convert(m, BrandContainer.class); + assertEquals("BRANDS", result.getPk()); + assertEquals("BRANDS", result.getSk()); + assertEquals("io.micronaut.aws.dynamodb.bigtimedeals.rows.BrandContainer", result.getClassName()); + assertEquals(Collections.singleton("Nike"), result.getBrands()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandRow.java new file mode 100644 index 0000000000..d1f7d2bf96 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/BrandRow.java @@ -0,0 +1,58 @@ +package io.micronaut.aws.dynamodb.bigtimedeals.rows; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.SingleTableRow; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; + +import java.util.Locale; + +@Introspected +public class BrandRow extends SingleTableRow { + private final String brandName; + private final String logoUrl; + private final Integer likeCount; + private final Integer watchCount; + + public BrandRow(String pk, + String sk, + String className, + String brandName, + String logoUrl, + Integer likeCount, + Integer watchCount) { + super(pk, sk, className); + this.brandName = brandName; + this.logoUrl = logoUrl; + this.likeCount = likeCount; + this.watchCount = watchCount; + } + + public BrandRow(CompositeKey key, + String brandName, + String logoUrl) { + this(key.getPartionKey(), key.getSortKey(), BrandRow.class.getName(), brandName, logoUrl, 0, 0); + } + + public String getBrandName() { + return brandName; + } + + public String getLogoUrl() { + return logoUrl; + } + + public Integer getLikeCount() { + return likeCount; + } + + public Integer getWatchCount() { + return watchCount; + } + + @NonNull + public static CompositeKey key(@NonNull String brandName) { + final String value = "BRAND#" + brandName.toUpperCase(Locale.ROOT); + return CompositeKey.of(value, value); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/CategoryRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/CategoryRow.java new file mode 100644 index 0000000000..421bb8e517 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/CategoryRow.java @@ -0,0 +1,60 @@ +package io.micronaut.aws.dynamodb.bigtimedeals.rows; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.SingleTableRow; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; + +import java.util.Locale; + +@Introspected +public class CategoryRow extends SingleTableRow { + private final String name; + private final Integer likeCount; + private final Integer watchCount; + + @Creator + public CategoryRow(String pk, + String sk, + String className, + String name, + Integer likeCount, + Integer watchCount) { + super(pk, sk, className); + this.name = name; + this.likeCount = likeCount; + this.watchCount = watchCount; + } + + public CategoryRow(CompositeKey key, + String name, + Integer likeCount, + Integer watchCount) { + this(key.getPartionKey(), key.getSortKey(), CategoryRow.class.getName(), name, likeCount, watchCount); + } + + public CategoryRow(String name, + Integer likeCount, + Integer watchCount) { + this(key(name), name, likeCount, watchCount); + } + + @NonNull + public static CompositeKey key(@NonNull String name) { + final String value = "CATEGORY#" + name.toUpperCase(Locale.ROOT); + return CompositeKey.of(value, value); + } + + public String getName() { + return name; + } + + public Integer getLikeCount() { + return likeCount; + } + + public Integer getWatchCount() { + return watchCount; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/DealRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/DealRow.java new file mode 100644 index 0000000000..c61bad0a0c --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/bigtimedeals/rows/DealRow.java @@ -0,0 +1,160 @@ +package io.micronaut.aws.dynamodb.bigtimedeals.rows; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.SingleTableRowWithThreeGlobalSecondaryIndex; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Optional; + +@Introspected +public class DealRow extends SingleTableRowWithThreeGlobalSecondaryIndex { + private final String dealId; + private final String title; + private final String link; + private final BigDecimal price; + private final String category; + private final String brand; + private final LocalDateTime createdAt; + + @Creator + public DealRow(String pk, + String sk, + String className, + String gsi1Pk, + String gsi1Sk, + String gsi2Pk, + String gsi2Sk, + String gsi3Pk, + String gsi3Sk, + String dealId, + String title, + String link, + BigDecimal price, + String category, + String brand, + LocalDateTime createdAt) { + super(pk, sk, className, gsi1Pk, gsi1Sk, gsi2Pk, gsi2Sk, gsi3Pk, gsi3Sk); + this.dealId = dealId; + this.title = title; + this.link = link; + this.price = price; + this.category = category; + this.brand = brand; + this.createdAt = createdAt; + } + + public DealRow(CompositeKey key, + @Nullable CompositeKey gsi1, + @Nullable CompositeKey gsi2, + @Nullable CompositeKey gsi3, + String dealId, + String title, + String link, + BigDecimal price, + String category, + String brand, + LocalDateTime createdAt) { + this(key.getPartionKey(), + key.getSortKey(), + DealRow.class.getName(), + gsi1 == null ? null : gsi1.getPartionKey(), + gsi1 == null ? null : gsi1.getSortKey(), + gsi2 == null ? null : gsi2.getPartionKey(), + gsi2 == null ? null : gsi2.getSortKey(), + gsi3 == null ? null : gsi3.getPartionKey(), + gsi3 == null ? null : gsi3.getSortKey(), + dealId, + title, + link, + price, + category, + brand, + createdAt); + } + + public DealRow(String dealId, + String title, + String link, + BigDecimal price, + String category, + String brand, + LocalDateTime createdAt) { + this(DealRow.key(dealId), + DealRow.gsi1(dealId, createdAt), + DealRow.gsi2(dealId, createdAt, brand).orElse(null), + DealRow.gsi3(dealId, createdAt, category).orElse(null), + dealId, + title, + link, + price, + category, + brand, + createdAt); + } + + public String getDealId() { + return dealId; + } + + public String getTitle() { + return title; + } + + public String getLink() { + return link; + } + + public BigDecimal getPrice() { + return price; + } + + public String getCategory() { + return category; + } + + public String getBrand() { + return brand; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + @NonNull + public static CompositeKey key(@NonNull String dealId) { + final String value = "DEAL#"+ dealId; + return CompositeKey.of(value, value); + } + + @NonNull + public static CompositeKey gsi1(@NonNull String dealId, @NonNull LocalDateTime createdAt) { + return CompositeKey.of("DEALS#" + truncateTimestamp(createdAt).toString(), "DEAL#" + dealId); + } + + @NonNull + public static Optional gsi2(@NonNull String dealId, @NonNull LocalDateTime createdAt, @Nullable String brand) { + if (brand == null) { + return Optional.empty(); + } + return Optional.of(CompositeKey.of("BRAND#" + brand.toUpperCase() + "#" + truncateTimestamp(createdAt).toString(), "DEAL#" + dealId)); + } + + @NonNull + public static Optional gsi3(@NonNull String dealId, @NonNull LocalDateTime createdAt, @Nullable String category) { + if (category == null) { + return Optional.empty(); + } + return Optional.of(CompositeKey.of("CATEGORY#" + category.toUpperCase() + "#" + truncateTimestamp(createdAt).toString(), "DEAL#" + dealId)); + } + + @NonNull + public static LocalDateTime truncateTimestamp(@NonNull LocalDateTime timestamp) { + return LocalDateTime.of(timestamp.toLocalDate(), LocalTime.of(0, 0, 0, 0)); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicBooleanToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicBooleanToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..8de7dbf7ef --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicBooleanToAttributeValueTypeConverterTest.java @@ -0,0 +1,49 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class AtomicBooleanToAttributeValueTypeConverterTest { + @Test + void boolToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + AtomicBoolean bool = null; + assertFalse(conversionService.convert(bool, Argument.of(AttributeValue.class)).isPresent()); + + bool = new AtomicBoolean(false); + assertTrue(conversionService.convert(bool, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(bool, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertFalse(attributeValue.bool()); + assertEquals(AttributeValue.Type.BOOL, attributeValue.type()); + + Optional booleanOptional = conversionService.convert(attributeValue, Argument.of(AtomicBoolean.class)); + assertTrue(booleanOptional.isPresent()); + assertFalse(booleanOptional.get().get()); + + bool = new AtomicBoolean(true); + assertTrue(conversionService.convert(bool, Argument.of(AttributeValue.class)).isPresent()); + attributeValueOptional = conversionService.convert(bool, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.BOOL, attributeValue.type()); + assertTrue(attributeValue.bool()); + + booleanOptional = conversionService.convert(attributeValue, Argument.of(AtomicBoolean.class)); + assertTrue(booleanOptional.isPresent()); + assertTrue(booleanOptional.get().get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicIntegerToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicIntegerToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..d1eda2632c --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicIntegerToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class AtomicIntegerToAttributeValueTypeConverterTest { + @Test + void atomicIntegerToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + AtomicInteger value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + + value = new AtomicInteger(3); + assertTrue(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + assertEquals("3", attributeValue.n()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(AtomicInteger.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(3, valueOptional.get().get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicLongToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicLongToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..2e6255b588 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/AtomicLongToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class AtomicLongToAttributeValueTypeConverterTest { + @Test + void boolToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + AtomicLong value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + + value = new AtomicLong(3L); + assertTrue(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + assertEquals("3", attributeValue.n()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(AtomicLong.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(3L, valueOptional.get().get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BigDecimalToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BigDecimalToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..bdaf060268 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BigDecimalToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.math.BigDecimal; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class BigDecimalToAttributeValueTypeConverterTest { + @Test + void bigDecimalToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + BigDecimal val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = new BigDecimal("10.5"); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional numberOptional = conversionService.convert(attributeValue, Argument.of(BigDecimal.class)); + assertTrue(numberOptional.isPresent()); + assertEquals(val, numberOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BigIntegerToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BigIntegerToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..3f3c3095e6 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BigIntegerToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.math.BigInteger; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class BigIntegerToAttributeValueTypeConverterTest { + @Test + void bigIntegerToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + BigInteger val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = new BigInteger("10"); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional numberOptional = conversionService.convert(attributeValue, Argument.of(BigInteger.class)); + assertTrue(numberOptional.isPresent()); + assertEquals(val, numberOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BooleanToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BooleanToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..ae5b9fa2cf --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/BooleanToAttributeValueTypeConverterTest.java @@ -0,0 +1,48 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class BooleanToAttributeValueTypeConverterTest { + @Test + void boolToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Boolean bool = null; + assertFalse(conversionService.convert(bool, Argument.of(AttributeValue.class)).isPresent()); + + bool = Boolean.FALSE; + assertTrue(conversionService.convert(bool, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(bool, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertFalse(attributeValue.bool()); + assertEquals(AttributeValue.Type.BOOL, attributeValue.type()); + + Optional booleanOptional = conversionService.convert(attributeValue, Argument.of(Boolean.class)); + assertTrue(booleanOptional.isPresent()); + assertFalse(booleanOptional.get()); + + bool = Boolean.TRUE; + assertTrue(conversionService.convert(bool, Argument.of(AttributeValue.class)).isPresent()); + attributeValueOptional = conversionService.convert(bool, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.BOOL, attributeValue.type()); + assertTrue(attributeValue.bool()); + + booleanOptional = conversionService.convert(attributeValue, Argument.of(Boolean.class)); + assertTrue(booleanOptional.isPresent()); + assertTrue(booleanOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/CharacterToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/CharacterToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..e423f5d56c --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/CharacterToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ + +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class CharacterToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Character cs = null; + assertFalse(conversionService.convert(cs, Argument.of(AttributeValue.class)).isPresent()); + cs = 'f'; + Optional attributeValueOptional = conversionService.convert(cs, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("f" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Character.class)); + assertTrue(valueOptional.isPresent()); + assertEquals('f', valueOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/DoubleToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/DoubleToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..b00dc64e82 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/DoubleToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class DoubleToAttributeValueTypeConverterTest { + @Test + void doubleToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Double val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = Double.valueOf("10"); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Double.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(10, valueOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/DurationToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/DurationToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..74d2cb1a08 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/DurationToAttributeValueTypeConverterTest.java @@ -0,0 +1,64 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class DurationToAttributeValueTypeConverterTest { + @Test + void doubleToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Duration val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + for (TestRun testRun : Arrays.asList(new TestRun(Duration.ofDays(1), "86400"), + new TestRun(Duration.ofSeconds(9), "9"), + new TestRun(Duration.ofSeconds(-9), "-9"), + new TestRun(Duration.ofNanos(1_234_567_890), "1.234567890"), + new TestRun(Duration.ofMillis(1), "0.001000000"), + new TestRun(Duration.ofNanos(1), "0.000000001") + )) { + val = testRun.getDuration(); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(testRun.getResult(), attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Duration.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(val, valueOptional.get()); + } + } + + static class TestRun { + private final Duration duration; + private final String result; + + public TestRun(Duration duration, String result) { + this.duration = duration; + this.result = result; + } + + public Duration getDuration() { + return duration; + } + + public String getResult() { + return result; + } + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/EnumToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/EnumToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..61a060bbd5 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/EnumToAttributeValueTypeConverterTest.java @@ -0,0 +1,34 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class EnumToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Status cs = null; + assertFalse(conversionService.convert(cs, Argument.of(AttributeValue.class)).isPresent()); + cs = Status.COMPLETED; + Optional attributeValueOptional = conversionService.convert(cs, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("COMPLETED" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Status.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(Status.COMPLETED, valueOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/FloatToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/FloatToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..dd196f9d55 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/FloatToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class FloatToAttributeValueTypeConverterTest { + @Test + void floatToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Float val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = 10.5f; + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Float.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(10.5f, valueOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/InstantToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/InstantToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..3aa5388c15 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/InstantToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class InstantToAttributeValueTypeConverterTest { + @Test + void instantToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Instant value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = Instant.EPOCH.minusSeconds(1); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + assertEquals("1969-12-31T23:59:59Z" ,attributeValue.s()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Instant.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(value, valueOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/IntegerToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/IntegerToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..eae24ed9de --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/IntegerToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class IntegerToAttributeValueTypeConverterTest { + @Test + void integerToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Integer val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = Integer.valueOf("10"); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional integerOptional = conversionService.convert(attributeValue, Argument.of(Integer.class)); + assertTrue(integerOptional.isPresent()); + assertEquals(10, integerOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalDateTimeToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalDateTimeToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..4b43943fdf --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalDateTimeToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class LocalDateTimeToAttributeValueTypeConverterTest { + @Test + void localDateTimeToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + LocalDateTime ld = null; + assertFalse(conversionService.convert(ld, Argument.of(AttributeValue.class)).isPresent()); + ld = LocalDateTime.of(2023, Month.MARCH, 23, 18, 30, 15); + Optional attributeValueOptional = conversionService.convert(ld, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("2023-03-23T18:30:15" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional localDateOptional = conversionService.convert(attributeValue, Argument.of(LocalDateTime.class)); + assertTrue(localDateOptional.isPresent()); + assertEquals(ld, localDateOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalDateToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalDateToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..6d3705a150 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalDateToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalDate; +import java.time.Month; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class LocalDateToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + LocalDate ld = null; + assertFalse(conversionService.convert(ld, Argument.of(AttributeValue.class)).isPresent()); + ld = LocalDate.of(2023, Month.MARCH, 23); + Optional attributeValueOptional = conversionService.convert(ld, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("2023-03-23" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional localDateOptional = conversionService.convert(attributeValue, Argument.of(LocalDate.class)); + assertTrue(localDateOptional.isPresent()); + assertEquals(ld, localDateOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalTimeToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalTimeToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..5031d5147c --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocalTimeToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.LocalTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class LocalTimeToAttributeValueTypeConverterTest { + @Test + void localDateTimeToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + LocalTime ld = null; + assertFalse(conversionService.convert(ld, Argument.of(AttributeValue.class)).isPresent()); + ld = LocalTime.parse("10:15:30"); + Optional attributeValueOptional = conversionService.convert(ld, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("10:15:30" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional localTimeOptional = conversionService.convert(attributeValue, Argument.of(LocalTime.class)); + assertTrue(localTimeOptional.isPresent()); + assertEquals(ld, localTimeOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocaleToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocaleToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..3561bbeaca --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LocaleToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Locale; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class LocaleToAttributeValueTypeConverterTest { + @Test + void localeToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Locale value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = new Locale("es", "MX"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("es-MX" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional localeOptional = conversionService.convert(attributeValue, Argument.of(Locale.class)); + assertTrue(localeOptional.isPresent()); + assertEquals(value, localeOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LongToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LongToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..53d696caf8 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/LongToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class LongToAttributeValueTypeConverterTest { + @Test + void longToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Long val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = Long.valueOf("10"); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional numberOptional = conversionService.convert(attributeValue, Argument.of(Long.class)); + assertTrue(numberOptional.isPresent()); + assertEquals(10, numberOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/MonthDayToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/MonthDayToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..6863fdac7b --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/MonthDayToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.MonthDay; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class MonthDayToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + MonthDay value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = MonthDay.of(5, 21); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("--05-21" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional monthDayOptional = conversionService.convert(attributeValue, Argument.of(MonthDay.class)); + assertTrue(monthDayOptional.isPresent()); + assertEquals(value, monthDayOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OffsetDateTimeToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OffsetDateTimeToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..686fbc79d2 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OffsetDateTimeToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class OffsetDateTimeToAttributeValueTypeConverterTest { + @Test + void offsetDateTimeToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + OffsetDateTime value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = Instant.EPOCH.atOffset(ZoneOffset.UTC).plusSeconds(1); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + assertEquals("1970-01-01T00:00:01Z" ,attributeValue.s()); + + Optional zoneIdOptional = conversionService.convert(attributeValue, Argument.of(OffsetDateTime.class)); + assertTrue(zoneIdOptional.isPresent()); + assertEquals(value, zoneIdOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalDoubleToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalDoubleToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..61809999af --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalDoubleToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalDouble; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class OptionalDoubleToAttributeValueTypeConverterTest { + @Test + void optionalDoubleToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + OptionalDouble val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = OptionalDouble.of(10); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional numberOptional = conversionService.convert(attributeValue, Argument.of(OptionalDouble.class)); + assertTrue(numberOptional.isPresent()); + assertEquals(10D, numberOptional.get().getAsDouble()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalIntToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalIntToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..bd2ec0969e --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalIntToAttributeValueTypeConverterTest.java @@ -0,0 +1,38 @@ + +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class OptionalIntToAttributeValueTypeConverterTest { + @Test + void optionalDoubleToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + OptionalInt val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = OptionalInt.of(10); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional numberOptional = conversionService.convert(attributeValue, Argument.of(OptionalInt.class)); + assertTrue(numberOptional.isPresent()); + assertEquals(10, numberOptional.get().getAsInt()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalLongToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalLongToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..69ef724c71 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/OptionalLongToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class OptionalLongToAttributeValueTypeConverterTest { + @Test + void optionalLongToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + OptionalLong val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = OptionalLong.of(10L); + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional numberOptional = conversionService.convert(attributeValue, Argument.of(OptionalLong.class)); + assertTrue(numberOptional.isPresent()); + assertEquals(10L, numberOptional.get().getAsLong()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/PeriodToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/PeriodToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..6e99e7c782 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/PeriodToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Period; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class PeriodToAttributeValueTypeConverterTest { + @Test + void periodToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Period value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = Period.ofYears(2); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("P2Y" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional monthDayOptional = conversionService.convert(attributeValue, Argument.of(Period.class)); + assertTrue(monthDayOptional.isPresent()); + assertEquals(value, monthDayOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ShortToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ShortToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..7d05e25746 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ShortToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class ShortToAttributeValueTypeConverterTest { + @Test + void shortToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + Short val = null; + assertFalse(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + + val = 2; + assertTrue(conversionService.convert(val, Argument.of(AttributeValue.class)).isPresent()); + Optional attributeValueOptional = conversionService.convert(val, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertNotNull(attributeValue.n()); + assertEquals(AttributeValue.Type.N, attributeValue.type()); + + Optional valueOptional = conversionService.convert(attributeValue, Argument.of(Short.class)); + assertTrue(valueOptional.isPresent()); + assertEquals(val, valueOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/Status.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/Status.java new file mode 100644 index 0000000000..e954e1fcce --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/Status.java @@ -0,0 +1,6 @@ +package io.micronaut.aws.dynamodb.converters; + +public enum Status { + DRAFT, + COMPLETED +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringBufferToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringBufferToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..aafb2579fd --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringBufferToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class StringBufferToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + StringBuffer value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = new StringBuffer(); + value.append("foo"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("foo" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional stringBuilderOptional = conversionService.convert(attributeValue, Argument.of(StringBuffer.class)); + assertTrue(stringBuilderOptional.isPresent()); + assertEquals(value.toString(), stringBuilderOptional.get().toString()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringBuilderToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringBuilderToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..ef7b83e51d --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringBuilderToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class StringBuilderToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + StringBuilder value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = new StringBuilder(); + value.append("foo"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("foo" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional stringBuilderOptional = conversionService.convert(attributeValue, Argument.of(StringBuilder.class)); + assertTrue(stringBuilderOptional.isPresent()); + assertEquals(value.toString(), stringBuilderOptional.get().toString()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..d5b964b738 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/StringToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +@MicronautTest(startApplication = false) +class StringToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + String cs = null; + assertFalse(conversionService.convert(cs, Argument.of(AttributeValue.class)).isPresent()); + cs = ""; + assertFalse(conversionService.convert(cs, Argument.of(AttributeValue.class)).isPresent()); + cs = "foo"; + Optional attributeValueOptional = conversionService.convert(cs, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("foo" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional charSequenceOptional = conversionService.convert(attributeValue, Argument.of(String.class)); + assertTrue(charSequenceOptional.isPresent()); + assertEquals("foo", charSequenceOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/URLToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/URLToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..62fe5caff8 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/URLToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class URLToAttributeValueTypeConverterTest { + @Test + void localDateTimeToAttributeValueTypeConverterTest(ConversionService conversionService) throws MalformedURLException { + assertNotNull(conversionService); + URL value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = new URL("https://micronaut.io"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("https://micronaut.io" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional urlOptional = conversionService.convert(attributeValue, Argument.of(URL.class)); + assertTrue(urlOptional.isPresent()); + assertEquals(value, urlOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/UUIDToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/UUIDToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..335d2a379f --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/UUIDToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +@MicronautTest(startApplication = false) +class UUIDToAttributeValueTypeConverterTest { + @Test + void charSequenceToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + UUID uuid = null; + assertFalse(conversionService.convert(uuid, Argument.of(AttributeValue.class)).isPresent()); + uuid = UUID.randomUUID(); + Optional attributeValueOptional = conversionService.convert(uuid, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(uuid.toString() ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional uuidOptional = conversionService.convert(attributeValue, Argument.of(UUID.class)); + assertTrue(uuidOptional.isPresent()); + assertEquals(uuid, uuidOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/UriToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/UriToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..d215b7ab0a --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/UriToAttributeValueTypeConverterTest.java @@ -0,0 +1,36 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.net.MalformedURLException; +import java.util.Optional; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class UriToAttributeValueTypeConverterTest { + @Test + void uriToAttributeValueTypeConverterTest(ConversionService conversionService) throws MalformedURLException { + assertNotNull(conversionService); + URI value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = URI.create("https://micronaut.io"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("https://micronaut.io" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional urlOptional = conversionService.convert(attributeValue, Argument.of(URI.class)); + assertTrue(urlOptional.isPresent()); + assertEquals(value, urlOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZoneIdToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZoneIdToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..ba728a4765 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZoneIdToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.ZoneId; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class ZoneIdToAttributeValueTypeConverterTest { + @Test + void zoneIdToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + ZoneId value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = ZoneId.of("GMT"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + assertEquals("GMT" ,attributeValue.s()); + + Optional zoneIdOptional = conversionService.convert(attributeValue, Argument.of(ZoneId.class)); + assertTrue(zoneIdOptional.isPresent()); + assertEquals(ZoneId.of("GMT"), zoneIdOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZoneOffsetToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZoneOffsetToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..ab8d6661cd --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZoneOffsetToAttributeValueTypeConverterTest.java @@ -0,0 +1,35 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.ZoneOffset; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class ZoneOffsetToAttributeValueTypeConverterTest { + @Test + void zoneIdToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + ZoneOffset value = null; + assertFalse(conversionService.convert(value, Argument.of(AttributeValue.class)).isPresent()); + value = ZoneOffset.of("+1"); + Optional attributeValueOptional = conversionService.convert(value, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + assertEquals("+01:00" ,attributeValue.s()); + + Optional zoneIdOptional = conversionService.convert(attributeValue, Argument.of(ZoneOffset.class)); + assertTrue(zoneIdOptional.isPresent()); + assertEquals(ZoneOffset.of("+01:00"), zoneIdOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZonedDateTimeToAttributeValueTypeConverterTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZonedDateTimeToAttributeValueTypeConverterTest.java new file mode 100644 index 0000000000..c8df03f5cf --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/converters/ZonedDateTimeToAttributeValueTypeConverterTest.java @@ -0,0 +1,37 @@ +package io.micronaut.aws.dynamodb.converters; + +import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.type.Argument; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class ZonedDateTimeToAttributeValueTypeConverterTest { + @Test + void localDateTimeToAttributeValueTypeConverterTest(ConversionService conversionService) { + assertNotNull(conversionService); + ZonedDateTime ld = null; + assertFalse(conversionService.convert(ld, Argument.of(AttributeValue.class)).isPresent()); + ld = Instant.EPOCH.atZone(ZoneId.of("Europe/Paris")); + Optional attributeValueOptional = conversionService.convert(ld, Argument.of(AttributeValue.class)); + assertTrue(attributeValueOptional.isPresent()); + AttributeValue attributeValue = attributeValueOptional.get(); + assertEquals("1970-01-01T01:00+01:00[Europe/Paris]" ,attributeValue.s()); + assertEquals(AttributeValue.Type.S, attributeValue.type()); + + Optional localDateOptional = conversionService.convert(attributeValue, Argument.of(ZonedDateTime.class)); + assertTrue(localDateOptional.isPresent()); + assertEquals(ld, localDateOptional.get()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Address.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Address.java new file mode 100644 index 0000000000..545b3dbb46 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Address.java @@ -0,0 +1,28 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public class Address { + private final String streetAddress; + private final String postalCode; + private final String country; + + public Address(String streetAddress, String postalCode, String country) { + this.streetAddress = streetAddress; + this.postalCode = postalCode; + this.country = country; + } + + public String getStreetAddress() { + return streetAddress; + } + + public String getPostalCode() { + return postalCode; + } + + public String getCountry() { + return country; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/BootStrap.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/BootStrap.java new file mode 100644 index 0000000000..9d0b13eebc --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/BootStrap.java @@ -0,0 +1,42 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.aws.dynamodb.conf.DynamoConfiguration; +import io.micronaut.aws.dynamodb.utils.TestDynamoRepository; +import io.micronaut.context.annotation.Requires; +import jakarta.inject.Singleton; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.BillingMode; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; + +import java.util.Arrays; + +@Requires(property = "spec.name", value = "EcommerceTest") +@Singleton +public class BootStrap extends TestDynamoRepository { + public static final String INDEX_GSI1 = "gsi1"; + public static final String INDEX_GSI1_PK = "gsi1Pk"; + public static final String INDEX_GSI1_SK = "gsi1Sk"; + public BootStrap(DynamoDbClient dynamoDbClient, + DynamoConfiguration dynamoConfiguration) { + super(dynamoDbClient, dynamoConfiguration); + } + + @Override + public CreateTableRequest createTableRequest(String tableName) { + return CreateTableRequest.builder() + .attributeDefinitions( + attributeDefinition("pk", ScalarAttributeType.S), + attributeDefinition("sk", ScalarAttributeType.S), + attributeDefinition(INDEX_GSI1_PK, ScalarAttributeType.S), + attributeDefinition(INDEX_GSI1_SK, ScalarAttributeType.S) + ) + .keySchema(Arrays.asList(keySchemaElement("pk", KeyType.HASH), keySchemaElement("sk", KeyType.RANGE))) + .globalSecondaryIndexes( + gsi(INDEX_GSI1, INDEX_GSI1_PK, INDEX_GSI1_SK)) + .billingMode(BillingMode.PAY_PER_REQUEST) + .tableName(tableName) + .build(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CreateCustomer.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CreateCustomer.java new file mode 100644 index 0000000000..2704ae4eaf --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CreateCustomer.java @@ -0,0 +1,41 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +@Serdeable +public class CreateCustomer { + @NonNull + @NotBlank + private final String username; + + @NonNull + @NotBlank + @Email + private final String email; + + @NonNull + @NotBlank + private final String name; + + + CreateCustomer(String username, String email, String name) { + this.username = username; + this.email = email; + this.name = name; + } + + public String getUsername() { + return username; + } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CreateOrder.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CreateOrder.java new file mode 100644 index 0000000000..a381fae713 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CreateOrder.java @@ -0,0 +1,38 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.util.List; + +@Serdeable +public class CreateOrder { + @NotNull + @NonNull + @Valid + private final Address address; + + @Size(min = 1) + @NotNull + @NonNull + private final List<@Valid Item> items; + + public CreateOrder(@NonNull Address address, + @NonNull List<@Valid Item> items) { + this.address = address; + this.items = items; + } + + @NonNull + public Address getAddress() { + return address; + } + + @NonNull + public List getItems() { + return items; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Customer.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Customer.java new file mode 100644 index 0000000000..bb88f62619 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Customer.java @@ -0,0 +1,58 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +import java.util.List; + +@Serdeable +public class Customer { + @NonNull + @NotBlank + private final String username; + + @NonNull + @NotBlank + @Email + private final String email; + + @NonNull + @NotBlank + private final String name; + + @Nullable + private List
addresses; + + public Customer(@NonNull String username, + @NonNull String email, + @NonNull String name, + @Nullable List
addresses) { + this.username = username; + this.email = email; + this.name = name; + this.addresses = addresses; + } + + @NonNull + public String getUsername() { + return username; + } + + @NonNull + public String getEmail() { + return email; + } + + @NonNull + public String getName() { + return name; + } + + @Nullable + public List
getAddresses() { + return addresses; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerController.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerController.java new file mode 100644 index 0000000000..da2aaf1d2b --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerController.java @@ -0,0 +1,65 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Put; +import io.micronaut.http.uri.UriBuilder; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import io.micronaut.http.annotation.Status; + +import java.util.List; +import java.util.Optional; + +@Requires(property = "spec.name", value = "EcommerceTest") +@Controller("/customers") +class CustomerController { + private final CustomerRepository customerRepository; + private final OrderRepository orderRepository; + CustomerController(CustomerRepository customerRepository, + OrderRepository orderRepository) { + this.customerRepository = customerRepository; + this.orderRepository = orderRepository; + } + + @Post + @Status(HttpStatus.OK) + void save(@NonNull @NotNull @Valid CreateCustomer customer) { + customerRepository.save(customer); + } + + @Get("/{username}") + Optional find(@PathVariable String username) { + return customerRepository.findByUsername(username); + } + + @Post("/{username}/orders") + HttpResponse saveOrder(@PathVariable String username, @Body CreateOrder order) { + String orderId = orderRepository.save(username, order); + return HttpResponse.created(UriBuilder.of("/customers").path(username).path("orders").path(orderId).build()); + } + + @Get("/{username}/orders") + List getOrders(@PathVariable String username) { + return orderRepository.findAllByUsername(username); + } + + @Get("/{username}/orders/{orderId}") + @Status(HttpStatus.OK) + public Optional findOrder(@PathVariable String username, @PathVariable String orderId) { + return orderRepository.findByUsernameAndOrderId(username, orderId); + } + + @Put("/{username}/orders/{orderId}/status") + @Status(HttpStatus.NO_CONTENT) + void updateStatusForOrder(@PathVariable String username, @PathVariable String orderId, @Body("status") io.micronaut.aws.dynamodb.ecommerce.Status status) { + orderRepository.updateStatusUsernameAndOrderId(username, orderId, status); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerEmail.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerEmail.java new file mode 100644 index 0000000000..11219a0438 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerEmail.java @@ -0,0 +1,33 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +@Introspected +public class CustomerEmail { + @NonNull + @NotBlank + private final String username; + + @NonNull + @NotBlank + @Email + private final String email; + + CustomerEmail(String username, String email) { + this.username = username; + this.email = email; + } + + @NonNull + public String getUsername() { + return username; + } + + @NonNull + public String getEmail() { + return email; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerRepository.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerRepository.java new file mode 100644 index 0000000000..812232f845 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/CustomerRepository.java @@ -0,0 +1,53 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.aws.dynamodb.DynamoDbConversionService; +import io.micronaut.aws.dynamodb.DynamoRepository; +import io.micronaut.aws.dynamodb.ecommerce.items.CustomerEmailRow; +import io.micronaut.aws.dynamodb.ecommerce.items.CustomerRow; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import jakarta.inject.Singleton; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.Put; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +@Requires(property = "spec.name", value = "EcommerceTest") +@Singleton +public class CustomerRepository { + + private final DynamoRepository dynamoRepository; + private final DynamoDbConversionService dynamoDbConversionService; + + CustomerRepository(DynamoRepository dynamoRepository, + DynamoDbConversionService dynamoDbConversionService) { + this.dynamoRepository = dynamoRepository; + this.dynamoDbConversionService = dynamoDbConversionService; + } + + + void save(@NonNull @NotNull @Valid CreateCustomer customer) { + CustomerEmail customerEmail = new CustomerEmail(customer.getUsername(), customer.getEmail()); + CustomerRow customerRow = new CustomerRow(CustomerRow.keyOf(customer.getUsername()), customerEmail.getUsername(), customer.getEmail(), customer.getName()); + CustomerEmailRow customerEmailRow = new CustomerEmailRow(CustomerEmailRow.keyOf(customer.getEmail()), customer.getEmail(), customer.getUsername()); + + Map customerItemMap = dynamoDbConversionService.convert(customerRow); + Consumer customerItemPutBuilder = builder -> builder.item(customerItemMap) + .conditionExpression("attribute_not_exists(pk)"); + Map customerEmailItemMap = dynamoDbConversionService.convert(customerEmailRow); + Consumer customerEmailItemPutBuilder = builder -> builder.item(customerEmailItemMap) + .conditionExpression("attribute_not_exists(pk)"); + dynamoRepository.transactWriteItems(customerItemPutBuilder, customerEmailItemPutBuilder); + } + + @NonNull + public Optional findByUsername(@NonNull String username) { + return dynamoRepository.getItem(CustomerRow.keyOf(username), CustomerRow.class) + .map(row -> new Customer(row.getUsername(), row.getEmail(), row.getName(), null)); + + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/EcommerceTest.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/EcommerceTest.java new file mode 100644 index 0000000000..7a95bf1bbd --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/EcommerceTest.java @@ -0,0 +1,137 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.aws.dynamodb.utils.DynamoDbLocal; +import io.micronaut.context.annotation.Property; +import io.micronaut.core.type.Argument; +import io.micronaut.http.HttpHeaders; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.uri.UriBuilder; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.test.support.TestPropertyProvider; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.math.BigDecimal; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@Property(name = "dynamodb.table-name", value = EcommerceTest.TABLE_NAME) +@Property(name = "spec.name", value = "EcommerceTest") +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class EcommerceTest implements TestPropertyProvider { + + public static final String TABLE_NAME = "ecommerce"; + @Override + public Map getProperties() { + return DynamoDbLocal.getProperties(); + } + + @Inject + @Client("/") + HttpClient httpClient; + + @Test + void ecommerceTest() { + String username = "alexdebrie"; + String path = "/customers"; + String email = "alexdebrie1@gmail.com"; + String name = "Alex DeBrie"; + BlockingHttpClient client = httpClient.toBlocking(); + Map body = Map.of("username", username, "email", email, "name", name); + HttpRequest saveCustomerRequest = HttpRequest.POST(path, body); + HttpResponse saveCustomerResponse = client.exchange(saveCustomerRequest); + assertEquals(HttpStatus.OK, saveCustomerResponse.getStatus()); + + findCustomer(client, path, username, email, name); + + String location = saveOrder(client, path, username); + String orderId = findOrder(client, location, Status.PLACED); + + updateOrderStatus(client, path, username, orderId, Status.CANCELLED); + HttpResponse orderResponse = client.exchange(location, Order.class); + assertEquals(HttpStatus.OK, orderResponse.getStatus()); + Order order = orderResponse.body(); + assertNotNull(order); + assertEquals(Status.CANCELLED, order.getStatus()); + + URI orderUri = UriBuilder.of(path).path(username).path("orders").build(); + HttpResponse> ordersByUserResponse = client.exchange(HttpRequest.GET(orderUri), Argument.listOf(Order.class)); + assertEquals(HttpStatus.OK, ordersByUserResponse.getStatus()); + List orders = ordersByUserResponse.body(); + assertNotNull(orders); + assertEquals(1, orders.size()); + assertOrder(orders.get(0), Status.CANCELLED); + } + + private void findCustomer(BlockingHttpClient client, String path, String username, String email, String name) { + HttpRequest findCustomerRequest = HttpRequest.GET(UriBuilder.of(path).path(username).build()); + HttpResponse findCustomerResponse = client.exchange(findCustomerRequest, Customer.class); + assertEquals(HttpStatus.OK, findCustomerResponse.getStatus()); + Customer customer = findCustomerResponse.body(); + assertNotNull(customer); + assertEquals(username, customer.getUsername()); + assertEquals(email, customer.getEmail()); + assertEquals(name, customer.getName()); + assertNull(customer.getAddresses()); + } + + private String saveOrder(BlockingHttpClient client, String path, String username) { + Map orderBody = Map.of("address", Map.of("streetAddress", "123 1st Street", "postalCode", "10001", "country", "USA"), + "items", Collections.singletonList(Map.of("itemId", "1d45", "description", "Air Force 1s", "price", 15.99, "amount", 1))); + + URI uri = UriBuilder.of(path).path(username).path("orders").build(); + HttpRequest saveOrderRequest = HttpRequest.POST(uri, orderBody); + HttpResponse saveOrderResponse = client.exchange(saveOrderRequest); + assertEquals(HttpStatus.CREATED, saveOrderResponse.getStatus()); + String location = saveOrderResponse.getHeaders().get(HttpHeaders.LOCATION); + assertNotNull(location); + return location; + } + + private String findOrder(BlockingHttpClient client, String location, Status status) { + HttpResponse orderResponse = client.exchange(location, Order.class); + assertEquals(HttpStatus.OK, orderResponse.getStatus()); + Order order = orderResponse.body(); + assertOrder(order, status); + assertOrderItems(order); + return order.getOrderId(); + } + + private void assertOrder(Order order, Status status) { + assertNotNull(order); + assertEquals(status, order.getStatus()); + assertNotNull(order.getAddress()); + assertEquals("123 1st Street", order.getAddress().getStreetAddress()); + assertEquals("USA", order.getAddress().getCountry()); + assertEquals("10001", order.getAddress().getPostalCode()); + } + + private void assertOrderItems(Order order) { + assertNotNull(order.getItems()); + assertEquals(1, order.getItems().size()); + assertNotNull(order.getOrderId()); + assertEquals("Air Force 1s", order.getItems().get(0).getDescription()); + assertEquals("1d45", order.getItems().get(0).getItemId()); + assertEquals(1, order.getItems().get(0).getAmount()); + assertEquals(new BigDecimal("15.99"), order.getItems().get(0).getPrice()); + } + + private void updateOrderStatus(BlockingHttpClient client, String path, String username, String orderId, Status status) { + URI updateStatusUri = UriBuilder.of(path).path(username).path("orders").path(orderId).path("status").build(); + HttpResponse updateStatusResponse = client.exchange(HttpRequest.PUT(updateStatusUri, Map.of("status", status))); + assertEquals(HttpStatus.NO_CONTENT, updateStatusResponse.getStatus()); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/IdGenerator.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/IdGenerator.java new file mode 100644 index 0000000000..cd0050d004 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/IdGenerator.java @@ -0,0 +1,11 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.context.annotation.DefaultImplementation; +import io.micronaut.core.annotation.NonNull; + +@DefaultImplementation(UUIDIdGenerator.class) +public interface IdGenerator { + + @NonNull + String generate(); +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Item.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Item.java new file mode 100644 index 0000000000..02b8791f95 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Item.java @@ -0,0 +1,50 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.math.BigDecimal; + +@Serdeable +public class Item { + @NonNull + @NotBlank + private final String itemId; + + @NotBlank + @NonNull + private final String description; + + @NonNull + @NotNull + private final BigDecimal price; + + @NonNull + @NotNull + private final Integer amount; + + Item(String itemId, String description, BigDecimal price, Integer amount) { + this.itemId = itemId; + this.description = description; + this.price = price; + this.amount = amount; + } + + public String getItemId() { + return itemId; + } + + public String getDescription() { + return description; + } + + public BigDecimal getPrice() { + return price; + } + + public Integer getAmount() { + return amount; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Order.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Order.java new file mode 100644 index 0000000000..5390e59ca2 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Order.java @@ -0,0 +1,71 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Introspected +public class Order { + private final String username; + private final String orderId; + private final Address address; + private final LocalDateTime createdAt; + private final Status status; + private final BigDecimal totalAmount; + private final Integer numberOfTimes; + + private final List items; + + public Order(String username, + String orderId, + Address address, + LocalDateTime createdAt, + Status status, + BigDecimal totalAmount, + Integer numberOfTimes, + @Nullable List items) { + this.username = username; + this.orderId = orderId; + this.address = address; + this.createdAt = createdAt; + this.status = status; + this.totalAmount = totalAmount; + this.numberOfTimes = numberOfTimes; + this.items = items; + } + + public String getUsername() { + return username; + } + + public String getOrderId() { + return orderId; + } + + public Address getAddress() { + return address; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public Status getStatus() { + return status; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public Integer getNumberOfTimes() { + return numberOfTimes; + } + + public List getItems() { + return items; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/OrderItem.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/OrderItem.java new file mode 100644 index 0000000000..15c2e2c869 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/OrderItem.java @@ -0,0 +1,60 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.math.BigDecimal; + +@Serdeable +public class OrderItem { + @NonNull + @NotBlank + private final String orderId; + + @NonNull + @NotBlank + private final String itemId; + + @NotBlank + @NonNull + private final String description; + + @NonNull + @NotNull + private final BigDecimal price; + + @NonNull + @NotNull + private final Integer amount; + + public OrderItem(String orderId, + String itemId, String description, BigDecimal price, Integer amount) { + this.orderId = orderId; + this.itemId = itemId; + this.description = description; + this.price = price; + this.amount = amount; + } + + public String getOrderId() { + return orderId; + } + + public String getItemId() { + return itemId; + } + + public String getDescription() { + return description; + } + + public BigDecimal getPrice() { + return price; + } + + public Integer getAmount() { + return amount; + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/OrderRepository.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/OrderRepository.java new file mode 100644 index 0000000000..b8a062aa2f --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/OrderRepository.java @@ -0,0 +1,186 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.aws.dynamodb.DynamoRepository; +import io.micronaut.aws.dynamodb.ecommerce.items.CustomerRow; +import io.micronaut.aws.dynamodb.ecommerce.items.OrderItemRow; +import io.micronaut.aws.dynamodb.ecommerce.items.OrderRow; +import io.micronaut.aws.dynamodb.utils.AttributeValueUtils; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.util.CollectionUtils; +import jakarta.inject.Singleton; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.Put; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.awssdk.services.dynamodb.model.ReturnValue; +import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsResponse; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@Requires(property = "spec.name", value = "EcommerceTest") +@Singleton +public class OrderRepository { + public static final int SCALE = 2; + public static final String KEY_CLASS_NAME = "className"; + private final DynamoRepository dynamoRepository; + private final IdGenerator idGenerator; + + public OrderRepository(DynamoRepository dynamoRepository, IdGenerator idGenerator) { + this.dynamoRepository = dynamoRepository; + this.idGenerator = idGenerator; + } + + public String save(@NonNull String username, @NonNull @Valid CreateOrder createOrder) { + String orderId = idGenerator.generate(); + OrderRow orderDynamoDbItem = orderRowOf(username, orderId, createOrder); + Map orderDynamoDbItemMap = dynamoRepository.getDynamoDbConversionService().convert(orderDynamoDbItem); + + List> l = createOrder.getItems().stream() + .map(item -> orderItemRowOf(orderId, item)) + .map(orderItemRow -> { + Consumer consumer = builder -> { + Map m = dynamoRepository.getDynamoDbConversionService().convert(orderItemRow); + builder.item(m); + }; + return consumer; + }).collect(Collectors.toList()); + l.add(builder -> builder.item(orderDynamoDbItemMap)); + TransactWriteItemsResponse transactWriteItemsResponse = dynamoRepository.transactWriteItems(l); + return orderId; + } + + @NonNull + public Optional findByUsernameAndOrderId(@NonNull @NotBlank String username, + @NonNull @NotBlank String orderId) { + QueryResponse rsp = dynamoRepository.query(builder -> builder.indexName(BootStrap.INDEX_GSI1) + .keyConditionExpression("#gsi1Pk = :gsi1Pk") + .expressionAttributeNames(Collections.singletonMap("#gsi1Pk", BootStrap.INDEX_GSI1_PK)) + .expressionAttributeValues(Collections.singletonMap(":gsi1Pk", AttributeValueUtils.s(OrderRow.gsi1Of(orderId).getPartionKey()))) + .scanIndexForward(false)); + if (!rsp.hasItems()) { + return Optional.empty(); + } + return ordersOfItems(rsp.items()).stream().findFirst(); + } + + public void updateStatusUsernameAndOrderId(@NonNull @NotBlank String username, + @NonNull @NotBlank String orderId, + @NonNull @NotNull Status status) { + dynamoRepository.updateItem(OrderRow.keyOf(username, orderId), builder -> builder + .conditionExpression("attribute_exists(pk)") + .updateExpression("SET #status = :status") + .expressionAttributeNames(Collections.singletonMap("#status", "status")) + .expressionAttributeValues(Collections.singletonMap(":status", AttributeValueUtils.s(status.toString()))) + .returnValues(ReturnValue.ALL_NEW)); + } + + @NonNull + public List findAllByUsername(@NonNull @NotBlank String username) { + QueryResponse queryResponse = dynamoRepository.query(builder -> builder.keyConditionExpression("#pk = :pk") + .expressionAttributeNames(Collections.singletonMap("#pk", "pk")) + .expressionAttributeValues(Collections.singletonMap(":pk", AttributeValueUtils.s(CustomerRow.keyOf(username).getPartionKey()))) + .scanIndexForward(false) + ); + if (!queryResponse.hasItems()) { + return Collections.emptyList(); + } + return ordersOfItems(queryResponse.items()); + } + + @NonNull + private List ordersOfItems(@NonNull List> items) { + if (CollectionUtils.isEmpty(items)) { + return Collections.emptyList(); + } + List orderRows = new ArrayList<>(); + Map> orderItemRows = new HashMap<>(); + for (Map item : items) { + if (item.containsKey(KEY_CLASS_NAME)) { + String type = item.get(KEY_CLASS_NAME).s(); + if (type != null) { + if (type.equals(OrderRow.class.getName())) { + orderRows.add(dynamoRepository.getDynamoDbConversionService().convert(item, OrderRow.class)); + + } else if (type.equals(OrderItemRow.class.getName())) { + OrderItemRow orderItemRow = dynamoRepository.getDynamoDbConversionService().convert(item, OrderItemRow.class); + if (!item.containsKey(orderItemRow.getOrderId())) { + orderItemRows.put(orderItemRow.getOrderId(), new ArrayList<>()); + } + orderItemRows.get(orderItemRow.getOrderId()).add(orderItemRow); + } + } + } + } + List orders = new ArrayList<>(); + for (OrderRow orderRow : orderRows) { + orders.add(orderOfOrderRow(orderRow, orderItemRows.get(orderRow.getOrderId()))); + } + return orders; + } + + @NonNull + private Order orderOfOrderRow(@NonNull OrderRow orderRow, @Nullable List orderItemRows) { + return new Order(orderRow.getUsername(), + orderRow.getOrderId(), + orderRow.getAddress(), + orderRow.getCreatedAt(), + orderRow.getStatus(), + orderRow.getTotalAmount(), + orderRow.getNumberItems(), + orderItemRows == null ? Collections.emptyList() : + orderItemRows.stream().map(this::orderItemOfOrderItemRow).collect(Collectors.toList())); + } + + @NonNull + private OrderItem orderItemOfOrderItemRow(@NonNull OrderItemRow orderItemRow) { + return new OrderItem(orderItemRow.getOrderId(), + orderItemRow.getItemId(), orderItemRow.getDescription(), orderItemRow.getPrice(), orderItemRow.getAmount()); + } + + @NonNull + private OrderItemRow orderItemRowOf(@NonNull String orderId, @NonNull @Valid Item orderItem) { + return new OrderItemRow(OrderItemRow.keyOf(orderId, orderItem.getItemId()), + OrderItemRow.gsi1Of(orderId, orderItem.getItemId()), + orderId, + orderItem.getItemId(), + orderItem.getDescription(), + orderItem.getPrice(), + orderItem.getAmount(), + orderItem.getPrice().multiply(BigDecimal.valueOf(orderItem.getAmount())).setScale(SCALE, RoundingMode.HALF_UP) + ); + } + + @NonNull + private OrderRow orderRowOf(@NonNull String username, @NonNull String orderId, @NonNull @Valid CreateOrder createOrder) { + BigDecimal totalAmount = createOrder.getItems().stream().map(Item::getPrice) + .reduce(BigDecimal.ZERO, BigDecimal::add) + .setScale(SCALE, RoundingMode.HALF_UP); + + Integer numberItems = createOrder.getItems().size(); + return new OrderRow(OrderRow.keyOf(username, orderId), + OrderRow.gsi1Of(orderId), + username, + orderId, + createOrder.getAddress(), + LocalDateTime.now(), + Status.PLACED, + totalAmount, + numberItems + ); + } + +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Status.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Status.java new file mode 100644 index 0000000000..2b69cc6110 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/Status.java @@ -0,0 +1,9 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +public enum Status { + PLACED, + PICKED, + SHIPPED, + DELIVERED, + CANCELLED +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/UUIDIdGenerator.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/UUIDIdGenerator.java new file mode 100644 index 0000000000..5cf92c8488 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/UUIDIdGenerator.java @@ -0,0 +1,15 @@ +package io.micronaut.aws.dynamodb.ecommerce; + +import io.micronaut.core.annotation.NonNull; +import jakarta.inject.Singleton; + +import java.util.UUID; + +@Singleton +public class UUIDIdGenerator implements IdGenerator { + @Override + @NonNull + public String generate() { + return UUID.randomUUID().toString(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/CustomerEmailRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/CustomerEmailRow.java new file mode 100644 index 0000000000..a0042a9e10 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/CustomerEmailRow.java @@ -0,0 +1,46 @@ +package io.micronaut.aws.dynamodb.ecommerce.items; + +import io.micronaut.aws.dynamodb.SingleTableRow; +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +@Serdeable +public class CustomerEmailRow extends SingleTableRow { + @NonNull + @NotBlank + private final String username; + + @NonNull + @NotBlank + @Email + private final String email; + + @Creator + public CustomerEmailRow(String pk, String sk, String className, String username, String email) { + super(pk, sk, className); + this.username = username; + this.email = email; + } + + public CustomerEmailRow(CompositeKey key, String username, String email) { + this(key.getPartionKey(), key.getSortKey(), CustomerEmailRow.class.getName(), username, email); + } + + public String getUsername() { + return username; + } + + public String getEmail() { + return email; + } + + @NonNull + public static CompositeKey keyOf(@NonNull String email) { + return CompositeKey.of("CUSTOMEREMAIL#" + email, + "CUSTOMEREMAIL#" + email); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/CustomerRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/CustomerRow.java new file mode 100644 index 0000000000..03c23aad55 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/CustomerRow.java @@ -0,0 +1,55 @@ +package io.micronaut.aws.dynamodb.ecommerce.items; + +import io.micronaut.aws.dynamodb.SingleTableRow; +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +@Introspected +public class CustomerRow extends SingleTableRow { + + @NonNull + @NotBlank + private final String username; + + @NonNull + @NotBlank + @Email + private final String email; + + @NonNull + @NotBlank + private final String name; + + @Creator + public CustomerRow(String pk, String sk, String className, String username, String email, String name) { + super(pk, sk, className); + this.username = username; + this.email = email; + this.name = name; + } + + public CustomerRow(CompositeKey key, String username, String email, String name) { + this(key.getPartionKey(), key.getSortKey(), CustomerRow.class.getName(), username, email, name); + } + + public String getUsername() { + return username; + } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + @NonNull + public static CompositeKey keyOf(@NonNull String username) { + return CompositeKey.of("CUSTOMER#" + username, "CUSTOMER#" + username); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/OrderItemRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/OrderItemRow.java new file mode 100644 index 0000000000..c76970e501 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/OrderItemRow.java @@ -0,0 +1,115 @@ +package io.micronaut.aws.dynamodb.ecommerce.items; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.GlobalSecondaryIndex1; +import io.micronaut.aws.dynamodb.SingleTableRowWithOneGlobalSecondaryIndex; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.NonNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.math.BigDecimal; + +public class OrderItemRow extends SingleTableRowWithOneGlobalSecondaryIndex { + + @NonNull + @NotBlank + private final String orderId; + + @NonNull + @NotBlank + private final String itemId; + + @NonNull + @NotBlank + private final String description; + + @NonNull + @NotNull + private final BigDecimal price; + + @NonNull + @NotNull + private final Integer amount; + + @NonNull + @NotNull + private final BigDecimal totalCost; + + @Creator + public OrderItemRow(String pk, + String sk, + String className, + String gsi1Pk, + String gsi1Sk, + String orderId, + String itemId, + String description, + BigDecimal price, + Integer amount, + BigDecimal totalCost) { + super(pk, sk, className, gsi1Pk, gsi1Sk); + this.orderId = orderId; + this.itemId = itemId; + this.description = description; + this.price = price; + this.amount = amount; + this.totalCost = totalCost; + } + + public OrderItemRow(CompositeKey key, + GlobalSecondaryIndex1 gsi1, + String orderId, + String itemId, + String description, + BigDecimal price, + Integer amount, + BigDecimal totalCost) { + this(key.getPartionKey(), + key.getSortKey(), + OrderItemRow.class.getName(), + gsi1.getPartionKey(), + gsi1.getSortKey(), + orderId, + itemId, + description, + price, + amount, + totalCost); + } + + public String getOrderId() { + return orderId; + } + + public String getItemId() { + return itemId; + } + + public String getDescription() { + return description; + } + + public BigDecimal getPrice() { + return price; + } + + public Integer getAmount() { + return amount; + } + + public BigDecimal getTotalCost() { + return totalCost; + } + + @NonNull + public static CompositeKey keyOf(@NonNull String orderId, String itemId) { + final String value = "ORDER#" + orderId + "#ITEM#" + itemId; + return CompositeKey.of(value, value); + } + + @NonNull + public static GlobalSecondaryIndex1 gsi1Of(@NonNull String orderId, String itemId) { + return GlobalSecondaryIndex1.of("ORDER#" + orderId, "ITEM#" + itemId); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/OrderRow.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/OrderRow.java new file mode 100644 index 0000000000..050b85a4ee --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/ecommerce/items/OrderRow.java @@ -0,0 +1,120 @@ +package io.micronaut.aws.dynamodb.ecommerce.items; + +import io.micronaut.aws.dynamodb.CompositeKey; +import io.micronaut.aws.dynamodb.GlobalSecondaryIndex1; +import io.micronaut.aws.dynamodb.SingleTableRowWithOneGlobalSecondaryIndex; +import io.micronaut.aws.dynamodb.ecommerce.Address; +import io.micronaut.aws.dynamodb.ecommerce.Status; +import io.micronaut.core.annotation.Creator; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Introspected +public class OrderRow extends SingleTableRowWithOneGlobalSecondaryIndex { + + private final String type; + + private final String username; + private final String orderId; + + private final Address address; + + private final LocalDateTime createdAt; + + private final Status status; + + private final BigDecimal totalAmount; + + private final Integer numberItems; + + @Creator + public OrderRow(String pk, + String sk, + String className, + String gsi1Pk, + String gsi1Sk, + String type, + String username, + String orderId, + Address address, + LocalDateTime createdAt, + Status status, + BigDecimal totalAmount, + Integer numberItems) { + super(pk, sk, className, gsi1Pk, gsi1Sk); + this.type = type; + this.username = username; + this.orderId = orderId; + this.address = address; + this.createdAt = createdAt; + this.status = status; + this.totalAmount = totalAmount; + this.numberItems = numberItems; + } + + public OrderRow(CompositeKey key, + GlobalSecondaryIndex1 gsi1, + String username, + String orderId, + Address address, + LocalDateTime createdAt, + Status status, + BigDecimal totalAmount, + Integer numberItems) { + this(key.getPartionKey(), key.getSortKey(), OrderRow.class.getName(), gsi1.getPartionKey(), gsi1.getSortKey(), + OrderRow.class.getName(), + username, + orderId, + address, + createdAt, + status, + totalAmount, + numberItems); + } + + public String getType() { + return type; + } + + public String getUsername() { + return username; + } + + public String getOrderId() { + return orderId; + } + + public Address getAddress() { + return address; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public Status getStatus() { + return status; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public Integer getNumberItems() { + return numberItems; + } + + @NonNull + public static CompositeKey keyOf(@NonNull String username, @NonNull String orderId) { + return CompositeKey.of("CUSTOMER#" + username, "ORDER#" + orderId); + } + + @NonNull + public static GlobalSecondaryIndex1 gsi1Of(@NonNull String orderId) { + return GlobalSecondaryIndex1.of("ORDER#" + orderId, "ORDER#" + orderId); + } + +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDBLocalExtension.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDBLocalExtension.java new file mode 100644 index 0000000000..db7bdb8286 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDBLocalExtension.java @@ -0,0 +1,12 @@ +package io.micronaut.aws.dynamodb.utils; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class DynamoDBLocalExtension implements AfterAllCallback { + @Override + public void afterAll(ExtensionContext context) throws Exception { + System.out.println("Shutting down dynamo"); + DynamoDbLocal.shutdown(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDbClientBuilderListener.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDbClientBuilderListener.java new file mode 100644 index 0000000000..d427f6f32a --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDbClientBuilderListener.java @@ -0,0 +1,50 @@ +package io.micronaut.aws.dynamodb.utils; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.context.annotation.Value; +import io.micronaut.context.event.BeanCreatedEvent; +import io.micronaut.context.event.BeanCreatedEventListener; +import io.micronaut.context.exceptions.ConfigurationException; +import jakarta.inject.Singleton; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; + +import java.net.URI; +import java.net.URISyntaxException; + +@Requires(property = "dynamodb-local.host") +@Requires(property = "dynamodb-local.port") +@Singleton +class DynamoDbClientBuilderListener + implements BeanCreatedEventListener { + private final URI endpoint; + private final String accessKeyId; + private final String secretAccessKey; + + DynamoDbClientBuilderListener(@Value("${dynamodb-local.host}") String host, + @Value("${dynamodb-local.port}") String port) { + try { + this.endpoint = new URI("http://" + host + ":" + port); + } catch (URISyntaxException e) { + throw new ConfigurationException("dynamodb.endpoint not a valid URI"); + } + this.accessKeyId = "fakeMyKeyId"; + this.secretAccessKey = "fakeSecretAccessKey"; + } + + @Override + public DynamoDbClientBuilder onCreated(BeanCreatedEvent event) { + return event.getBean().endpointOverride(endpoint) + .credentialsProvider(() -> new AwsCredentials() { + @Override + public String accessKeyId() { + return accessKeyId; + } + + @Override + public String secretAccessKey() { + return secretAccessKey; + } + }); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDbLocal.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDbLocal.java new file mode 100644 index 0000000000..67dbfb88cc --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/DynamoDbLocal.java @@ -0,0 +1,38 @@ +package io.micronaut.aws.dynamodb.utils; + +import io.micronaut.core.util.CollectionUtils; +import org.testcontainers.containers.GenericContainer; + +import java.util.Map; + +public class DynamoDbLocal implements AutoCloseable { + + private static GenericContainer dynamoDBLocal; + + private static GenericContainer getDynamoDBLocal() { + if (dynamoDBLocal == null) { + dynamoDBLocal = new GenericContainer("amazon/dynamodb-local") + .withExposedPorts(8000); + dynamoDBLocal.start(); + } + return dynamoDBLocal; + } + + public static Map getProperties() { + return CollectionUtils.mapOf( + "dynamodb-local.host", "localhost", + "dynamodb-local.port", getDynamoDBLocal().getFirstMappedPort()); + + } + + public static void shutdown() { + if (dynamoDBLocal != null) { + dynamoDBLocal.close(); + } + } + + @Override + public void close() throws Exception { + shutdown(); + } +} diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/TestBootstrap.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/TestBootstrap.java new file mode 100644 index 0000000000..43fa2dafe8 --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/TestBootstrap.java @@ -0,0 +1,26 @@ +package io.micronaut.aws.dynamodb.utils; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.context.event.ApplicationEventListener; +import io.micronaut.context.event.StartupEvent; +import jakarta.inject.Singleton; + +@Requires(property = "dynamodb-local.host") +@Requires(property = "dynamodb-local.port") +@Singleton +public class TestBootstrap implements ApplicationEventListener { + + private final TestDynamoRepository dynamoRepository; + + public TestBootstrap(TestDynamoRepository dynamoRepository) { + this.dynamoRepository = dynamoRepository; + } + + @Override + public void onApplicationEvent(StartupEvent event) { + if (!dynamoRepository.existsTable()) { + dynamoRepository.createTable(); + } + } +} + diff --git a/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/TestDynamoRepository.java b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/TestDynamoRepository.java new file mode 100644 index 0000000000..41b5e98b3d --- /dev/null +++ b/dynamodb/src/test/java/io/micronaut/aws/dynamodb/utils/TestDynamoRepository.java @@ -0,0 +1,97 @@ +package io.micronaut.aws.dynamodb.utils; + +import io.micronaut.aws.dynamodb.conf.DynamoConfiguration; +import jakarta.inject.Singleton; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.BillingMode; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest; +import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.Projection; +import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import java.util.Arrays; +import java.util.Collections; + +public abstract class TestDynamoRepository { + public static final String ATTRIBUTE_PK = "pk"; + + protected final DynamoDbClient dynamoDbClient; + protected final DynamoConfiguration dynamoConfiguration; + + public TestDynamoRepository(DynamoDbClient dynamoDbClient, + DynamoConfiguration dynamoConfiguration) { + this.dynamoDbClient = dynamoDbClient; + this.dynamoConfiguration = dynamoConfiguration; + } + + + public boolean existsTable() { + try { + dynamoDbClient.describeTable(DescribeTableRequest.builder() + .tableName(dynamoConfiguration.getTableName()) + .build()); + return true; + } catch (ResourceNotFoundException e) { + return false; + } + } + + public void createTable() { + dynamoDbClient.createTable(createTableRequest(dynamoConfiguration.getTableName())); + } + + public abstract CreateTableRequest createTableRequest(String tableName); + + + public static KeySchemaElement keySchemaElement(String attributeName, KeyType keyType) { + return KeySchemaElement.builder() + .attributeName(attributeName) + .keyType(keyType) + .build(); + } + + public static AttributeDefinition attributeDefinition(String attributeName, ScalarAttributeType attributeType) { + return AttributeDefinition.builder() + .attributeName(attributeName) + .attributeType(attributeType) + .build(); + } + + public static GlobalSecondaryIndex gsi(String indexName, + String pkAttributeName, + String skAttributeName) { + return GlobalSecondaryIndex.builder() + .indexName(indexName) + .keySchema(KeySchemaElement.builder() + .attributeName(pkAttributeName) + .keyType(KeyType.HASH) + .build(), KeySchemaElement.builder() + .attributeName(skAttributeName) + .keyType(KeyType.RANGE) + .build()) + .projection(Projection.builder() + .projectionType(ProjectionType.ALL) + .build()) + .build(); + } + + public static GlobalSecondaryIndex gsi(String indexName, + String pkAttributeName, + ProjectionType projectionType) { + return GlobalSecondaryIndex.builder() + .indexName(indexName) + .keySchema(KeySchemaElement.builder() + .attributeName(pkAttributeName) + .keyType(KeyType.HASH) + .build()) + .projection(Projection.builder() + .projectionType(projectionType) + .build()) + .build(); + } +} diff --git a/dynamodb/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/dynamodb/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 0000000000..f4341771fd --- /dev/null +++ b/dynamodb/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +io.micronaut.aws.dynamodb.utils.DynamoDBLocalExtension diff --git a/dynamodb/src/test/resources/logback.xml b/dynamodb/src/test/resources/logback.xml new file mode 100644 index 0000000000..822ce3c95f --- /dev/null +++ b/dynamodb/src/test/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cdfbf139ba..91f0c97c0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ bouncycastle = '1.70' fileupload = '0.0.5' jetty = '9.4.50.v20221201' logback-json-classic = '0.1.5' +ksuid = '1.1.2' micronaut-discovery = '4.0.0-M1' micronaut-groovy = '4.0.0-M1' @@ -87,6 +88,8 @@ junit-jupiter-api = { module = 'org.junit.jupiter:junit-jupiter-api' } junit-platform-engine = { module = "org.junit.platform:junit-platform-suite-engine" } aws-java-sdk-lambda = { module = 'com.amazonaws:aws-java-sdk-lambda' } +ksuid = { module = "com.github.ksuid:ksuid", version.ref = 'ksuid' } + logback-classic = { module = "ch.qos.logback:logback-classic" } logback-json-classic = { module = 'ch.qos.logback.contrib:logback-json-classic', version.ref = 'logback-json-classic' } jackson-databind = { module = 'com.fasterxml.jackson.core:jackson-databind' } diff --git a/settings.gradle.kts b/settings.gradle.kts index 854327207f..72128c0836 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,6 +15,7 @@ rootProject.name = "aws-parent" include("aws-ua") include("aws-bom") +include("dynamodb") include("function-aws") include("function-client-aws") include("function-aws-api-proxy") diff --git a/src/main/docs/guide/sdkv2/dynamodb.adoc b/src/main/docs/guide/sdkv2/dynamodb.adoc index 315f1e991e..a5fb956981 100644 --- a/src/main/docs/guide/sdkv2/dynamodb.adoc +++ b/src/main/docs/guide/sdkv2/dynamodb.adoc @@ -12,4 +12,4 @@ And: * `software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClientBuilder` * `software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient`. -The HTTP client, credentials and region will be configured as per described in the <>. \ No newline at end of file +The HTTP client, credentials and region will be configured as per described in the <>. diff --git a/src/main/docs/guide/sdkv2/dynamodb/dynamodbConverters.adoc b/src/main/docs/guide/sdkv2/dynamodb/dynamodbConverters.adoc new file mode 100644 index 0000000000..19294b293b --- /dev/null +++ b/src/main/docs/guide/sdkv2/dynamodb/dynamodbConverters.adoc @@ -0,0 +1,3 @@ +Add the following dependency, to add https://docs.micronaut.io/latest/guide/#customTypeConverter[TypeConverters] to and from `software.amazon.awssdk.services.dynamodb.model.AttributeValue`. + +dependency:micronaut-dynamodb[groupId="io.micronaut.aws"] diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index 5de6409772..ec5d7a3937 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -23,7 +23,9 @@ sdkv2: endpointoverride: Endpoint override apiGatewayManagementApi: API Gateway Management API Client s3: S3 - dynamodb: Dynamo DB + dynamodb: + title: DynamoDB + dynamodbConverters: DynamoDB TypeConverters ses: SES sns: SNS sqs: SQS diff --git a/test-suite/build.gradle.kts b/test-suite/build.gradle.kts index 533209a681..ec1bb0c9da 100644 --- a/test-suite/build.gradle.kts +++ b/test-suite/build.gradle.kts @@ -16,11 +16,7 @@ dependencies { testImplementation(libs.junit.jupiter.api) testImplementation(mnTest.micronaut.test.junit5) testRuntimeOnly(libs.junit.jupiter.engine) - testImplementation(platform(mn.micronaut.core.bom)) - testImplementation("org.junit.jupiter:junit-jupiter-api") - testImplementation("io.micronaut.test:micronaut-test-junit5") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testImplementation(projects.micronautFunctionAws) testImplementation(projects.micronautFunctionClientAws) testRuntimeOnly(mn.snakeyaml)