1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.socialsignin.spring.data.dynamodb.repository.query;
17
18 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel;
19 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel;
20 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller;
21 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
22 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter;
23 import com.amazonaws.services.dynamodbv2.model.AttributeValue;
24 import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
25 import com.amazonaws.services.dynamodbv2.model.Condition;
26 import com.amazonaws.services.dynamodbv2.model.QueryRequest;
27 import com.amazonaws.services.dynamodbv2.model.Select;
28 import org.socialsignin.spring.data.dynamodb.core.DynamoDBOperations;
29 import org.socialsignin.spring.data.dynamodb.marshaller.Date2IsoDynamoDBMarshaller;
30 import org.socialsignin.spring.data.dynamodb.marshaller.Instant2IsoDynamoDBMarshaller;
31 import org.socialsignin.spring.data.dynamodb.query.Query;
32 import org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBEntityInformation;
33 import org.socialsignin.spring.data.dynamodb.utils.SortHandler;
34 import org.springframework.data.domain.Sort;
35 import org.springframework.data.domain.Sort.Direction;
36 import org.springframework.data.domain.Sort.Order;
37 import org.springframework.lang.Nullable;
38 import org.springframework.util.Assert;
39 import org.springframework.util.ClassUtils;
40 import org.springframework.util.LinkedMultiValueMap;
41 import org.springframework.util.MultiValueMap;
42
43 import java.time.Instant;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.Date;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Map.Entry;
53 import java.util.Optional;
54
55
56
57
58
59 public abstract class AbstractDynamoDBQueryCriteria<T, ID> implements DynamoDBQueryCriteria<T, ID>, SortHandler {
60
61 protected Class<T> clazz;
62 private DynamoDBEntityInformation<T, ID> entityInformation;
63 private Map<String, String> attributeNamesByPropertyName;
64 private final DynamoDBMapperTableModel<T> tableModel;
65 private String hashKeyPropertyName;
66
67 protected MultiValueMap<String, Condition> attributeConditions;
68 protected MultiValueMap<String, Condition> propertyConditions;
69
70 protected Object hashKeyAttributeValue;
71 protected Object hashKeyPropertyValue;
72 protected String globalSecondaryIndexName;
73 protected Sort sort = Sort.unsorted();
74 protected Optional<String> projection = Optional.empty();
75
76 public abstract boolean isApplicableForLoad();
77
78 protected QueryRequest buildQueryRequest(String tableName, String theIndexName, String hashKeyAttributeName,
79 String rangeKeyAttributeName, String rangeKeyPropertyName, List<Condition> hashKeyConditions,
80 List<Condition> rangeKeyConditions) {
81
82
83 QueryRequest queryRequest = new QueryRequest();
84 queryRequest.setTableName(tableName);
85 queryRequest.setIndexName(theIndexName);
86
87 if (isApplicableForGlobalSecondaryIndex()) {
88 List<String> allowedSortProperties = new ArrayList<>();
89
90 for (Entry<String, List<Condition>> singlePropertyCondition : propertyConditions.entrySet()) {
91 if (entityInformation.getGlobalSecondaryIndexNamesByPropertyName().keySet()
92 .contains(singlePropertyCondition.getKey())) {
93 allowedSortProperties.add(singlePropertyCondition.getKey());
94 }
95 }
96
97 HashMap<String, Condition> keyConditions = new HashMap<>();
98
99 if (hashKeyConditions != null && hashKeyConditions.size() > 0) {
100 for (Condition hashKeyCondition : hashKeyConditions) {
101 keyConditions.put(hashKeyAttributeName, hashKeyCondition);
102 allowedSortProperties.add(hashKeyPropertyName);
103 }
104 }
105 if (rangeKeyConditions != null && rangeKeyConditions.size() > 0) {
106 for (Condition rangeKeyCondition : rangeKeyConditions) {
107 keyConditions.put(rangeKeyAttributeName, rangeKeyCondition);
108 allowedSortProperties.add(rangeKeyPropertyName);
109 }
110 }
111
112 for (Entry<String, List<Condition>> singleAttributeConditions : attributeConditions.entrySet()) {
113
114 for (Condition condition : singleAttributeConditions.getValue()) {
115 keyConditions.put(singleAttributeConditions.getKey(), condition);
116 }
117 }
118
119 for (Order order : sort) {
120 final String sortProperty = order.getProperty();
121 if (entityInformation.isGlobalIndexRangeKeyProperty(sortProperty)) {
122 allowedSortProperties.add(sortProperty);
123 }
124 }
125
126 queryRequest.setKeyConditions(keyConditions);
127
128 if (projection.isPresent()) {
129 queryRequest.setSelect(Select.SPECIFIC_ATTRIBUTES);
130 queryRequest.setProjectionExpression(projection.get());
131 } else {
132 queryRequest.setSelect(Select.ALL_PROJECTED_ATTRIBUTES);
133 }
134
135 applySortIfSpecified(queryRequest, new ArrayList<>(new HashSet<>(allowedSortProperties)));
136 }
137 return queryRequest;
138 }
139
140 protected void applySortIfSpecified(DynamoDBQueryExpression<T> queryExpression,
141 List<String> permittedPropertyNames) {
142 if (permittedPropertyNames.size() > 1) {
143 throw new UnsupportedOperationException("Can only sort by at most a single range or index range key");
144
145 }
146
147 boolean sortAlreadySet = false;
148 for (Order order : sort) {
149 if (permittedPropertyNames.contains(order.getProperty())) {
150 if (sortAlreadySet) {
151 throw new UnsupportedOperationException("Sorting by multiple attributes not possible");
152
153 }
154 queryExpression.setScanIndexForward(order.getDirection().equals(Direction.ASC));
155 sortAlreadySet = true;
156 } else {
157 throw new UnsupportedOperationException(
158 "Sorting only possible by " + permittedPropertyNames + " for the criteria specified");
159 }
160 }
161 }
162
163 protected void applySortIfSpecified(QueryRequest queryRequest, List<String> permittedPropertyNames) {
164 if (permittedPropertyNames.size() > 2) {
165 throw new UnsupportedOperationException("Can only sort by at most a single global hash and range key");
166 }
167
168 boolean sortAlreadySet = false;
169 for (Order order : sort) {
170 if (permittedPropertyNames.contains(order.getProperty())) {
171 if (sortAlreadySet) {
172 throw new UnsupportedOperationException("Sorting by multiple attributes not possible");
173
174 }
175 if (queryRequest.getKeyConditions().size() > 1 && !hasIndexHashKeyEqualCondition()) {
176 throw new UnsupportedOperationException(
177 "Sorting for global index queries with criteria on both hash and range not possible");
178
179 }
180 queryRequest.setScanIndexForward(order.getDirection().equals(Direction.ASC));
181 sortAlreadySet = true;
182 } else {
183 throw new UnsupportedOperationException(
184 "Sorting only possible by " + permittedPropertyNames + " for the criteria specified");
185 }
186 }
187 }
188
189 public boolean comparisonOperatorsPermittedForQuery() {
190 List<ComparisonOperator> comparisonOperatorsPermittedForQuery = Arrays.asList(new ComparisonOperator[]{
191 ComparisonOperator.EQ, ComparisonOperator.LE, ComparisonOperator.LT, ComparisonOperator.GE,
192 ComparisonOperator.GT, ComparisonOperator.BEGINS_WITH, ComparisonOperator.BETWEEN});
193
194
195 for (Collection<Condition> conditions : attributeConditions.values()) {
196 for (Condition condition : conditions) {
197 if (!comparisonOperatorsPermittedForQuery
198 .contains(ComparisonOperator.fromValue(condition.getComparisonOperator()))) {
199 return false;
200 }
201 }
202 }
203 return true;
204 }
205
206 protected List<Condition> getHashKeyConditions() {
207 List<Condition> hashKeyConditions = null;
208 if (isApplicableForGlobalSecondaryIndex() && entityInformation.getGlobalSecondaryIndexNamesByPropertyName()
209 .keySet().contains(getHashKeyPropertyName())) {
210 hashKeyConditions = getHashKeyAttributeValue() == null
211 ? null
212 : Arrays.asList(createSingleValueCondition(getHashKeyPropertyName(), ComparisonOperator.EQ,
213 getHashKeyAttributeValue(), getHashKeyAttributeValue().getClass(), true));
214 if (hashKeyConditions == null) {
215 if (attributeConditions.containsKey(getHashKeyAttributeName())) {
216 hashKeyConditions = attributeConditions.get(getHashKeyAttributeName());
217 }
218
219 }
220
221 }
222 return hashKeyConditions;
223 }
224
225 public AbstractDynamoDBQueryCriteria(DynamoDBEntityInformation<T, ID> dynamoDBEntityInformation,
226 final DynamoDBMapperTableModel<T> tableModel) {
227 this.clazz = dynamoDBEntityInformation.getJavaType();
228 this.attributeConditions = new LinkedMultiValueMap<>();
229 this.propertyConditions = new LinkedMultiValueMap<>();
230 this.hashKeyPropertyName = dynamoDBEntityInformation.getHashKeyPropertyName();
231 this.entityInformation = dynamoDBEntityInformation;
232 this.attributeNamesByPropertyName = new HashMap<>();
233
234
235 this.tableModel = tableModel;
236 }
237
238 private String getFirstDeclaredIndexNameForAttribute(Map<String, String[]> indexNamesByAttributeName,
239 List<String> indexNamesToCheck, String attributeName) {
240 String indexName = null;
241 String[] declaredOrderedIndexNamesForAttribute = indexNamesByAttributeName.get(attributeName);
242 for (String declaredOrderedIndexNameForAttribute : declaredOrderedIndexNamesForAttribute) {
243 if (indexName == null && indexNamesToCheck.contains(declaredOrderedIndexNameForAttribute)) {
244 indexName = declaredOrderedIndexNameForAttribute;
245 }
246 }
247
248 return indexName;
249 }
250
251 protected String getGlobalSecondaryIndexName() {
252
253
254
255
256
257
258 if (globalSecondaryIndexName == null && attributeConditions != null && !attributeConditions.isEmpty()) {
259
260
261
262 Map<String, String[]> indexNamesByAttributeName = new HashMap<>();
263
264
265
266
267 MultiValueMap<String, String> attributeListsByIndexName = new LinkedMultiValueMap<>();
268
269
270 for (Entry<String, String[]> indexNamesForPropertyNameEntry : entityInformation
271 .getGlobalSecondaryIndexNamesByPropertyName().entrySet()) {
272 String propertyName = indexNamesForPropertyNameEntry.getKey();
273 String attributeName = getAttributeName(propertyName);
274 indexNamesByAttributeName.put(attributeName, indexNamesForPropertyNameEntry.getValue());
275 for (String indexNameForPropertyName : indexNamesForPropertyNameEntry.getValue()) {
276 attributeListsByIndexName.add(indexNameForPropertyName, attributeName);
277 }
278 }
279
280
281 List<String> exactMatchIndexNames = new ArrayList<>();
282 List<String> partialMatchIndexNames = new ArrayList<>();
283
284
285
286
287
288 for (Entry<String, List<String>> attributeListForIndexNameEntry : attributeListsByIndexName.entrySet()) {
289 String indexNameForAttributeList = attributeListForIndexNameEntry.getKey();
290 List<String> attributeList = attributeListForIndexNameEntry.getValue();
291 if (attributeList.containsAll(attributeConditions.keySet())) {
292 if (attributeConditions.keySet().containsAll(attributeList)) {
293 exactMatchIndexNames.add(indexNameForAttributeList);
294 } else {
295 partialMatchIndexNames.add(indexNameForAttributeList);
296 }
297 }
298 }
299
300 if (exactMatchIndexNames.size() > 1) {
301 throw new RuntimeException(
302 "Multiple indexes defined on same attribute set:" + attributeConditions.keySet());
303 } else if (exactMatchIndexNames.size() == 1) {
304 globalSecondaryIndexName = exactMatchIndexNames.get(0);
305 } else if (partialMatchIndexNames.size() > 1) {
306 if (attributeConditions.size() == 1) {
307 globalSecondaryIndexName = getFirstDeclaredIndexNameForAttribute(indexNamesByAttributeName,
308 partialMatchIndexNames, attributeConditions.keySet().iterator().next());
309 }
310 if (globalSecondaryIndexName == null) {
311 globalSecondaryIndexName = partialMatchIndexNames.get(0);
312 }
313 } else if (partialMatchIndexNames.size() == 1) {
314 globalSecondaryIndexName = partialMatchIndexNames.get(0);
315 }
316 }
317 return globalSecondaryIndexName;
318 }
319 protected boolean isHashKeyProperty(String propertyName) {
320 return hashKeyPropertyName.equals(propertyName);
321 }
322
323 protected String getHashKeyPropertyName() {
324 return hashKeyPropertyName;
325 }
326
327 protected String getHashKeyAttributeName() {
328 return getAttributeName(getHashKeyPropertyName());
329 }
330
331 protected boolean hasIndexHashKeyEqualCondition() {
332 boolean hasIndexHashKeyEqualCondition = false;
333 for (Map.Entry<String, List<Condition>> propertyConditionList : propertyConditions.entrySet()) {
334 if (entityInformation.isGlobalIndexHashKeyProperty(propertyConditionList.getKey())) {
335 for (Condition condition : propertyConditionList.getValue()) {
336 if (condition.getComparisonOperator().equals(ComparisonOperator.EQ.name())) {
337 hasIndexHashKeyEqualCondition = true;
338 }
339 }
340 }
341 }
342 if (hashKeyAttributeValue != null && entityInformation.isGlobalIndexHashKeyProperty(hashKeyPropertyName)) {
343 hasIndexHashKeyEqualCondition = true;
344 }
345 return hasIndexHashKeyEqualCondition;
346 }
347
348 protected boolean hasIndexRangeKeyCondition() {
349 boolean hasIndexRangeKeyCondition = false;
350 for (Map.Entry<String, List<Condition>> propertyConditionList : propertyConditions.entrySet()) {
351 if (entityInformation.isGlobalIndexRangeKeyProperty(propertyConditionList.getKey())) {
352 hasIndexRangeKeyCondition = true;
353 }
354 }
355 if (hashKeyAttributeValue != null && entityInformation.isGlobalIndexRangeKeyProperty(hashKeyPropertyName)) {
356 hasIndexRangeKeyCondition = true;
357 }
358 return hasIndexRangeKeyCondition;
359 }
360 protected boolean isApplicableForGlobalSecondaryIndex() {
361 boolean global = this.getGlobalSecondaryIndexName() != null;
362 if (global && getHashKeyAttributeValue() != null && !entityInformation
363 .getGlobalSecondaryIndexNamesByPropertyName().keySet().contains(getHashKeyPropertyName())) {
364 return false;
365 }
366
367 int attributeConditionCount = attributeConditions.keySet().size();
368 boolean attributeConditionsAppropriate = hasIndexHashKeyEqualCondition()
369 && (attributeConditionCount == 1 || (attributeConditionCount == 2 && hasIndexRangeKeyCondition()));
370 return global && (attributeConditionCount == 0 || attributeConditionsAppropriate)
371 && comparisonOperatorsPermittedForQuery();
372
373 }
374
375 public DynamoDBQueryCriteria<T, ID> withHashKeyEquals(Object value) {
376 Assert.notNull(value, "Creating conditions on null hash keys not supported: please specify a value for '"
377 + getHashKeyPropertyName() + "'");
378
379 hashKeyAttributeValue = getPropertyAttributeValue(getHashKeyPropertyName(), value);
380 hashKeyPropertyValue = value;
381 return this;
382 }
383
384 public boolean isHashKeySpecified() {
385 return getHashKeyAttributeValue() != null;
386 }
387
388 public Object getHashKeyAttributeValue() {
389 return hashKeyAttributeValue;
390 }
391
392 public Object getHashKeyPropertyValue() {
393 return hashKeyPropertyValue;
394 }
395
396 protected String getAttributeName(String propertyName) {
397 String attributeName = attributeNamesByPropertyName.get(propertyName);
398 if (attributeName == null) {
399 attributeName = entityInformation.getOverriddenAttributeName(propertyName).orElse(propertyName);
400 attributeNamesByPropertyName.put(propertyName, attributeName);
401 }
402 return attributeName;
403
404 }
405
406 @Override
407 public DynamoDBQueryCriteria<T, ID> withPropertyBetween(String propertyName, Object value1, Object value2,
408 Class<?> type) {
409 Condition condition = createCollectionCondition(propertyName, ComparisonOperator.BETWEEN,
410 Arrays.asList(value1, value2), type);
411 return withCondition(propertyName, condition);
412 }
413
414 @Override
415 public DynamoDBQueryCriteria<T, ID> withPropertyIn(String propertyName, Iterable<?> value, Class<?> propertyType) {
416
417 Condition condition = createCollectionCondition(propertyName, ComparisonOperator.IN, value, propertyType);
418 return withCondition(propertyName, condition);
419 }
420
421 @Override
422 public DynamoDBQueryCriteria<T, ID> withSingleValueCriteria(String propertyName,
423 ComparisonOperator comparisonOperator, Object value, Class<?> propertyType) {
424 if (comparisonOperator.equals(ComparisonOperator.EQ)) {
425 return withPropertyEquals(propertyName, value, propertyType);
426 } else {
427 Condition condition = createSingleValueCondition(propertyName, comparisonOperator, value, propertyType,
428 false);
429 return withCondition(propertyName, condition);
430 }
431 }
432
433 @Override
434 public Query<T> buildQuery(DynamoDBOperations dynamoDBOperations) {
435 if (isApplicableForLoad()) {
436 return buildSingleEntityLoadQuery(dynamoDBOperations);
437 } else {
438 return buildFinderQuery(dynamoDBOperations);
439 }
440 }
441
442 @Override
443 public Query<Long> buildCountQuery(DynamoDBOperations dynamoDBOperations, boolean pageQuery) {
444 if (isApplicableForLoad()) {
445 return buildSingleEntityCountQuery(dynamoDBOperations);
446 } else {
447 return buildFinderCountQuery(dynamoDBOperations, pageQuery);
448 }
449 }
450
451 protected abstract Query<T> buildSingleEntityLoadQuery(DynamoDBOperations dynamoDBOperations);
452
453 protected abstract Query<Long> buildSingleEntityCountQuery(DynamoDBOperations dynamoDBOperations);
454
455 protected abstract Query<T> buildFinderQuery(DynamoDBOperations dynamoDBOperations);
456
457 protected abstract Query<Long> buildFinderCountQuery(DynamoDBOperations dynamoDBOperations, boolean pageQuery);
458
459 protected abstract boolean isOnlyHashKeySpecified();
460
461 @Override
462 public DynamoDBQueryCriteria<T, ID> withNoValuedCriteria(String propertyName,
463 ComparisonOperator comparisonOperator) {
464 Condition condition = createNoValueCondition(propertyName, comparisonOperator);
465 return withCondition(propertyName, condition);
466
467 }
468
469 public DynamoDBQueryCriteria<T, ID> withCondition(String propertyName, Condition condition) {
470 attributeConditions.add(getAttributeName(propertyName), condition);
471 propertyConditions.add(propertyName, condition);
472
473 return this;
474 }
475
476 @SuppressWarnings({"deprecation", "unchecked"})
477 protected <V extends Object> Object getPropertyAttributeValue(final String propertyName, final V value) {
478
479
480 DynamoDBTypeConverter<Object, V> converter = (DynamoDBTypeConverter<Object, V>) entityInformation
481 .getTypeConverterForProperty(propertyName);
482
483 if (converter != null) {
484 return converter.convert(value);
485 }
486
487 DynamoDBMarshaller<V> marshaller = (DynamoDBMarshaller<V>) entityInformation
488 .getMarshallerForProperty(propertyName);
489
490 if (marshaller != null) {
491 return marshaller.marshall(value);
492 } else if (tableModel != null) {
493
494
495 String attributeName = getAttributeName(propertyName);
496
497 DynamoDBMapperFieldModel<T, Object> fieldModel = tableModel.field(attributeName);
498 if (fieldModel != null) {
499 return fieldModel.convert(value);
500 }
501 }
502
503 return value;
504 }
505
506 protected <V> Condition createNoValueCondition(String propertyName, ComparisonOperator comparisonOperator) {
507
508 Condition condition = new Condition().withComparisonOperator(comparisonOperator);
509
510 return condition;
511 }
512
513 private List<String> getNumberListAsStringList(List<Number> numberList) {
514 List<String> list = new ArrayList<>();
515 for (Number number : numberList) {
516 if (number != null) {
517 list.add(number.toString());
518 } else {
519 list.add(null);
520 }
521 }
522 return list;
523 }
524
525 @SuppressWarnings("deprecation")
526 private List<String> getDateListAsStringList(List<Date> dateList) {
527 DynamoDBMarshaller<Date> marshaller = new Date2IsoDynamoDBMarshaller();
528 List<String> list = new ArrayList<String>();
529 for (Date date : dateList) {
530 if (date != null) {
531 list.add(marshaller.marshall(date));
532 } else {
533 list.add(null);
534 }
535 }
536 return list;
537 }
538
539 @SuppressWarnings("deprecation")
540 private List<String> getInstantListAsStringList(List<Instant> dateList) {
541 DynamoDBMarshaller<Instant> marshaller = new Instant2IsoDynamoDBMarshaller();
542 List<String> list = new ArrayList<>();
543 for (Instant date : dateList) {
544 if (date != null) {
545 list.add(marshaller.marshall(date));
546 } else {
547 list.add(null);
548 }
549 }
550 return list;
551 }
552
553 private List<String> getBooleanListAsStringList(List<Boolean> booleanList) {
554 List<String> list = new ArrayList<>();
555 for (Boolean booleanValue : booleanList) {
556 if (booleanValue != null) {
557 list.add(booleanValue.booleanValue() ? "1" : "0");
558 } else {
559 list.add(null);
560 }
561 }
562 return list;
563 }
564
565 @SuppressWarnings("unchecked")
566 @Nullable
567 private <P> List<P> getAttributeValueAsList(@Nullable Object attributeValue) {
568 if (attributeValue == null) {
569 return null;
570 }
571 boolean isIterable = ClassUtils.isAssignable(Iterable.class, attributeValue.getClass());
572 if (isIterable) {
573 List<P> attributeValueAsList = new ArrayList<>();
574 Iterable<P> iterable = (Iterable<P>) attributeValue;
575 for (P attributeValueElement : iterable) {
576 attributeValueAsList.add(attributeValueElement);
577 }
578 return attributeValueAsList;
579 }
580 return null;
581 }
582
583 protected <P> List<AttributeValue> addAttributeValue(List<AttributeValue> attributeValueList,
584 @Nullable Object attributeValue, Class<P> propertyType, boolean expandCollectionValues) {
585 AttributeValue attributeValueObject = new AttributeValue();
586
587 if (ClassUtils.isAssignable(String.class, propertyType)) {
588 List<String> attributeValueAsList = getAttributeValueAsList(attributeValue);
589 if (expandCollectionValues && attributeValueAsList != null) {
590 attributeValueObject.withSS(attributeValueAsList);
591 } else {
592 attributeValueObject.withS((String) attributeValue);
593 }
594 } else if (ClassUtils.isAssignable(Number.class, propertyType)) {
595
596 List<Number> attributeValueAsList = getAttributeValueAsList(attributeValue);
597 if (expandCollectionValues && attributeValueAsList != null) {
598 List<String> attributeValueAsStringList = getNumberListAsStringList(attributeValueAsList);
599 attributeValueObject.withNS(attributeValueAsStringList);
600 } else {
601 attributeValueObject.withN(attributeValue.toString());
602 }
603 } else if (ClassUtils.isAssignable(Boolean.class, propertyType)) {
604 List<Boolean> attributeValueAsList = getAttributeValueAsList(attributeValue);
605 if (expandCollectionValues && attributeValueAsList != null) {
606 List<String> attributeValueAsStringList = getBooleanListAsStringList(attributeValueAsList);
607 attributeValueObject.withNS(attributeValueAsStringList);
608 } else {
609 boolean boolValue = ((Boolean) attributeValue).booleanValue();
610 attributeValueObject.withN(boolValue ? "1" : "0");
611 }
612 } else if (ClassUtils.isAssignable(Date.class, propertyType)) {
613 List<Date> attributeValueAsList = getAttributeValueAsList(attributeValue);
614 if (expandCollectionValues && attributeValueAsList != null) {
615 List<String> attributeValueAsStringList = getDateListAsStringList(attributeValueAsList);
616 attributeValueObject.withSS(attributeValueAsStringList);
617 } else {
618 Date date = (Date) attributeValue;
619 String marshalledDate = new Date2IsoDynamoDBMarshaller().marshall(date);
620 attributeValueObject.withS(marshalledDate);
621 }
622 } else if (ClassUtils.isAssignable(Instant.class, propertyType)) {
623 List<Instant> attributeValueAsList = getAttributeValueAsList(attributeValue);
624 if (expandCollectionValues && attributeValueAsList != null) {
625 List<String> attributeValueAsStringList = getInstantListAsStringList(attributeValueAsList);
626 attributeValueObject.withSS(attributeValueAsStringList);
627 } else {
628 Instant date = (Instant) attributeValue;
629 String marshalledDate = new Instant2IsoDynamoDBMarshaller().marshall(date);
630 attributeValueObject.withS(marshalledDate);
631 }
632 } else {
633 throw new RuntimeException("Cannot create condition for type:" + attributeValue.getClass()
634 + " property conditions must be String,Number or Boolean, or have a DynamoDBMarshaller configured");
635 }
636 attributeValueList.add(attributeValueObject);
637
638 return attributeValueList;
639 }
640
641 protected Condition createSingleValueCondition(String propertyName, ComparisonOperator comparisonOperator, Object o,
642 Class<?> propertyType, boolean alreadyMarshalledIfRequired) {
643
644 Assert.notNull(o, "Creating conditions on null property values not supported: please specify a value for '"
645 + propertyName + "'");
646
647 List<AttributeValue> attributeValueList = new ArrayList<>(1);
648 Object attributeValue = !alreadyMarshalledIfRequired ? getPropertyAttributeValue(propertyName, o) : o;
649 if (ClassUtils.isAssignableValue(AttributeValue.class, attributeValue)) {
650 attributeValueList.add((AttributeValue) attributeValue);
651 } else {
652 boolean marshalled = !alreadyMarshalledIfRequired && attributeValue != o
653 && !entityInformation.isCompositeHashAndRangeKeyProperty(propertyName);
654
655 Class<?> targetPropertyType = marshalled ? String.class : propertyType;
656 addAttributeValue(attributeValueList, attributeValue, targetPropertyType, true);
657 }
658
659 return new Condition().withComparisonOperator(comparisonOperator).withAttributeValueList(attributeValueList);
660 }
661
662 protected Condition createCollectionCondition(String propertyName, ComparisonOperator comparisonOperator,
663 Iterable<?> o, Class<?> propertyType) {
664
665 Assert.notNull(o, "Creating conditions on null property values not supported: please specify a value for '"
666 + propertyName + "'");
667 List<AttributeValue> attributeValueList = new ArrayList<>();
668 boolean marshalled = false;
669 for (Object object : o) {
670 Object attributeValue = getPropertyAttributeValue(propertyName, object);
671 if (ClassUtils.isAssignableValue(AttributeValue.class, attributeValue)) {
672 attributeValueList.add((AttributeValue) attributeValue);
673 } else {
674 if (attributeValue != null) {
675 marshalled = attributeValue != object
676 && !entityInformation.isCompositeHashAndRangeKeyProperty(propertyName);
677 }
678 Class<?> targetPropertyType = marshalled ? String.class : propertyType;
679 addAttributeValue(attributeValueList, attributeValue, targetPropertyType, false);
680 }
681 }
682
683 return new Condition().withComparisonOperator(comparisonOperator).withAttributeValueList(attributeValueList);
684
685 }
686
687 @Override
688 public DynamoDBQueryCriteria<T, ID> withSort(Sort sort) {
689 this.sort = sort;
690 return this;
691 }
692
693 @Override
694 public DynamoDBQueryCriteria<T, ID> withProjection(Optional<String> projection) {
695 this.projection = projection;
696 return this;
697 }
698 }