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.DynamoDBMapper;
19 import org.socialsignin.spring.data.dynamodb.core.DynamoDBOperations;
20 import org.socialsignin.spring.data.dynamodb.domain.UnpagedPageImpl;
21 import org.socialsignin.spring.data.dynamodb.exception.BatchDeleteException;
22 import org.socialsignin.spring.data.dynamodb.query.Query;
23 import org.socialsignin.spring.data.dynamodb.utils.ExceptionHandler;
24 import org.springframework.data.domain.Page;
25 import org.springframework.data.domain.PageImpl;
26 import org.springframework.data.domain.Pageable;
27 import org.springframework.data.domain.Slice;
28 import org.springframework.data.domain.SliceImpl;
29 import org.springframework.data.repository.query.ParameterAccessor;
30 import org.springframework.data.repository.query.Parameters;
31 import org.springframework.data.repository.query.ParametersParameterAccessor;
32 import org.springframework.data.repository.query.RepositoryQuery;
33
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Iterator;
37 import java.util.List;
38
39
40
41
42
43 public abstract class AbstractDynamoDBQuery<T, ID> implements RepositoryQuery, ExceptionHandler {
44
45 protected final DynamoDBOperations dynamoDBOperations;
46 private final DynamoDBQueryMethod<T, ID> method;
47
48 public AbstractDynamoDBQuery(DynamoDBOperations dynamoDBOperations, DynamoDBQueryMethod<T, ID> method) {
49 this.dynamoDBOperations = dynamoDBOperations;
50 this.method = method;
51 }
52
53 protected QueryExecution<T, ID> getExecution() {
54 if (method.isCollectionQuery() && !isSingleEntityResultsRestriction()) {
55 return new CollectionExecution();
56 } else if (method.isSliceQuery() && !isSingleEntityResultsRestriction()) {
57 return new SlicedExecution(method.getParameters());
58 } else if (method.isPageQuery() && !isSingleEntityResultsRestriction()) {
59 return new PagedExecution(method.getParameters());
60 } else if (method.isModifyingQuery()) {
61 throw new UnsupportedOperationException("Modifying queries not yet supported");
62 } else if (isSingleEntityResultsRestriction()) {
63 return new SingleEntityLimitedExecution();
64 } else if (isDeleteQuery()) {
65 return new DeleteExecution();
66 } else {
67 return new SingleEntityExecution();
68 }
69 }
70
71 protected abstract Query<T> doCreateQuery(Object[] values);
72 protected abstract Query<Long> doCreateCountQuery(Object[] values, boolean pageQuery);
73 protected abstract boolean isCountQuery();
74 protected abstract boolean isExistsQuery();
75 protected abstract boolean isDeleteQuery();
76
77 protected abstract Integer getResultsRestrictionIfApplicable();
78 protected abstract boolean isSingleEntityResultsRestriction();
79
80 protected Query<T> doCreateQueryWithPermissions(Object values[]) {
81 Query<T> query = doCreateQuery(values);
82 query.setScanEnabled(method.isScanEnabled());
83 return query;
84 }
85
86 protected Query<Long> doCreateCountQueryWithPermissions(Object values[], boolean pageQuery) {
87 Query<Long> query = doCreateCountQuery(values, pageQuery);
88 query.setScanCountEnabled(method.isScanCountEnabled());
89 return query;
90 }
91
92 private interface QueryExecution<T, ID> {
93 Object execute(AbstractDynamoDBQuery<T, ID> query, Object[] values);
94 }
95
96 class CollectionExecution implements QueryExecution<T, ID> {
97
98 @Override
99 public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
100 Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
101 if (getResultsRestrictionIfApplicable() != null) {
102 return restrictMaxResultsIfNecessary(query.getResultList().iterator());
103 } else
104 return query.getResultList();
105 }
106
107 private List<T> restrictMaxResultsIfNecessary(Iterator<T> iterator) {
108 int processed = 0;
109 List<T> resultsPage = new ArrayList<>();
110 while (iterator.hasNext() && processed < getResultsRestrictionIfApplicable()) {
111 resultsPage.add(iterator.next());
112 processed++;
113 }
114 return resultsPage;
115 }
116
117 }
118
119
120
121
122
123 class PagedExecution implements QueryExecution<T, ID> {
124
125 private final Parameters<?, ?> parameters;
126
127 public PagedExecution(Parameters<?, ?> parameters) {
128
129 this.parameters = parameters;
130 }
131
132 private long scanThroughResults(Iterator<T> iterator, long resultsToScan) {
133 long processed = 0;
134 while (iterator.hasNext() && processed < resultsToScan) {
135 iterator.next();
136 processed++;
137 }
138 return processed;
139 }
140
141 private List<T> readPageOfResultsRestrictMaxResultsIfNecessary(Iterator<T> iterator, int pageSize) {
142 int processed = 0;
143 int toProcess = getResultsRestrictionIfApplicable() != null
144 ? Math.min(pageSize, getResultsRestrictionIfApplicable())
145 : pageSize;
146 List<T> resultsPage = new ArrayList<>();
147 while (iterator.hasNext() && processed < toProcess) {
148 resultsPage.add(iterator.next());
149 processed++;
150 }
151 return resultsPage;
152 }
153
154 @Override
155 public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
156
157 ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
158 Pageable pageable = accessor.getPageable();
159 Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
160
161 List<T> results = query.getResultList();
162 return createPage(results, pageable, dynamoDBQuery, values);
163 }
164
165 private Page<T> createPage(List<T> allResults, Pageable pageable, AbstractDynamoDBQuery<T, ID> dynamoDBQuery,
166 Object[] values) {
167
168
169 Iterator<T> iterator = allResults.iterator();
170
171
172 if (!pageable.isUnpaged() && pageable.getOffset() > 0) {
173 long processedCount = scanThroughResults(iterator, pageable.getOffset());
174 if (processedCount < pageable.getOffset()) {
175 return new PageImpl<>(Collections.emptyList());
176 }
177 }
178
179
180 Query<Long> countQuery = dynamoDBQuery.doCreateCountQueryWithPermissions(values, true);
181 long count = countQuery.getSingleResult();
182
183
184 if (!pageable.isUnpaged()) {
185
186 if (getResultsRestrictionIfApplicable() != null) {
187 count = Math.min(count, getResultsRestrictionIfApplicable());
188 }
189
190 List<T> results = readPageOfResultsRestrictMaxResultsIfNecessary(iterator, pageable.getPageSize());
191 return new PageImpl<>(results, pageable, count);
192 } else {
193
194 return new UnpagedPageImpl<>(allResults, count);
195 }
196 }
197 }
198
199 class SlicedExecution implements QueryExecution<T, ID> {
200
201 private final Parameters<?, ?> parameters;
202
203 public SlicedExecution(Parameters<?, ?> parameters) {
204
205 this.parameters = parameters;
206 }
207
208 private long scanThroughResults(Iterator<T> iterator, long resultsToScan) {
209 long processed = 0;
210 while (iterator.hasNext() && processed < resultsToScan) {
211 iterator.next();
212 processed++;
213 }
214 return processed;
215 }
216
217 private List<T> readPageOfResultsRestrictMaxResultsIfNecessary(Iterator<T> iterator, int pageSize) {
218 int processed = 0;
219 int toProcess = getResultsRestrictionIfApplicable() != null
220 ? Math.min(pageSize, getResultsRestrictionIfApplicable())
221 : pageSize;
222
223 List<T> resultsPage = new ArrayList<>();
224 while (iterator.hasNext() && processed < toProcess) {
225 resultsPage.add(iterator.next());
226 processed++;
227 }
228 return resultsPage;
229 }
230
231 @Override
232 public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
233
234 ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
235 Pageable pageable = accessor.getPageable();
236 Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
237 List<T> results = query.getResultList();
238 return createSlice(results, pageable);
239 }
240
241 private Slice<T> createSlice(List<T> allResults, Pageable pageable) {
242
243 Iterator<T> iterator = allResults.iterator();
244 if (pageable.getOffset() > 0) {
245 long processedCount = scanThroughResults(iterator, pageable.getOffset());
246 if (processedCount < pageable.getOffset())
247 return new SliceImpl<>(new ArrayList<T>());
248 }
249 List<T> results = readPageOfResultsRestrictMaxResultsIfNecessary(iterator, pageable.getPageSize());
250
251 boolean hasMoreResults = scanThroughResults(iterator, 1) > 0;
252 if (getResultsRestrictionIfApplicable() != null
253 && getResultsRestrictionIfApplicable().intValue() <= results.size())
254 hasMoreResults = false;
255 return new SliceImpl<>(results, pageable, hasMoreResults);
256 }
257 }
258
259 class DeleteExecution implements QueryExecution<T, ID> {
260
261 @Override
262 public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) throws BatchDeleteException {
263 List<T> entities = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList();
264 List<DynamoDBMapper.FailedBatch> failedBatches = dynamoDBOperations.batchDelete(entities);
265 if (failedBatches.isEmpty()) {
266 return entities;
267 } else {
268 throw repackageToException(failedBatches, BatchDeleteException.class);
269 }
270 }
271 }
272
273 class SingleEntityExecution implements QueryExecution<T, ID> {
274
275 @Override
276 public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
277 if (isCountQuery()) {
278 return dynamoDBQuery.doCreateCountQueryWithPermissions(values, false).getSingleResult();
279 } else if (isExistsQuery()) {
280 return !dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList().isEmpty();
281 } else {
282 return dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult();
283 }
284
285 }
286 }
287
288 class SingleEntityLimitedExecution implements QueryExecution<T, ID> {
289
290 @Override
291 public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
292 if (isCountQuery()) {
293 return dynamoDBQuery.doCreateCountQueryWithPermissions(values, false).getSingleResult();
294 } else {
295 List<T> resultList = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList();
296 return resultList.size() == 0 ? null : resultList.get(0);
297
298 }
299
300 }
301 }
302
303
304
305
306
307
308
309 public Object execute(Object[] parameters) {
310
311 return getExecution().execute(this, parameters);
312 }
313
314 @Override
315 public DynamoDBQueryMethod<T, ID> getQueryMethod() {
316 return this.method;
317 }
318
319 }