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.DynamoDBMapperTableModel;
19 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
20 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
21 import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
22 import com.amazonaws.services.dynamodbv2.model.Condition;
23 import com.amazonaws.services.dynamodbv2.model.QueryRequest;
24 import com.amazonaws.services.dynamodbv2.model.Select;
25 import org.socialsignin.spring.data.dynamodb.core.DynamoDBOperations;
26 import org.socialsignin.spring.data.dynamodb.query.CountByHashAndRangeKeyQuery;
27 import org.socialsignin.spring.data.dynamodb.query.MultipleEntityQueryExpressionQuery;
28 import org.socialsignin.spring.data.dynamodb.query.MultipleEntityQueryRequestQuery;
29 import org.socialsignin.spring.data.dynamodb.query.MultipleEntityScanExpressionQuery;
30 import org.socialsignin.spring.data.dynamodb.query.Query;
31 import org.socialsignin.spring.data.dynamodb.query.QueryExpressionCountQuery;
32 import org.socialsignin.spring.data.dynamodb.query.QueryRequestCountQuery;
33 import org.socialsignin.spring.data.dynamodb.query.ScanExpressionCountQuery;
34 import org.socialsignin.spring.data.dynamodb.query.SingleEntityLoadByHashAndRangeKeyQuery;
35 import org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBIdIsHashAndRangeKeyEntityInformation;
36 import org.springframework.util.Assert;
37
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Map.Entry;
45 import java.util.Set;
46
47
48
49
50
51 public class DynamoDBEntityWithHashAndRangeKeyCriteria<T, ID> extends AbstractDynamoDBQueryCriteria<T, ID> {
52
53 private Object rangeKeyAttributeValue;
54 private Object rangeKeyPropertyValue;
55 private String rangeKeyPropertyName;
56 private Set<String> indexRangeKeyPropertyNames;
57 private DynamoDBIdIsHashAndRangeKeyEntityInformation<T, ID> entityInformation;
58
59 protected String getRangeKeyAttributeName() {
60 return getAttributeName(getRangeKeyPropertyName());
61 }
62
63 protected String getRangeKeyPropertyName() {
64 return rangeKeyPropertyName;
65 }
66
67 protected boolean isRangeKeyProperty(String propertyName) {
68 return rangeKeyPropertyName.equals(propertyName);
69 }
70
71 public DynamoDBEntityWithHashAndRangeKeyCriteria(
72 DynamoDBIdIsHashAndRangeKeyEntityInformation<T, ID> entityInformation,
73 DynamoDBMapperTableModel<T> tableModel) {
74
75 super(entityInformation, tableModel);
76 this.rangeKeyPropertyName = entityInformation.getRangeKeyPropertyName();
77 this.indexRangeKeyPropertyNames = entityInformation.getIndexRangeKeyPropertyNames();
78 if (indexRangeKeyPropertyNames == null) {
79 indexRangeKeyPropertyNames = new HashSet<>();
80 }
81 this.entityInformation = entityInformation;
82 }
83
84 public Set<String> getIndexRangeKeyAttributeNames() {
85 Set<String> indexRangeKeyAttributeNames = new HashSet<>();
86 for (String indexRangeKeyPropertyName : indexRangeKeyPropertyNames) {
87 indexRangeKeyAttributeNames.add(getAttributeName(indexRangeKeyPropertyName));
88 }
89 return indexRangeKeyAttributeNames;
90 }
91
92 protected Object getRangeKeyAttributeValue() {
93 return rangeKeyAttributeValue;
94 }
95
96 protected Object getRangeKeyPropertyValue() {
97 return rangeKeyPropertyValue;
98 }
99
100 protected boolean isRangeKeySpecified() {
101 return getRangeKeyAttributeValue() != null;
102 }
103
104 protected Query<T> buildSingleEntityLoadQuery(DynamoDBOperations dynamoDBOperations) {
105 return new SingleEntityLoadByHashAndRangeKeyQuery<>(dynamoDBOperations, entityInformation.getJavaType(),
106 getHashKeyPropertyValue(), getRangeKeyPropertyValue());
107 }
108
109 protected Query<Long> buildSingleEntityCountQuery(DynamoDBOperations dynamoDBOperations) {
110 return new CountByHashAndRangeKeyQuery<>(dynamoDBOperations, entityInformation.getJavaType(),
111 getHashKeyPropertyValue(), getRangeKeyPropertyValue());
112 }
113
114 private void checkComparisonOperatorPermittedForCompositeHashAndRangeKey(ComparisonOperator comparisonOperator) {
115
116 if (!ComparisonOperator.EQ.equals(comparisonOperator) && !ComparisonOperator.CONTAINS.equals(comparisonOperator)
117 && !ComparisonOperator.BEGINS_WITH.equals(comparisonOperator)) {
118 throw new UnsupportedOperationException(
119 "Only EQ,CONTAINS,BEGINS_WITH supported for composite id comparison");
120 }
121
122 }
123
124 @SuppressWarnings("unchecked")
125 @Override
126 public DynamoDBQueryCriteria<T, ID> withSingleValueCriteria(String propertyName,
127 ComparisonOperator comparisonOperator, Object value, Class<?> propertyType) {
128
129 if (entityInformation.isCompositeHashAndRangeKeyProperty(propertyName)) {
130 checkComparisonOperatorPermittedForCompositeHashAndRangeKey(comparisonOperator);
131 Object hashKey = entityInformation.getHashKey((ID) value);
132 Object rangeKey = entityInformation.getRangeKey((ID) value);
133 if (hashKey != null) {
134 withSingleValueCriteria(getHashKeyPropertyName(), comparisonOperator, hashKey, hashKey.getClass());
135 }
136 if (rangeKey != null) {
137 withSingleValueCriteria(getRangeKeyPropertyName(), comparisonOperator, rangeKey, rangeKey.getClass());
138 }
139 return this;
140 } else {
141 return super.withSingleValueCriteria(propertyName, comparisonOperator, value, propertyType);
142 }
143 }
144
145 public DynamoDBQueryExpression<T> buildQueryExpression() {
146 DynamoDBQueryExpression<T> queryExpression = new DynamoDBQueryExpression<T>();
147 if (isHashKeySpecified()) {
148 T hashKeyPrototype = entityInformation.getHashKeyPropotypeEntityForHashKey(getHashKeyPropertyValue());
149 queryExpression.withHashKeyValues(hashKeyPrototype);
150 queryExpression.withRangeKeyConditions(new HashMap<String, Condition>());
151 }
152
153 if (isRangeKeySpecified() && !isApplicableForGlobalSecondaryIndex()) {
154 Condition rangeKeyCondition = createSingleValueCondition(getRangeKeyPropertyName(), ComparisonOperator.EQ,
155 getRangeKeyAttributeValue(), getRangeKeyAttributeValue().getClass(), true);
156 queryExpression.withRangeKeyCondition(getRangeKeyAttributeName(), rangeKeyCondition);
157 applySortIfSpecified(queryExpression, Arrays.asList(new String[]{getRangeKeyPropertyName()}));
158
159 } else if (isOnlyASingleAttributeConditionAndItIsOnEitherRangeOrIndexRangeKey()
160 || (isApplicableForGlobalSecondaryIndex())) {
161
162 Entry<String, List<Condition>> singlePropertyConditions = propertyConditions.entrySet().iterator().next();
163
164 List<String> allowedSortProperties = new ArrayList<>();
165 for (Entry<String, List<Condition>> singlePropertyCondition : propertyConditions.entrySet()) {
166 if (entityInformation.getGlobalSecondaryIndexNamesByPropertyName().keySet()
167 .contains(singlePropertyCondition.getKey())) {
168 allowedSortProperties.add(singlePropertyCondition.getKey());
169 }
170 }
171 if (allowedSortProperties.size() == 0) {
172 allowedSortProperties.add(singlePropertyConditions.getKey());
173 }
174
175 for (Entry<String, List<Condition>> singleAttributeConditions : attributeConditions.entrySet()) {
176 for (Condition condition : singleAttributeConditions.getValue()) {
177 queryExpression.withRangeKeyCondition(singleAttributeConditions.getKey(), condition);
178 }
179 }
180
181 applySortIfSpecified(queryExpression, allowedSortProperties);
182 if (getGlobalSecondaryIndexName() != null) {
183 queryExpression.setIndexName(getGlobalSecondaryIndexName());
184 }
185 } else {
186 applySortIfSpecified(queryExpression, Arrays.asList(new String[]{getRangeKeyPropertyName()}));
187 }
188
189 if (projection.isPresent()) {
190 queryExpression.setSelect(Select.SPECIFIC_ATTRIBUTES);
191 queryExpression.setProjectionExpression(projection.get());
192 }
193
194 return queryExpression;
195 }
196
197 protected List<Condition> getRangeKeyConditions() {
198 List<Condition> rangeKeyConditions = null;
199 if (isApplicableForGlobalSecondaryIndex() && entityInformation.getGlobalSecondaryIndexNamesByPropertyName()
200 .keySet().contains(getRangeKeyPropertyName())) {
201 rangeKeyConditions = getRangeKeyAttributeValue() == null
202 ? null
203 : Arrays.asList(createSingleValueCondition(getRangeKeyPropertyName(), ComparisonOperator.EQ,
204 getRangeKeyAttributeValue(), getRangeKeyAttributeValue().getClass(), true));
205
206 }
207 return rangeKeyConditions;
208 }
209
210 protected Query<T> buildFinderQuery(DynamoDBOperations dynamoDBOperations) {
211 if (isApplicableForQuery()) {
212 if (isApplicableForGlobalSecondaryIndex()) {
213 String tableName = dynamoDBOperations.getOverriddenTableName(clazz,
214 entityInformation.getDynamoDBTableName());
215 QueryRequest queryRequest = buildQueryRequest(tableName, getGlobalSecondaryIndexName(),
216 getHashKeyAttributeName(), getRangeKeyAttributeName(), this.getRangeKeyPropertyName(),
217 getHashKeyConditions(), getRangeKeyConditions());
218 return new MultipleEntityQueryRequestQuery<>(dynamoDBOperations, entityInformation.getJavaType(),
219 queryRequest);
220 } else {
221 DynamoDBQueryExpression<T> queryExpression = buildQueryExpression();
222 return new MultipleEntityQueryExpressionQuery<>(dynamoDBOperations, entityInformation.getJavaType(),
223 queryExpression);
224 }
225 } else {
226 return new MultipleEntityScanExpressionQuery<>(dynamoDBOperations, clazz, buildScanExpression());
227 }
228 }
229
230 protected Query<Long> buildFinderCountQuery(DynamoDBOperations dynamoDBOperations, boolean pageQuery) {
231 if (isApplicableForQuery()) {
232 if (isApplicableForGlobalSecondaryIndex()) {
233 String tableName = dynamoDBOperations.getOverriddenTableName(clazz,
234 entityInformation.getDynamoDBTableName());
235 QueryRequest queryRequest = buildQueryRequest(tableName, getGlobalSecondaryIndexName(),
236 getHashKeyAttributeName(), getRangeKeyAttributeName(), this.getRangeKeyPropertyName(),
237 getHashKeyConditions(), getRangeKeyConditions());
238 return new QueryRequestCountQuery(dynamoDBOperations, queryRequest);
239
240 } else {
241 DynamoDBQueryExpression<T> queryExpression = buildQueryExpression();
242 return new QueryExpressionCountQuery<>(dynamoDBOperations, entityInformation.getJavaType(),
243 queryExpression);
244
245 }
246 } else {
247 return new ScanExpressionCountQuery<T>(dynamoDBOperations, clazz, buildScanExpression(), pageQuery);
248 }
249 }
250
251 @Override
252 public boolean isApplicableForLoad() {
253 return attributeConditions.size() == 0 && isHashAndRangeKeySpecified();
254 }
255
256 protected boolean isHashAndRangeKeySpecified() {
257 return isHashKeySpecified() && isRangeKeySpecified();
258 }
259
260 protected boolean isOnlyASingleAttributeConditionAndItIsOnEitherRangeOrIndexRangeKey() {
261 boolean isOnlyASingleAttributeConditionAndItIsOnEitherRangeOrIndexRangeKey = false;
262 if (!isRangeKeySpecified() && attributeConditions.size() == 1) {
263 Entry<String, List<Condition>> conditionsEntry = attributeConditions.entrySet().iterator().next();
264 if (conditionsEntry.getKey().equals(getRangeKeyAttributeName())
265 || getIndexRangeKeyAttributeNames().contains(conditionsEntry.getKey())) {
266 if (conditionsEntry.getValue().size() == 1) {
267 isOnlyASingleAttributeConditionAndItIsOnEitherRangeOrIndexRangeKey = true;
268 }
269 }
270 }
271 return isOnlyASingleAttributeConditionAndItIsOnEitherRangeOrIndexRangeKey;
272
273 }
274
275 @Override
276 protected boolean hasIndexHashKeyEqualCondition() {
277
278 boolean hasCondition = super.hasIndexHashKeyEqualCondition();
279 if (!hasCondition) {
280 if (rangeKeyAttributeValue != null
281 && entityInformation.isGlobalIndexHashKeyProperty(rangeKeyPropertyName)) {
282 hasCondition = true;
283 }
284 }
285 return hasCondition;
286 }
287
288 @Override
289 protected boolean hasIndexRangeKeyCondition() {
290 boolean hasCondition = super.hasIndexRangeKeyCondition();
291 if (!hasCondition) {
292 if (rangeKeyAttributeValue != null
293 && entityInformation.isGlobalIndexRangeKeyProperty(rangeKeyPropertyName)) {
294 hasCondition = true;
295 }
296 }
297 return hasCondition;
298 }
299
300 protected boolean isApplicableForGlobalSecondaryIndex() {
301 boolean global = super.isApplicableForGlobalSecondaryIndex();
302 if (global && getRangeKeyAttributeValue() != null && !entityInformation
303 .getGlobalSecondaryIndexNamesByPropertyName().keySet().contains(getRangeKeyPropertyName())) {
304 return false;
305 }
306
307 return global;
308
309 }
310
311 protected String getGlobalSecondaryIndexName() {
312
313
314 String globalSecondaryIndexName = super.getGlobalSecondaryIndexName();
315
316
317
318
319
320
321 if (globalSecondaryIndexName == null) {
322 if (this.hashKeyAttributeValue == null && getRangeKeyAttributeValue() != null) {
323 String[] rangeKeyIndexNames = entityInformation.getGlobalSecondaryIndexNamesByPropertyName()
324 .get(this.getRangeKeyPropertyName());
325 globalSecondaryIndexName = rangeKeyIndexNames != null && rangeKeyIndexNames.length > 0
326 ? rangeKeyIndexNames[0]
327 : null;
328 }
329 }
330 return globalSecondaryIndexName;
331 }
332
333 public boolean isApplicableForQuery() {
334
335 return isOnlyHashKeySpecified()
336 || (isHashKeySpecified() && isOnlyASingleAttributeConditionAndItIsOnEitherRangeOrIndexRangeKey()
337 && comparisonOperatorsPermittedForQuery())
338 || isApplicableForGlobalSecondaryIndex();
339
340 }
341
342 public DynamoDBScanExpression buildScanExpression() {
343
344 ensureNoSort(sort);
345
346 DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
347 if (isHashKeySpecified()) {
348 scanExpression.addFilterCondition(getHashKeyAttributeName(),
349 createSingleValueCondition(getHashKeyPropertyName(), ComparisonOperator.EQ,
350 getHashKeyAttributeValue(), getHashKeyAttributeValue().getClass(), true));
351 }
352 if (isRangeKeySpecified()) {
353 scanExpression.addFilterCondition(getRangeKeyAttributeName(),
354 createSingleValueCondition(getRangeKeyPropertyName(), ComparisonOperator.EQ,
355 getRangeKeyAttributeValue(), getRangeKeyAttributeValue().getClass(), true));
356 }
357 for (Map.Entry<String, List<Condition>> conditionEntry : attributeConditions.entrySet()) {
358 for (Condition condition : conditionEntry.getValue()) {
359 scanExpression.addFilterCondition(conditionEntry.getKey(), condition);
360 }
361 }
362 return scanExpression;
363 }
364
365 public DynamoDBQueryCriteria<T, ID> withRangeKeyEquals(Object value) {
366 Assert.notNull(value, "Creating conditions on null range keys not supported: please specify a value for '"
367 + getRangeKeyPropertyName() + "'");
368
369 rangeKeyAttributeValue = getPropertyAttributeValue(getRangeKeyPropertyName(), value);
370 rangeKeyPropertyValue = value;
371 return this;
372 }
373
374 @SuppressWarnings("unchecked")
375 @Override
376 public DynamoDBQueryCriteria<T, ID> withPropertyEquals(String propertyName, Object value, Class<?> propertyType) {
377 if (isHashKeyProperty(propertyName)) {
378 return withHashKeyEquals(value);
379 } else if (isRangeKeyProperty(propertyName)) {
380 return withRangeKeyEquals(value);
381 } else if (entityInformation.isCompositeHashAndRangeKeyProperty(propertyName)) {
382 Assert.notNull(value,
383 "Creating conditions on null composite id properties not supported: please specify a value for '"
384 + propertyName + "'");
385 Object hashKey = entityInformation.getHashKey((ID) value);
386 Object rangeKey = entityInformation.getRangeKey((ID) value);
387 if (hashKey != null) {
388 withHashKeyEquals(hashKey);
389 }
390 if (rangeKey != null) {
391 withRangeKeyEquals(rangeKey);
392 }
393 return this;
394 } else {
395 Condition condition = createSingleValueCondition(propertyName, ComparisonOperator.EQ, value, propertyType,
396 false);
397 return withCondition(propertyName, condition);
398 }
399
400 }
401
402 @Override
403 protected boolean isOnlyHashKeySpecified() {
404 return isHashKeySpecified() && attributeConditions.size() == 0 && !isRangeKeySpecified();
405 }
406
407 }