diff --git a/docs/guide/src/docs/asciidoc/dynamodb.adoc b/docs/guide/src/docs/asciidoc/dynamodb.adoc index 7581336c9..ce0307e59 100644 --- a/docs/guide/src/docs/asciidoc/dynamodb.adoc +++ b/docs/guide/src/docs/asciidoc/dynamodb.adoc @@ -581,6 +581,7 @@ include::{root-dir}/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy <8> You can combine `@SortKey` and `@Filter` annotations to specify the sort key condition <9> Only`EQ`, `LE`, `LT`, `GE`, `GT`, `BETWEEN` and `BEGINS_WITH` operators are supported <10> You can also use `@Filter` annotation to specify the sort key name +<11> If you use any customization annotations on delete method, then the method will be used as batch delete method TIP: The operator `EQ` is used by default if `@Filter` annotation is not present. This makes it special and the service introduction tries to find the appropriate operation based on the actual value. For collections or arrays, `inList` operation is actually used. If the actual value is `null` then `isNull` operation is used. For other types, `eq` operation is used. For sort keys, `eq` operation is always used. diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/ServiceIntroduction.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/ServiceIntroduction.java index bc609a52e..f4022cf58 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/ServiceIntroduction.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/ServiceIntroduction.java @@ -49,6 +49,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; /** @@ -62,6 +63,11 @@ public class ServiceIntroduction implements MethodInterceptor { private static final String HASH = "hash"; private static final String RANGE = "range"; + private static class ItemArgument { + Argument argument; + boolean single; + } + private static class FilterArgument { Argument firstArgument; Argument secondArgument; @@ -156,7 +162,7 @@ private Object doIntercept(MethodInvocationContext context, private Object doIntercept(MethodInvocationContext context, DynamoDbService service) { String methodName = context.getMethodName(); if (methodName.startsWith("save")) { - return handleSave(service, context); + return handleSave(service, context, findItemArgument(service, context)); } if (methodName.startsWith("get") || methodName.startsWith("load")) { @@ -210,6 +216,13 @@ private Object doIntercept(MethodInvocationContext context, return service.update(criteria); } + if (methodName.startsWith("delete")) { + Optional maybeItemArgument = findItemArgument(service, context); + if (maybeItemArgument.isPresent()) { + return handleDelete(service, context, maybeItemArgument); + } + } + if (methodName.startsWith("query") || methodName.startsWith("findAll") || methodName.startsWith("list") || methodName.startsWith("count") || methodName.startsWith("delete")) { String index = context.getTargetMethod().isAnnotationPresent(Index.class) ? context.getTargetMethod().getAnnotation(Index.class).value() : null; boolean consistent = context.getTargetMethod().isAnnotationPresent(Consistent.class) && context.getTargetMethod().getAnnotation(Consistent.class).value(); @@ -226,10 +239,11 @@ private Object doIntercept(MethodInvocationContext context, } if (methodName.startsWith("delete")) { + Optional maybeItemArgument = findItemArgument(service, context); if (customized) { return service.deleteAll(service.query(generateQuery(context, partitionAndSort, index, consistent, descending))); } - return handleDelete(service, context); + return handleDelete(service, context, maybeItemArgument); } if (customized) { @@ -252,41 +266,35 @@ private Object publisherOrIterable(Publisher result, Class type) { return Flux.from(result).collectList().blockOptional().orElse(Collections.emptyList()); } - private Object handleSave(DynamoDbService service, MethodInvocationContext context) { + @SuppressWarnings("unchecked") + private Object handleSave(DynamoDbService service, MethodInvocationContext context, Optional maybeItemArgument) { Map> params = context.getParameters(); - Argument[] args = context.getArguments(); - - if (args.length != 1) { - throw new UnsupportedOperationException("Method expects 1 parameter - item, iterable of items or array of items"); - } - Argument itemArgument = args[0]; - Publisher items = toPublisher(service.getItemType(), itemArgument, params); - - if (itemArgument.getType().isArray() || Iterable.class.isAssignableFrom(itemArgument.getType()) || Publisher.class.isAssignableFrom(itemArgument.getType())) { + if (maybeItemArgument.isPresent()) { + ItemArgument itemArgument = maybeItemArgument.get(); + Publisher items = toPublisher(service.getItemType(), itemArgument.argument, params); + if (itemArgument.single) { + return service.save((T) params.get(itemArgument.argument.getName()).getValue()); + } return publisherOrIterable(service.saveAll(items), context.getReturnType().getType()); + } else { + throw new UnsupportedOperationException("Method expects 1 parameter - item, iterable of items or array of items"); } - - return service.save((T) params.get(itemArgument.getName()).getValue()); } - private Object handleDelete(DynamoDbService service, MethodInvocationContext context) { + private Object handleDelete(DynamoDbService service, MethodInvocationContext context, Optional maybeItemArgument) { Map> params = context.getParameters(); - Argument[] args = context.getArguments(); - if (args.length == 1) { - Argument itemArgument = args[0]; - Publisher items = toPublisher(service.getItemType(), itemArgument, params); - - if (itemArgument.getType().isArray() || Iterable.class.isAssignableFrom(itemArgument.getType()) || Publisher.class.isAssignableFrom(itemArgument.getType())) { - return service.deleteAll(items); - } - - if (service.getItemType().isAssignableFrom(itemArgument.getType())) { + if (maybeItemArgument.isPresent()) { + ItemArgument itemArgument = maybeItemArgument.get(); + Publisher items = toPublisher(service.getItemType(), itemArgument.argument, params); + if (itemArgument.single) { return service.delete(Flux.from(items).blockFirst()); } + return service.deleteAll(items); } + Argument[] args = context.getArguments(); if (args.length > 2) { throw new UnsupportedOperationException("Method expects at most 2 parameters - partition key and sort key, an item or items"); } @@ -323,6 +331,30 @@ private Object handleGet(DynamoDbService service, MethodInvocationContext return service.get(partitionValue, partitionAndSort.getSortValue(params)); } + private Optional findItemArgument(DynamoDbService service, MethodInvocationContext context) { + Map> params = context.getParameters(); + Argument[] args = context.getArguments(); + + if (args.length == 1) { + Argument itemArgument = args[0]; + if (itemArgument.getType().isArray() || Iterable.class.isAssignableFrom(itemArgument.getType()) || Publisher.class.isAssignableFrom(itemArgument.getType())) { + ItemArgument item = new ItemArgument(); + item.argument = itemArgument; + item.single = false; + return Optional.of(item); + } + + if (service.getItemType().isAssignableFrom(itemArgument.getType())) { + ItemArgument item = new ItemArgument(); + item.argument = itemArgument; + item.single = true; + return Optional.of(item); + } + } + + return Optional.empty(); + } + private QueryArguments findHashAndRange(Argument[] arguments, DynamoDbService table) { QueryArguments names = new QueryArguments(); for (Argument argument : arguments) { diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy index 61adfb7bd..ffc5d6a96 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy @@ -71,6 +71,7 @@ class DefaultDynamoDBServiceSpec extends Specification { } // end::setup[] + @SuppressWarnings('LineLength') void 'unsupported methods throws meaningful messages'() { when: unknownMethodsService.doSomething() @@ -87,8 +88,8 @@ class DefaultDynamoDBServiceSpec extends Specification { when: unknownMethodsService.delete('1', '1', '1') then: - UnsupportedOperationException e3 = thrown(UnsupportedOperationException) - e3.message == 'Method expects at most 2 parameters - partition key and sort key, an item or items' + IllegalArgumentException e3 = thrown(IllegalArgumentException) + e3.message == '''Unknown property somethingElse for DynamoDBEntity{parentId='null', id='null', rangeIndex='null', date=null, number=0, mapProperty={}}''' when: unknownMethodsService.get('1', '1', '1')